fog_pack/
timestamp.rs

1use std::cmp;
2use std::convert::TryFrom;
3use std::fmt;
4use std::ops;
5use std::ops::AddAssign;
6use std::ops::SubAssign;
7use std::sync::OnceLock;
8use std::sync::RwLock;
9use std::time::SystemTime;
10
11use byteorder::{LittleEndian, ReadBytesExt};
12
13use fog_crypto::serde::{
14    CryptoEnum, FOG_TYPE_ENUM, FOG_TYPE_ENUM_TIME_INDEX, FOG_TYPE_ENUM_TIME_NAME,
15};
16
17use serde::Deserialize;
18use serde::Serialize;
19use serde::{
20    de::{Deserializer, EnumAccess, Error, Unexpected, VariantAccess},
21    ser::{SerializeStructVariant, Serializer},
22};
23use serde_bytes::ByteBuf;
24
25const NTP_EPOCH_OFFSET: i64 = -86400 * (70 * 365 + 17);
26const MAX_NANOSEC: u32 = 999_999_999;
27const NANOS_PER_SEC: u32 = 1_000_000_000;
28const MICROS_PER_SEC: i64 = 1_000_000;
29const MILLIS_PER_SEC: i64 = 1_000;
30static UTC_LEAP: OnceLock<RwLock<LeapSeconds>> = OnceLock::new();
31
32fn get_table() -> std::sync::RwLockReadGuard<'static, LeapSeconds> {
33    let table = UTC_LEAP.get_or_init(|| RwLock::new(LeapSeconds::default()));
34    match table.read() {
35        Ok(o) => o,
36        Err(e) => e.into_inner(),
37    }
38}
39
40/// Correct a UTC timestamp to a proper TAI timestamp
41fn utc_to_tai(t: Timestamp) -> Timestamp {
42    let table = get_table();
43    t - table.reverse_leap_seconds(t)
44}
45
46/// Convert a TAI timestamp into a UTC timestamp
47fn tai_to_utc(t: Timestamp) -> Timestamp {
48    let table = get_table();
49    t + table.leap_seconds(t)
50}
51
52/// Set up the leap second table that converts from TAI to UTC and vice-versa.
53///
54/// See [`LeapSeconds`] for how to create a table for this function.
55pub fn set_utc_leap_seconds(table: LeapSeconds) {
56    let store = UTC_LEAP.get_or_init(|| RwLock::new(LeapSeconds::default()));
57    let mut store = match store.write() {
58        Ok(o) => o,
59        Err(e) => e.into_inner(),
60    };
61    *store = table;
62}
63
64/// A difference between [`Timestamp`] values. Can be used to adjust timestamps.
65#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize, Hash)]
66pub struct TimeDelta {
67    secs: i64,
68    nanos: u32,
69}
70
71impl TimeDelta {
72    /// Construct a new time delta. Fails if nanoseconds is one billion or
73    /// more.
74    pub fn new(secs: i64, nanos: u32) -> Option<Self> {
75        if nanos > MAX_NANOSEC {
76            return None;
77        }
78        Some(Self { secs, nanos })
79    }
80
81    /// Construct a `TimeDelta` from the specified number of seconds.
82    pub fn from_secs(secs: i64) -> Self {
83        Self { secs, nanos: 0 }
84    }
85
86    /// Construct a `TimeDelta` from the specified number of milliseconds.
87    pub fn from_millis(millis: i64) -> Self {
88        Self {
89            secs: millis.div_euclid(MILLIS_PER_SEC),
90            nanos: millis.rem_euclid(MILLIS_PER_SEC) as u32,
91        }
92    }
93
94    /// Construct a `TimeDelta` from the specified number of microseconds.
95    pub fn from_micros(micros: i64) -> Self {
96        Self {
97            secs: micros.div_euclid(MICROS_PER_SEC),
98            nanos: micros.rem_euclid(MICROS_PER_SEC) as u32,
99        }
100    }
101
102    /// Construct a `TimeDelta` from the specified number of nanoseconds.
103    pub fn from_nanos(nanos: i64) -> Self {
104        Self {
105            secs: nanos.div_euclid(NANOS_PER_SEC as i64),
106            nanos: nanos.rem_euclid(NANOS_PER_SEC as i64) as u32,
107        }
108    }
109
110    /// Returns the fractional part of this `TimeDelta`, in nanoseconds.
111    pub fn subsec_nanos(&self) -> u32 {
112        self.nanos
113    }
114
115    /// Returns the total number of whole seconds contained by this `TimeDelta`.
116    ///
117    /// The returned value doesn't contain the fractional part of the delta.
118    pub fn as_secs(&self) -> i64 {
119        self.secs
120    }
121}
122
123impl ops::AddAssign<TimeDelta> for TimeDelta {
124    fn add_assign(&mut self, rhs: TimeDelta) {
125        self.nanos += rhs.nanos;
126        self.secs += rhs.secs;
127        if self.nanos >= NANOS_PER_SEC {
128            self.nanos -= NANOS_PER_SEC;
129            self.secs += 1;
130        }
131    }
132}
133
134impl ops::Add<TimeDelta> for TimeDelta {
135    type Output = TimeDelta;
136    fn add(mut self, rhs: TimeDelta) -> Self::Output {
137        self.add_assign(rhs);
138        self
139    }
140}
141
142impl ops::SubAssign<TimeDelta> for TimeDelta {
143    fn sub_assign(&mut self, rhs: TimeDelta) {
144        if self.nanos < rhs.nanos {
145            self.nanos += NANOS_PER_SEC;
146            self.secs -= 1;
147        }
148        self.nanos -= rhs.nanos;
149        self.secs -= rhs.secs;
150    }
151}
152
153impl ops::Sub<TimeDelta> for TimeDelta {
154    type Output = TimeDelta;
155    fn sub(mut self, rhs: TimeDelta) -> Self::Output {
156        self.sub_assign(rhs);
157        self
158    }
159}
160
161impl ops::Neg for TimeDelta {
162    type Output = TimeDelta;
163    fn neg(mut self) -> Self::Output {
164        self.secs = -self.secs;
165        if self.nanos != 0 {
166            self.nanos = NANOS_PER_SEC - self.nanos;
167            self.secs -= 1;
168        }
169        self
170    }
171}
172
173/// A fully decoded leap second table, suitable for converting between time standards.
174///
175/// This table should contain strictly increasing timestamps and the associated
176/// time differences to apply from that timestamp onward. It can be used to get
177/// the difference to apply, and can take an invalid timestamp and correct it.
178///
179/// All timestamps should use TAI time starting from the 1970 UNIX epoch, and
180/// the timestamps in this table should be no different. That means this does
181/// *not* match the NTP leap second table; use
182/// [`from_ntp_file`][LeapSeconds::from_ntp_file] to parse one appropriately.
183///
184/// This struct doesn't implement `Serialize` or `Deserialize` for a reason;
185/// it's very easy to construct invalid tables. Consider making a format that
186/// delta-encodes timestamps and differences instead, which is more amenable to
187/// schema-based validation.
188#[derive(Clone, Debug)]
189pub struct LeapSeconds(pub Vec<(Timestamp, TimeDelta)>);
190
191impl Default for LeapSeconds {
192    fn default() -> Self {
193        let file = include_str!("leap-seconds.list");
194        Self::from_ntp_file(file).unwrap()
195    }
196}
197
198impl LeapSeconds {
199    /// Construct a new leap second table. Assumes the `Timestamp` values are
200    /// strictly increasing, that the `TimeDelta` values don't change by
201    /// more than a few seconds, and that the timestamps are spaced more than
202    /// twice the `TimeDelta` values apart.
203    pub fn new(table: Vec<(Timestamp, TimeDelta)>) -> Self {
204        Self(table)
205    }
206
207    /// Look up the amount of time to subtract from a timestamp that has leap
208    /// seconds in it. Used for converting from UTC to TAI. This function
209    /// assumes that the provided Timestamp is *incorrect*, in that it assumes
210    /// the Timestamp is in UTC Unix seconds.
211    pub fn reverse_leap_seconds(&self, t: Timestamp) -> TimeDelta {
212        for leap_second in self.0.iter().rev() {
213            if (t - leap_second.1) >= leap_second.0 {
214                return leap_second.1;
215            }
216        }
217        TimeDelta::default()
218    }
219
220    /// Look up the amount of time to add to a timestamp to compensate for leap
221    /// seconds, according to this table. Used for converting from TAI to UTC.
222    pub fn leap_seconds(&self, t: Timestamp) -> TimeDelta {
223        for leap_second in self.0.iter().rev() {
224            if t >= leap_second.0 {
225                return leap_second.1;
226            }
227        }
228        TimeDelta::default()
229    }
230
231    /// Parse a NTP leap seconds file that has been read in as a string.
232    ///
233    /// The latest leap seconds file can be fetched from
234    /// <https://hpiers.obspm.fr/iers/bul/bulc/ntp/leap-seconds.list>. This is
235    /// the file published by IERS, the official source of leap second
236    /// publications.
237    ///
238    /// Historical leap seconds files are available at
239    /// <https://hpiers.obspm.fr/iers/bul/bulc/ntp/>.
240    ///
241    /// The default leap seconds list is loaded from a compiled-in version of
242    /// this list, which will be updated whenever a new list is published
243    /// (bumping the patch version of this crate).
244    pub fn from_ntp_file(file: &str) -> Option<Self> {
245        let mut table = Vec::new();
246        for line in file.lines() {
247            if let Some(first_char) = line.chars().next() {
248                if first_char == '#' {
249                    continue;
250                } else {
251                    let mut data = line.split_whitespace();
252
253                    // Get the UTC time since 1900-01-01 00:00:00
254                    let Some(secs_utc) = data.next() else { return None };
255                    let Ok(secs_utc) = str::parse::<i64>(secs_utc) else { return None };
256
257                    // Get the delta to apply to the timestamp from this point onward
258                    let Some(delta) = data.next() else { return None };
259                    let Ok(delta) = str::parse::<i64>(delta) else { return None };
260
261                    // Create a proper TAI timestamp and put in the correct time delta to apply
262                    let time = Timestamp::from_tai_secs(secs_utc + delta + NTP_EPOCH_OFFSET);
263                    let delta = TimeDelta::from_secs(-delta);
264                    table.push((time, delta));
265                }
266            }
267        }
268        Some(LeapSeconds(table))
269    }
270}
271
272/// Structure for holding a raw fog-pack timestamp.
273/// This stores a TAI timestamp relative to the Unix epoch of 1970-01-01T00:00:00Z.
274/// This is what a correctly configured Linux TIME_TAI clock would return. It
275/// also matches the IEEE 1588 Precision Time Protocol standard epoch and timescale.
276///
277/// This is *not* UTC time, Unix Time, or POSIX Time.
278///
279/// Functions for converting from and to UTC are available as
280/// [`from_utc`][Self::from_utc] and [`utc`][Self::utc]. The conversion is
281/// handled using a built-in table of leap seconds. This table can be updated by
282/// calling [`set_utc_leap_seconds`] with a new table. See [`LeapSeconds`] for
283/// how to create a new table.
284///
285/// While these functions do their best to provide correct round-trip conversion
286/// for TAI->UTC->TAI and UTC->TAI->UTC, the handling around the exact leap
287/// second point can create a delta, due to UTC Unix time reusing a seconds
288/// value during the leap second. Using TAI directly if possible is thus
289/// preferred, as is sticking to Timestamps as much as possible and only
290/// converting back to UTC when you need to display the timestamp for people.
291#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
292pub struct Timestamp {
293    secs: i64,
294    nanos: u32,
295}
296
297impl Timestamp {
298    /// Create a TAI timestamp from a raw seconds + nanoseconds value. This
299    /// should be the number of seconds since the Unix epoch of
300    /// 1970-01-01T00:00:00Z, without any leap seconds thrown about.
301    pub fn from_tai(secs: i64, nanos: u32) -> Option<Timestamp> {
302        if nanos > MAX_NANOSEC {
303            return None;
304        }
305        Some(Timestamp { secs, nanos })
306    }
307
308    /// Create a timestamp from a raw UTC seconds + nanosecond value. This
309    /// should be the number of seconds since the Unix epoch of
310    /// 1970-01-01T00:00:00Z, with the usual UTC leap seconds thrown in. This is
311    /// commonly referred to as Unix time, and is the default timebase for many
312    /// computer systems.
313    ///
314    /// The UTC-to-TAI conversion is handled using a built-in table of leap
315    /// seconds. This table can be updated by calling [`set_utc_leap_seconds`]
316    /// with the table.
317    pub fn from_utc(secs: i64, nanos: u32) -> Option<Timestamp> {
318        if nanos > MAX_NANOSEC {
319            return None;
320        }
321        Some(utc_to_tai(Timestamp { secs, nanos }))
322    }
323
324    /// Create a timestamp from a raw UTC seconds value. See
325    /// [`from_utc`][Self::from_utc] for details.
326    pub fn from_utc_secs(secs: i64) -> Timestamp {
327        utc_to_tai(Timestamp { secs, nanos: 0 })
328    }
329
330    /// Create a timestamp from a raw TAI seconds value. See
331    /// [`from_tai`][Self::from_tai] for details.
332    pub fn from_tai_secs(secs: i64) -> Timestamp {
333        Timestamp { secs, nanos: 0 }
334    }
335
336    /// Zero time - TAI Unix epoch time
337    pub const fn zero() -> Timestamp {
338        Timestamp { secs: 0, nanos: 0 }
339    }
340
341    /// Minimum possible time that can be represented
342    pub const fn min_value() -> Timestamp {
343        Timestamp {
344            secs: i64::MIN,
345            nanos: 0,
346        }
347    }
348
349    /// Maximum possible time that can be represented
350    pub const fn max_value() -> Timestamp {
351        Timestamp {
352            secs: i64::MAX,
353            nanos: MAX_NANOSEC,
354        }
355    }
356
357    /// Find the earlier of two timestamps and return it.
358    pub fn min(self, other: Timestamp) -> Timestamp {
359        if self < other {
360            self
361        } else {
362            other
363        }
364    }
365
366    /// Find the later of two timestamps and return it.
367    pub fn max(self, other: Timestamp) -> Timestamp {
368        if self > other {
369            self
370        } else {
371            other
372        }
373    }
374
375    /// Add 1 nanosecond to timestamp.
376    pub fn next(mut self) -> Timestamp {
377        if self.nanos < MAX_NANOSEC {
378            self.nanos += 1;
379        } else {
380            self.nanos = 0;
381            self.secs += 1;
382        }
383        self
384    }
385
386    /// Subtract 1 nanosecond from timestamp.
387    pub fn prev(mut self) -> Timestamp {
388        if self.nanos > 0 {
389            self.nanos -= 1;
390        } else {
391            self.nanos = MAX_NANOSEC;
392            self.secs -= 1;
393        }
394        self
395    }
396
397    /// Return the UNIX timestamp (number of seconds since January 1, 1970
398    /// 0:00:00 UTC). As a reminder, this is UTC time and thus has leap seconds
399    /// removed/added.
400    ///
401    /// The TAI-to-UTC conversion is handled using a built-in table of leap
402    /// seconds. This table can be updated by calling [`set_utc_leap_seconds`]
403    /// with the table.
404    pub fn utc(&self) -> (i64, u32) {
405        let t = tai_to_utc(*self);
406        (t.secs, t.nanos)
407    }
408
409    /// Return the TAI number of seconds since January 1, 1970 00:00:00 UTC.
410    pub fn tai_secs(&self) -> i64 {
411        self.secs
412    }
413
414    /// Returns the number of nanoseconds past the second count.
415    pub fn tai_subsec_nanos(&self) -> u32 {
416        self.nanos
417    }
418
419    /// Calculates the time that has elapsed between the other timestamp and
420    /// this one. Effectively `self - other`.
421    pub fn time_since(&self, other: &Timestamp) -> TimeDelta {
422        let rhs = TimeDelta {
423            secs: other.secs,
424            nanos: other.nanos,
425        };
426        let new = *self - rhs;
427        TimeDelta {
428            secs: new.secs,
429            nanos: new.nanos,
430        }
431    }
432
433    /// Convert into a byte vector. For extending an existing byte vector, see
434    /// [`encode_vec`](Self::encode_vec).
435    pub fn as_vec(&self) -> Vec<u8> {
436        let mut v = Vec::new();
437        self.encode_vec(&mut v);
438        v
439    }
440
441    /// Encode onto a byte vector one of 3 formats:
442    /// 1. If nanoseconds is nonzero, encode the seconds as little-endian i64,
443    ///    and the nanoseconds as little-endian u32.
444    /// 2. If nanoseconds is zero & seconds maps to a u32, encode just the
445    ///    seconds as little-endian u32.
446    /// 3. If nanoseconds is zero & seconds does not map to a u32, encode the
447    ///    seconds as little-endian i64.
448    ///
449    /// The length can be used to determine the format in which it was written.
450    pub fn encode_vec(&self, vec: &mut Vec<u8>) {
451        if self.nanos != 0 {
452            vec.reserve(8 + 4);
453            vec.extend_from_slice(&self.secs.to_le_bytes());
454            vec.extend_from_slice(&self.nanos.to_le_bytes());
455        } else if (self.secs <= u32::MAX as i64) && (self.secs >= 0) {
456            vec.reserve(4);
457            vec.extend_from_slice(&(self.secs as u32).to_le_bytes());
458        } else {
459            vec.reserve(8);
460            vec.extend_from_slice(&self.secs.to_le_bytes());
461        }
462    }
463
464    /// Return the number of bytes needed to encode the timestamp as a byte
465    /// vector with [`encode_vec`][Self::encode_vec].
466    pub fn size(&self) -> usize {
467        if self.nanos != 0 {
468            8 + 4
469        } else if (self.secs <= u32::MAX as i64) && (self.secs >= 0) {
470            4
471        } else {
472            8
473        }
474    }
475
476    /// Create a Timestamp based on the current system time.
477    pub fn now() -> Timestamp {
478        Timestamp::from(SystemTime::now())
479    }
480}
481
482impl From<SystemTime> for Timestamp {
483    fn from(value: SystemTime) -> Self {
484        let t = value.duration_since(SystemTime::UNIX_EPOCH).unwrap();
485        Timestamp::from_utc(t.as_secs() as i64, t.subsec_nanos()).unwrap()
486    }
487}
488
489impl ops::Add<i64> for Timestamp {
490    type Output = Timestamp;
491    fn add(mut self, rhs: i64) -> Self {
492        self.secs += rhs;
493        self
494    }
495}
496
497impl ops::AddAssign<i64> for Timestamp {
498    fn add_assign(&mut self, rhs: i64) {
499        self.secs += rhs;
500    }
501}
502
503impl ops::Sub<i64> for Timestamp {
504    type Output = Timestamp;
505    fn sub(mut self, rhs: i64) -> Self {
506        self.secs -= rhs;
507        self
508    }
509}
510
511impl ops::SubAssign<i64> for Timestamp {
512    fn sub_assign(&mut self, rhs: i64) {
513        self.secs -= rhs;
514    }
515}
516
517impl ops::Add<TimeDelta> for Timestamp {
518    type Output = Timestamp;
519    fn add(mut self, rhs: TimeDelta) -> Timestamp {
520        self += rhs;
521        self
522    }
523}
524
525impl ops::AddAssign<TimeDelta> for Timestamp {
526    fn add_assign(&mut self, rhs: TimeDelta) {
527        self.nanos += rhs.nanos;
528        if self.nanos >= NANOS_PER_SEC {
529            self.nanos -= NANOS_PER_SEC;
530            self.secs += 1;
531        }
532        self.secs += rhs.secs;
533    }
534}
535
536impl ops::Sub<TimeDelta> for Timestamp {
537    type Output = Timestamp;
538    fn sub(mut self, rhs: TimeDelta) -> Timestamp {
539        self -= rhs;
540        self
541    }
542}
543
544impl ops::SubAssign<TimeDelta> for Timestamp {
545    fn sub_assign(&mut self, rhs: TimeDelta) {
546        if self.nanos < rhs.nanos {
547            self.nanos += NANOS_PER_SEC;
548            self.secs -= 1;
549        }
550        self.nanos -= rhs.nanos;
551        self.secs -= rhs.secs;
552    }
553}
554
555impl ops::Sub<Timestamp> for Timestamp {
556    type Output = TimeDelta;
557    fn sub(self, rhs: Timestamp) -> TimeDelta {
558        self.time_since(&rhs)
559    }
560}
561
562impl cmp::Ord for Timestamp {
563    fn cmp(&self, other: &Timestamp) -> cmp::Ordering {
564        match self.secs.cmp(&other.secs) {
565            cmp::Ordering::Equal => self.nanos.cmp(&other.nanos),
566            other => other,
567        }
568    }
569}
570
571impl cmp::PartialOrd for Timestamp {
572    fn partial_cmp(&self, other: &Timestamp) -> Option<cmp::Ordering> {
573        Some(self.cmp(other))
574    }
575}
576
577impl fmt::Display for Timestamp {
578    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
579        write!(f, "TAI: {} secs + {} ns", self.secs, self.nanos)
580    }
581}
582
583/// Parse an encoded timestamp. Must be 4, 8, or 12 bytes (matching what was
584/// written by [`Timestamp::encode_vec`])
585impl TryFrom<&[u8]> for Timestamp {
586    type Error = String;
587    fn try_from(value: &[u8]) -> Result<Self, Self::Error> {
588        let mut raw = value;
589        let (secs, nanos) = match value.len() {
590            12 => {
591                let secs = raw.read_i64::<LittleEndian>().unwrap();
592                let nanos = raw.read_u32::<LittleEndian>().unwrap();
593                (secs, nanos)
594            }
595            8 => {
596                let secs = raw.read_i64::<LittleEndian>().unwrap();
597                (secs, 0)
598            }
599            4 => {
600                let secs = raw.read_u32::<LittleEndian>().unwrap() as i64;
601                (secs, 0)
602            }
603            _ => {
604                return Err(format!(
605                    "not a recognized Timestamp length ({} bytes)",
606                    value.len()
607                ))
608            }
609        };
610        Ok(Timestamp { secs, nanos })
611    }
612}
613
614impl serde::ser::Serialize for Timestamp {
615    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
616    where
617        S: Serializer,
618    {
619        if serializer.is_human_readable() {
620            // Human-readable format instead of compacted byte sequence
621            let mut sv = serializer.serialize_struct_variant(
622                FOG_TYPE_ENUM,
623                FOG_TYPE_ENUM_TIME_INDEX as u32,
624                FOG_TYPE_ENUM_TIME_NAME,
625                2,
626            )?;
627            // Always serialize all fields, in case the field names are omitted and this is turned
628            // into just an array
629            sv.serialize_field("secs", &self.secs)?;
630            sv.serialize_field("nanos", &self.nanos)?;
631            sv.end()
632        } else {
633            // Use compacted byte sequence if not human-readable
634            let value = ByteBuf::from(self.as_vec());
635            serializer.serialize_newtype_variant(
636                FOG_TYPE_ENUM,
637                FOG_TYPE_ENUM_TIME_INDEX as u32,
638                FOG_TYPE_ENUM_TIME_NAME,
639                &value,
640            )
641        }
642    }
643}
644
645impl<'de> serde::de::Deserialize<'de> for Timestamp {
646    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
647    where
648        D: Deserializer<'de>,
649    {
650        struct TimeVisitor {
651            is_human_readable: bool,
652        }
653
654        impl<'de> serde::de::Visitor<'de> for TimeVisitor {
655            type Value = Timestamp;
656
657            fn expecting(&self, fmt: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
658                write!(
659                    fmt,
660                    "{} enum with variant {} (id {})",
661                    FOG_TYPE_ENUM, FOG_TYPE_ENUM_TIME_NAME, FOG_TYPE_ENUM_TIME_INDEX
662                )
663            }
664
665            fn visit_enum<A>(self, data: A) -> Result<Self::Value, A::Error>
666            where
667                A: EnumAccess<'de>,
668            {
669                let variant = match data.variant()? {
670                    (CryptoEnum::Time, variant) => variant,
671                    (e, _) => {
672                        return Err(A::Error::invalid_type(
673                            Unexpected::Other(e.as_str()),
674                            &"Time",
675                        ))
676                    }
677                };
678                if self.is_human_readable {
679                    use serde::de::MapAccess;
680                    struct TimeStructVisitor;
681                    impl<'de> serde::de::Visitor<'de> for TimeStructVisitor {
682                        type Value = Timestamp;
683                        fn expecting(
684                            &self,
685                            fmt: &mut fmt::Formatter<'_>,
686                        ) -> Result<(), fmt::Error> {
687                            write!(fmt, "timestamp struct")
688                        }
689
690                        fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error>
691                        where
692                            A: MapAccess<'de>,
693                        {
694                            let mut secs: Option<i64> = None;
695                            let mut nanos: u32 = 0;
696                            while let Some(key) = map.next_key::<String>()? {
697                                match key.as_ref() {
698                                    "std" => {
699                                        let v: u8 = map.next_value()?;
700                                        if v != 0 {
701                                            return Err(A::Error::invalid_value(
702                                                Unexpected::Unsigned(v as u64),
703                                                &"0",
704                                            ));
705                                        }
706                                    }
707                                    "secs" => {
708                                        secs = Some(map.next_value()?);
709                                    }
710                                    "nanos" => {
711                                        nanos = map.next_value()?;
712                                    }
713                                    _ => {
714                                        return Err(A::Error::unknown_field(
715                                            key.as_ref(),
716                                            &["std", "secs", "nanos"],
717                                        ))
718                                    }
719                                }
720                            }
721                            let secs = secs.ok_or_else(|| A::Error::missing_field("secs"))?;
722                            Timestamp::from_tai(secs, nanos)
723                                .ok_or_else(|| A::Error::custom("Invalid timestamp"))
724                        }
725                    }
726                    variant.struct_variant(&["std", "secs", "nanos"], TimeStructVisitor)
727                } else {
728                    let bytes: ByteBuf = variant.newtype_variant()?;
729                    Timestamp::try_from(bytes.as_ref()).map_err(A::Error::custom)
730                }
731            }
732        }
733
734        let is_human_readable = deserializer.is_human_readable();
735        deserializer.deserialize_enum(
736            FOG_TYPE_ENUM,
737            &[FOG_TYPE_ENUM_TIME_NAME],
738            TimeVisitor { is_human_readable },
739        )
740    }
741}
742
743#[cfg(test)]
744mod test {
745    use super::*;
746
747    fn edge_cases() -> Vec<(usize, Timestamp)> {
748        vec![
749            (4, Timestamp::from_tai(0, 0).unwrap()),
750            (4, Timestamp::from_tai(1, 0).unwrap()),
751            (12, Timestamp::from_tai(1, 1).unwrap()),
752            (4, Timestamp::from_tai(u32::MAX as i64 - 1, 0).unwrap()),
753            (4, Timestamp::from_tai(u32::MAX as i64, 0).unwrap()),
754            (8, Timestamp::from_tai(u32::MAX as i64 + 1, 0).unwrap()),
755            (8, Timestamp::from_tai(i64::MIN, 0).unwrap()),
756            (12, Timestamp::from_tai(i64::MIN, 1).unwrap()),
757        ]
758    }
759
760    #[test]
761    fn roundtrip() {
762        for (index, case) in edge_cases().iter().enumerate() {
763            println!(
764                "Test #{}: '{}' with expected length = {}",
765                index, case.1, case.0
766            );
767            let mut enc = Vec::new();
768            case.1.encode_vec(&mut enc);
769            assert_eq!(enc.len(), case.0);
770            let decoded = Timestamp::try_from(enc.as_ref()).unwrap();
771            assert_eq!(decoded, case.1);
772        }
773    }
774
775    #[test]
776    fn too_long() {
777        for case in edge_cases() {
778            println!("Test with Timestamp = {}", case.1);
779            let mut enc = Vec::new();
780            case.1.encode_vec(&mut enc);
781            enc.push(0u8);
782            assert!(Timestamp::try_from(enc.as_ref()).is_err());
783        }
784    }
785
786    #[test]
787    fn too_short() {
788        for case in edge_cases() {
789            println!("Test with Timestamp = {}", case.1);
790            let mut enc = Vec::new();
791            case.1.encode_vec(&mut enc);
792            enc.pop();
793            assert!(Timestamp::try_from(enc.as_ref()).is_err());
794        }
795    }
796
797    #[test]
798    fn leap_seconds() {
799        let table = LeapSeconds::default();
800        let (tai_time, diff) = table.0.last().unwrap();
801        assert_eq!(*diff, TimeDelta::from_secs(-37));
802        assert_eq!(tai_time.utc().0, 3692217600 + NTP_EPOCH_OFFSET);
803        assert_eq!(tai_time.utc().1, 0);
804        for i in -5..=5 {
805            // TAI should *almost* perfectly round-trip. UTC can't represent the
806            // leap second correctly, so that point will have a delta.
807            let time = *tai_time + i;
808            let utc = time.utc();
809            let time2 = Timestamp::from_utc(utc.0, utc.1).unwrap();
810            if i == -1 {
811                assert_eq!(
812                    time,
813                    time2 - 1,
814                    "Failed for offset of {}, expected a diff of 1",
815                    i
816                );
817            } else {
818                assert_eq!(time, time2, "Failed for offset of {}, expected no diff", i);
819            }
820
821            // UTC should always perfectly round-trip
822            let utc = tai_time.tai_secs() - diff.as_secs() + i;
823            let tai = Timestamp::from_utc_secs(utc);
824            let utc2 = tai.utc();
825            assert_eq!(utc2.0, utc, "Failed for offset of {}, expected no diff", i);
826            assert_eq!(
827                utc2.1, 0,
828                "Failed for offset of {}, expected 0 ns for UTC",
829                i
830            );
831        }
832    }
833
834    #[test]
835    fn check_diffs() {
836        let time = Timestamp::from_tai(5, 5).unwrap();
837        let time2 = Timestamp::from_tai(6, 1).unwrap();
838        let diff = time2.time_since(&time);
839        let diff2 = time.time_since(&time2);
840        let neg_diff2 = -diff2;
841        let neg_diff3 = -neg_diff2;
842        assert_eq!(diff, neg_diff2);
843        assert_eq!(diff2, neg_diff3);
844    }
845}