tai64/
lib.rs

1#![no_std]
2#![doc = include_str!("../README.md")]
3#![doc(html_root_url = "https://docs.rs/tai64/4.0.0")]
4#![forbid(unsafe_code)]
5#![warn(missing_docs, rust_2018_idioms, unused_qualifications)]
6
7#[cfg(feature = "std")]
8extern crate std;
9
10use core::{fmt, ops, time::Duration};
11
12#[cfg(feature = "serde")]
13use serde::{de, ser, Deserialize, Serialize};
14
15#[cfg(feature = "std")]
16use std::time::{SystemTime, UNIX_EPOCH};
17
18#[cfg(feature = "zeroize")]
19use zeroize::Zeroize;
20
21/// Number of nanoseconds in a second
22const NANOS_PER_SECOND: u32 = 1_000_000_000;
23
24/// A `TAI64` label.
25#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)]
26pub struct Tai64(pub u64);
27
28impl Tai64 {
29    /// Unix epoch in `TAI64`: 1970-01-01 00:00:10 TAI.
30    pub const UNIX_EPOCH: Self = Self(10 + (1 << 62));
31
32    /// Length of serialized `TAI64` timestamp in bytes.
33    pub const BYTE_SIZE: usize = 8;
34
35    /// Get `TAI64N` timestamp according to system clock.
36    #[cfg(feature = "std")]
37    pub fn now() -> Self {
38        Tai64N::now().into()
39    }
40
41    /// Parse `TAI64` from a byte slice
42    pub fn from_slice(slice: &[u8]) -> Result<Self, Error> {
43        slice.try_into()
44    }
45
46    /// Serialize TAI64 as bytes
47    pub fn to_bytes(self) -> [u8; Self::BYTE_SIZE] {
48        self.into()
49    }
50
51    /// Convert Unix timestamp to `TAI64`.
52    pub fn from_unix(secs: i64) -> Self {
53        Tai64((secs + 10 + (1 << 62)) as u64)
54    }
55
56    /// Convert `TAI64` to unix timestamp.
57    pub fn to_unix(self) -> i64 {
58        (self.0 as i64) - (10 + (1 << 62))
59    }
60}
61
62impl From<Tai64N> for Tai64 {
63    /// Remove the nanosecond component from a TAI64N value
64    fn from(other: Tai64N) -> Self {
65        other.0
66    }
67}
68
69impl From<[u8; Tai64::BYTE_SIZE]> for Tai64 {
70    /// Parse TAI64 from external representation
71    fn from(bytes: [u8; Tai64::BYTE_SIZE]) -> Self {
72        Tai64(u64::from_be_bytes(bytes))
73    }
74}
75
76impl<'a> TryFrom<&'a [u8]> for Tai64 {
77    type Error = Error;
78
79    fn try_from(slice: &'a [u8]) -> Result<Self, Error> {
80        let bytes: [u8; Tai64::BYTE_SIZE] = slice.try_into().map_err(|_| Error::LengthInvalid)?;
81        Ok(bytes.into())
82    }
83}
84
85impl From<Tai64> for [u8; 8] {
86    /// Serialize TAI64 to external representation
87    fn from(tai: Tai64) -> [u8; 8] {
88        tai.0.to_be_bytes()
89    }
90}
91
92impl ops::Add<u64> for Tai64 {
93    type Output = Self;
94
95    fn add(self, x: u64) -> Self {
96        Tai64(self.0 + x)
97    }
98}
99
100impl ops::Sub<u64> for Tai64 {
101    type Output = Self;
102
103    fn sub(self, x: u64) -> Self {
104        Tai64(self.0 - x)
105    }
106}
107
108#[cfg(feature = "serde")]
109impl<'de> Deserialize<'de> for Tai64 {
110    fn deserialize<D: de::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
111        Ok(<[u8; Tai64::BYTE_SIZE]>::deserialize(deserializer)?.into())
112    }
113}
114
115#[cfg(feature = "serde")]
116impl Serialize for Tai64 {
117    fn serialize<S: ser::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
118        self.to_bytes().serialize(serializer)
119    }
120}
121
122#[cfg(feature = "zeroize")]
123impl Zeroize for Tai64 {
124    fn zeroize(&mut self) {
125        self.0.zeroize();
126    }
127}
128
129/// A `TAI64N` timestamp.
130///
131/// Invariant: The nanosecond part <= 999999999.
132#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)]
133pub struct Tai64N(pub Tai64, pub u32);
134
135#[cfg(feature = "zeroize")]
136impl Zeroize for Tai64N {
137    fn zeroize(&mut self) {
138        self.0.zeroize();
139        self.1.zeroize();
140    }
141}
142
143impl Tai64N {
144    /// Unix epoch in `TAI64N`: 1970-01-01 00:00:10 TAI.
145    pub const UNIX_EPOCH: Self = Self(Tai64::UNIX_EPOCH, 0);
146
147    /// Length of serialized `TAI64N` timestamp.
148    pub const BYTE_SIZE: usize = 12;
149
150    /// Get `TAI64N` timestamp according to system clock.
151    #[cfg(feature = "std")]
152    pub fn now() -> Self {
153        Self::from_system_time(&SystemTime::now())
154    }
155
156    /// Parse TAI64N from a byte slice
157    pub fn from_slice(slice: &[u8]) -> Result<Self, Error> {
158        slice.try_into()
159    }
160
161    /// Serialize TAI64N as bytes
162    pub fn to_bytes(self) -> [u8; Tai64N::BYTE_SIZE] {
163        self.into()
164    }
165
166    /// Calculate how much time passes since the `other` timestamp.
167    ///
168    /// Returns `Ok(Duration)` if `other` is earlier than `self`,
169    /// `Err(Duration)` otherwise.
170    pub fn duration_since(&self, other: &Self) -> Result<Duration, Duration> {
171        if self >= other {
172            let (carry, n) = if self.1 >= other.1 {
173                (0, self.1 - other.1)
174            } else {
175                (1, NANOS_PER_SECOND + self.1 - other.1)
176            };
177
178            let s = (self.0).0 - carry - (other.0).0;
179            Ok(Duration::new(s, n))
180        } else {
181            Err(other.duration_since(self).unwrap())
182        }
183    }
184
185    /// Convert `SystemTime` to `TAI64N`.
186    #[allow(clippy::trivially_copy_pass_by_ref)]
187    #[cfg(feature = "std")]
188    pub fn from_system_time(t: &SystemTime) -> Self {
189        match t.duration_since(UNIX_EPOCH) {
190            Ok(d) => Self::UNIX_EPOCH + d,
191            Err(e) => Self::UNIX_EPOCH - e.duration(),
192        }
193    }
194
195    /// Convert `TAI64N`to `SystemTime`.
196    #[cfg(feature = "std")]
197    pub fn to_system_time(self) -> SystemTime {
198        match self.duration_since(&Self::UNIX_EPOCH) {
199            Ok(d) => UNIX_EPOCH + d,
200            Err(d) => UNIX_EPOCH - d,
201        }
202    }
203}
204
205impl From<Tai64> for Tai64N {
206    /// Remove the nanosecond component from a TAI64N value
207    fn from(other: Tai64) -> Self {
208        Tai64N(other, 0)
209    }
210}
211
212impl TryFrom<[u8; Self::BYTE_SIZE]> for Tai64N {
213    type Error = Error;
214
215    /// Parse TAI64 from external representation
216    fn try_from(bytes: [u8; Tai64N::BYTE_SIZE]) -> Result<Self, Error> {
217        let secs = Tai64::from_slice(&bytes[..Tai64::BYTE_SIZE])?;
218
219        let mut nano_bytes = [0u8; 4];
220        nano_bytes.copy_from_slice(&bytes[Tai64::BYTE_SIZE..]);
221        let nanos = u32::from_be_bytes(nano_bytes);
222
223        if nanos < NANOS_PER_SECOND {
224            Ok(Tai64N(secs, nanos))
225        } else {
226            Err(Error::NanosInvalid)
227        }
228    }
229}
230
231impl<'a> TryFrom<&'a [u8]> for Tai64N {
232    type Error = Error;
233
234    fn try_from(slice: &'a [u8]) -> Result<Self, Error> {
235        let bytes: [u8; Tai64N::BYTE_SIZE] = slice.try_into().map_err(|_| Error::LengthInvalid)?;
236        bytes.try_into()
237    }
238}
239
240impl From<Tai64N> for [u8; Tai64N::BYTE_SIZE] {
241    /// Serialize TAI64 to external representation
242    fn from(tai: Tai64N) -> [u8; Tai64N::BYTE_SIZE] {
243        let mut result = [0u8; Tai64N::BYTE_SIZE];
244        result[..Tai64::BYTE_SIZE].copy_from_slice(&tai.0.to_bytes());
245        result[Tai64::BYTE_SIZE..].copy_from_slice(&tai.1.to_be_bytes());
246        result
247    }
248}
249
250#[cfg(feature = "std")]
251impl From<SystemTime> for Tai64N {
252    fn from(t: SystemTime) -> Self {
253        Tai64N::from_system_time(&t)
254    }
255}
256
257#[allow(clippy::suspicious_arithmetic_impl)]
258impl ops::Add<Duration> for Tai64N {
259    type Output = Self;
260
261    fn add(self, d: Duration) -> Self {
262        let n = self.1 + d.subsec_nanos();
263
264        let (carry, n) = if n >= NANOS_PER_SECOND {
265            (1, n - NANOS_PER_SECOND)
266        } else {
267            (0, n)
268        };
269
270        Tai64N(self.0 + d.as_secs() + carry, n)
271    }
272}
273
274impl ops::Sub<Duration> for Tai64N {
275    type Output = Self;
276
277    fn sub(self, d: Duration) -> Self {
278        let (carry, n) = if self.1 >= d.subsec_nanos() {
279            (0, self.1 - d.subsec_nanos())
280        } else {
281            (1, NANOS_PER_SECOND + self.1 - d.subsec_nanos())
282        };
283        Tai64N(self.0 - carry - d.as_secs(), n)
284    }
285}
286
287#[cfg(feature = "serde")]
288impl<'de> Deserialize<'de> for Tai64N {
289    fn deserialize<D: de::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
290        use de::Error;
291        <[u8; Tai64N::BYTE_SIZE]>::deserialize(deserializer)?
292            .try_into()
293            .map_err(D::Error::custom)
294    }
295}
296
297#[cfg(feature = "serde")]
298impl Serialize for Tai64N {
299    fn serialize<S: ser::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
300        self.to_bytes().serialize(serializer)
301    }
302}
303
304/// TAI64 errors.
305#[derive(Copy, Clone, Debug, Eq, PartialEq)]
306pub enum Error {
307    /// Invalid length
308    LengthInvalid,
309
310    /// Nanosecond part must be <= 999999999.
311    NanosInvalid,
312}
313
314impl fmt::Display for Error {
315    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
316        let description = match self {
317            Error::LengthInvalid => "length invalid",
318            Error::NanosInvalid => "invalid number of nanoseconds",
319        };
320
321        write!(f, "{}", description)
322    }
323}
324
325#[cfg(feature = "std")]
326impl std::error::Error for Error {}
327
328#[cfg(all(test, feature = "std"))]
329mod tests {
330    use super::*;
331
332    #[test]
333    fn before_epoch() {
334        let t = UNIX_EPOCH - Duration::new(0, 1);
335        let tai64n = Tai64N::from_system_time(&t);
336        let t1 = tai64n.to_system_time();
337
338        assert_eq!(t, t1);
339
340        let t = UNIX_EPOCH - Duration::new(488294802189, 999999999);
341        let tai64n = Tai64N::from_system_time(&t);
342        let t1 = tai64n.to_system_time();
343
344        assert_eq!(t, t1);
345
346        let t = UNIX_EPOCH - Duration::new(73234, 68416841);
347        let tai64n = Tai64N::from_system_time(&t);
348        let t1 = tai64n.to_system_time();
349
350        assert_eq!(t, t1);
351    }
352}