Skip to main content

statime_base/
time_types.rs

1//! Time types useful for time synchronization daemons
2//!
3//! This module provides time types explicitly designed for use in time
4//! synchronization daemons. This includes support for marking the timescale
5//! used, as well as support for signed durations, which are useful for
6//! representing offsets between clocks.
7
8use core::{
9    marker::PhantomData,
10    ops::{Add, AddAssign, Div, Mul, MulAssign, Neg, Sub, SubAssign},
11};
12
13#[cfg(feature = "serde")]
14use serde::{Deserialize, Serialize};
15
16/// A timestamp in the scale `Timescale`.
17///
18/// Arithmetic on timestamps is implemented as wrapping.
19///
20/// The timescale ensures that arithmetic done with timescales
21/// is done with the correct timescale on both sides. This ensures
22/// that for example the following doesn't compile:
23///
24/// ```compile_fail
25/// # use statime_base::{Timestamp, UTC, TAI};
26/// let delta = Timestamp::<UTC>::from_seconds_nanos_since_unix_epoch(100, 0) - Timestamp::<TAI>::from_seconds_nanos_since_unix_epoch(50, 0);
27/// ```
28// The internal value is in units of 2^-64ths of a second, with the UNIX EPOCH as 0
29pub struct Timestamp<Timescale>(u128, PhantomData<Timescale>);
30
31#[cfg(feature = "serde")]
32#[derive(Deserialize, Serialize)]
33struct SerializationTimestamp {
34    upper: u64,
35    lower: u64,
36}
37
38#[cfg(feature = "serde")]
39impl<T> Serialize for Timestamp<T> {
40    #[expect(clippy::cast_possible_truncation, reason = "Cast will never truncate")]
41    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
42    where
43        S: serde::Serializer,
44    {
45        SerializationTimestamp {
46            upper: (self.0 >> 64) as u64,
47            lower: (self.0 & u128::from(u64::MAX)) as u64,
48        }
49        .serialize(serializer)
50    }
51}
52
53#[cfg(feature = "serde")]
54impl<'de, T> Deserialize<'de> for Timestamp<T> {
55    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
56    where
57        D: serde::Deserializer<'de>,
58    {
59        let st = SerializationTimestamp::deserialize(deserializer)?;
60        Ok(Self(
61            (u128::from(st.upper) << 64) | u128::from(st.lower),
62            PhantomData,
63        ))
64    }
65}
66
67/// Marker for the UTC timescale
68pub struct UTC;
69/// Marker for the TAI timescale
70pub struct TAI;
71
72/// A span of time, or a difference of two timestamps.
73///
74/// Arithmetic on durations is implemented as saturating.
75// The internal value is in units of 2^-64ths of a second.
76#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
77pub struct Duration(i128);
78
79#[cfg(feature = "serde")]
80#[derive(Serialize, Deserialize)]
81struct SerializationDuration {
82    upper: i64,
83    lower: u64,
84}
85
86#[cfg(feature = "serde")]
87impl Serialize for Duration {
88    #[expect(clippy::cast_possible_truncation, reason = "Cast will never truncate")]
89    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
90    where
91        S: serde::Serializer,
92    {
93        let raw = self.0.cast_unsigned();
94        SerializationDuration {
95            upper: ((raw >> 64) as u64).cast_signed(),
96            lower: (raw & u128::from(u64::MAX)) as u64,
97        }
98        .serialize(serializer)
99    }
100}
101
102#[cfg(feature = "serde")]
103impl<'de> Deserialize<'de> for Duration {
104    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
105    where
106        D: serde::Deserializer<'de>,
107    {
108        let sd = SerializationDuration::deserialize(deserializer)?;
109        Ok(Self(
110            ((u128::from(sd.upper.cast_unsigned()) << 64) | u128::from(sd.lower)).cast_signed(),
111        ))
112    }
113}
114
115/// A timestamp that can be in either the UTC or TAI timescales.
116#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
117pub enum UniversalTimestamp {
118    /// A UTC timestamp
119    Utc(Timestamp<UTC>),
120    /// A TAI timestamp
121    Tai(Timestamp<TAI>),
122}
123
124impl From<Timestamp<UTC>> for UniversalTimestamp {
125    fn from(value: Timestamp<UTC>) -> Self {
126        Self::Utc(value)
127    }
128}
129
130impl From<Timestamp<TAI>> for UniversalTimestamp {
131    fn from(value: Timestamp<TAI>) -> Self {
132        Self::Tai(value)
133    }
134}
135
136impl UniversalTimestamp {
137    /// Get the timestamp converted to a UTC timestamp, given the difference TAI-UTC.
138    #[must_use]
139    pub fn as_utc(self, tai_offset: i64) -> Timestamp<UTC> {
140        match self {
141            UniversalTimestamp::Utc(timestamp) => timestamp,
142            UniversalTimestamp::Tai(timestamp) => {
143                Timestamp::UNIX_EPOCH - Duration::from_seconds_nanos(tai_offset, 0)
144                    + (timestamp - Timestamp::UNIX_EPOCH)
145            }
146        }
147    }
148
149    /// Get the timestamp converted to a TAI timestamp, given the difference TAI-UTC.
150    #[must_use]
151    pub fn as_tai(self, tai_offset: i64) -> Timestamp<TAI> {
152        match self {
153            UniversalTimestamp::Utc(timestamp) => {
154                Timestamp::UNIX_EPOCH
155                    + Duration::from_seconds_nanos(tai_offset, 0)
156                    + (timestamp - Timestamp::UNIX_EPOCH)
157            }
158            UniversalTimestamp::Tai(timestamp) => timestamp,
159        }
160    }
161}
162
163impl<A> Timestamp<A> {
164    /// Representation of the UNIX EPOCH in this timescale.
165    ///
166    /// In other words: midnight on the first of januari 1970, as defined in
167    /// the given timescale.
168    pub const UNIX_EPOCH: Timestamp<A> = Timestamp(0, PhantomData);
169
170    /// Create a new timestamp on the timescale, the given number of seconds
171    /// and nanoseconds since the unix epoch.
172    #[must_use]
173    pub fn from_seconds_nanos_since_unix_epoch(seconds: u64, nanos: u32) -> Timestamp<A> {
174        let converted_nanos = (u128::from(nanos) << 64) / 1_000_000_000;
175        Timestamp((u128::from(seconds) << 64) + converted_nanos, PhantomData)
176    }
177}
178
179impl<A> core::fmt::Debug for Timestamp<A> {
180    #[expect(
181        clippy::cast_precision_loss,
182        reason = "Loss of precision isn't important for debug printing."
183    )]
184    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
185        f.write_fmt(format_args!(
186            "Timestamp<{}>({}s)",
187            core::any::type_name::<A>(),
188            ((self.0 as f64) / ((1u128 << 64) as f64))
189        ))
190    }
191}
192
193impl<A> Clone for Timestamp<A> {
194    fn clone(&self) -> Self {
195        *self
196    }
197}
198
199impl<A> Copy for Timestamp<A> {}
200
201impl<A> PartialEq for Timestamp<A> {
202    fn eq(&self, other: &Self) -> bool {
203        self.0 == other.0
204    }
205}
206
207impl<A> Eq for Timestamp<A> {}
208
209impl<A> core::hash::Hash for Timestamp<A> {
210    fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
211        self.0.hash(state);
212    }
213}
214
215impl Duration {
216    /// A zero-length duration.
217    pub const ZERO: Duration = Duration(0);
218
219    /// Absolute length of the duration, saturated.
220    #[must_use]
221    pub fn abs(self) -> Duration {
222        Self(self.0.saturating_abs())
223    }
224
225    /// The length of the duration as a floating point number of seconds.
226    ///
227    /// Note: Very large durations will loose precision when converted
228    /// to a floating point.
229    #[must_use]
230    #[expect(
231        clippy::cast_precision_loss,
232        reason = "Power of two u128 to float cast is exact, other precision rounding errors are acceptable"
233    )]
234    pub fn as_seconds(self) -> f64 {
235        (self.0 as f64) / ((1u128 << 64) as f64)
236    }
237
238    /// The length of the duration in steps (2^-64ths of a second)
239    #[must_use]
240    pub fn as_raw_steps(self) -> i128 {
241        self.0
242    }
243
244    /// The length of the duration in nanoseconds, rounded.
245    #[must_use]
246    pub fn as_nanos(self) -> i128 {
247        // We split and do the subsecond and second parts separately to avoid overflows.
248        let subseconds = (self.0.cast_unsigned() & u128::from(u64::MAX)) * 1_000_000_000;
249        let seconds = self.0 >> 64;
250        let nanos_subseconds = (subseconds >> 64)
251            + u128::from((subseconds & 0xFFFF_FFFF_FFFF_FFFF) >= 0x8000_0000_0000_0000);
252        seconds * 1_000_000_000 + nanos_subseconds.cast_signed()
253    }
254
255    /// Create a duration from a length in steps (2^-64ths of a second)
256    #[must_use]
257    pub fn from_raw_steps(steps: i128) -> Self {
258        Self(steps)
259    }
260
261    /// Log2 length of the duration
262    #[must_use]
263    #[expect(
264        clippy::cast_possible_truncation,
265        reason = "The casts to i32 and i8 always fit"
266    )]
267    pub fn log2(self) -> i8 {
268        if self == Duration::ZERO {
269            return i8::MIN;
270        }
271
272        ((self.0.ilog2().cast_signed()) - 64) as i8
273    }
274
275    /// Create a duration from a floating point number of seconds.
276    ///
277    /// Note: If the provided value is too large or too small to representable as a
278    /// duration, the result will be clamped to the maximum or minimum respectively.
279    #[must_use]
280    #[expect(
281        clippy::cast_precision_loss,
282        reason = "Power of two u128 to float cast is exact, other precision rounding errors are acceptable"
283    )]
284    #[expect(
285        clippy::cast_possible_truncation,
286        reason = "Acceptable: large durations may truncate to the maximum/minimum representable number."
287    )]
288    pub fn from_f64_seconds(value: f64) -> Self {
289        Duration((value * ((1u128 << 64) as f64)) as i128)
290    }
291
292    /// Create a duration from a given number of seconds, plus the given number
293    /// of nanoseconds.
294    ///
295    /// The nanoseconds are always applied in the positive direction, so for
296    /// example `from_seconds_nanos(-1, 500_000_000)` represents minus half a
297    /// second.
298    #[must_use]
299    pub const fn from_seconds_nanos(seconds: i64, nanos: u32) -> Self {
300        let converted_nanos = ((nanos as i128) << 64) / 1_000_000_000;
301        Duration(((seconds as i128) << 64) + converted_nanos)
302    }
303}
304
305impl core::fmt::Debug for Duration {
306    #[expect(
307        clippy::cast_precision_loss,
308        reason = "Loss of precision isn't important for debug printing."
309    )]
310    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
311        f.write_fmt(format_args!(
312            "Duration({}s)",
313            ((self.0 as f64) / ((1u128 << 64) as f64))
314        ))
315    }
316}
317
318impl core::fmt::Display for Duration {
319    #[expect(
320        clippy::cast_precision_loss,
321        reason = "Loss of precision isn't important for debug printing."
322    )]
323    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
324        f.write_fmt(format_args!(
325            "{}s",
326            ((self.0 as f64) / ((1u128 << 64) as f64))
327        ))
328    }
329}
330
331impl<A> Sub<Timestamp<A>> for Timestamp<A> {
332    type Output = Duration;
333
334    fn sub(self, rhs: Timestamp<A>) -> Self::Output {
335        Duration(self.0.wrapping_sub(rhs.0).cast_signed())
336    }
337}
338
339impl<A> Add<Duration> for Timestamp<A> {
340    type Output = Timestamp<A>;
341
342    fn add(self, rhs: Duration) -> Self::Output {
343        Timestamp(self.0.wrapping_add(rhs.0.cast_unsigned()), PhantomData)
344    }
345}
346
347impl<A> AddAssign<Duration> for Timestamp<A> {
348    fn add_assign(&mut self, rhs: Duration) {
349        self.0 = self.0.wrapping_add(rhs.0.cast_unsigned());
350    }
351}
352
353impl<A> Sub<Duration> for Timestamp<A> {
354    type Output = Timestamp<A>;
355
356    fn sub(self, rhs: Duration) -> Self::Output {
357        Timestamp(self.0.wrapping_sub(rhs.0.cast_unsigned()), PhantomData)
358    }
359}
360
361impl<A> SubAssign<Duration> for Timestamp<A> {
362    fn sub_assign(&mut self, rhs: Duration) {
363        self.0 = self.0.wrapping_sub(rhs.0.cast_unsigned());
364    }
365}
366
367impl Add<Duration> for Duration {
368    type Output = Duration;
369
370    fn add(self, rhs: Duration) -> Self::Output {
371        Duration(self.0.saturating_add(rhs.0))
372    }
373}
374
375impl AddAssign<Duration> for Duration {
376    fn add_assign(&mut self, rhs: Duration) {
377        self.0 = self.0.saturating_add(rhs.0);
378    }
379}
380
381impl Sub<Duration> for Duration {
382    type Output = Duration;
383
384    fn sub(self, rhs: Duration) -> Self::Output {
385        Duration(self.0.saturating_sub(rhs.0))
386    }
387}
388
389impl SubAssign<Duration> for Duration {
390    fn sub_assign(&mut self, rhs: Duration) {
391        self.0 = self.0.saturating_sub(rhs.0);
392    }
393}
394
395impl Neg for Duration {
396    type Output = Duration;
397
398    fn neg(self) -> Self::Output {
399        Self(self.0.saturating_neg())
400    }
401}
402
403macro_rules! duration_mul {
404    ($intty:ident) => {
405        impl Mul<Duration> for $intty {
406            type Output = Duration;
407
408            fn mul(self, rhs: Duration) -> Self::Output {
409                Duration(rhs.0.saturating_mul(i128::from(self)))
410            }
411        }
412
413        impl Mul<$intty> for Duration {
414            type Output = Duration;
415
416            fn mul(self, rhs: $intty) -> Self::Output {
417                Duration(self.0.saturating_mul(i128::from(rhs)))
418            }
419        }
420
421        impl MulAssign<$intty> for Duration {
422            fn mul_assign(&mut self, rhs: $intty) {
423                self.0 = self.0.saturating_mul(i128::from(rhs));
424            }
425        }
426    };
427}
428
429macro_rules! duration_div {
430    ($intty:ident) => {
431        impl Div<$intty> for Duration {
432            type Output = Duration;
433
434            fn div(self, rhs: $intty) -> Self::Output {
435                Duration(self.0.saturating_div(i128::from(rhs)))
436            }
437        }
438    };
439}
440
441duration_mul!(u8);
442duration_mul!(i8);
443duration_mul!(u16);
444duration_mul!(i16);
445duration_mul!(u32);
446duration_mul!(i32);
447duration_mul!(u64);
448duration_mul!(i64);
449
450duration_div!(u8);
451duration_div!(i8);
452duration_div!(u16);
453duration_div!(i16);
454duration_div!(u32);
455duration_div!(i32);
456duration_div!(u64);
457duration_div!(i64);
458
459#[cfg(all(test, feature = "std"))]
460#[expect(clippy::float_cmp, reason = "Test code")]
461mod tests {
462    use core::hash::Hasher;
463    use std::hash::DefaultHasher;
464
465    use crate::{TAI, UniversalTimestamp};
466
467    use super::{Duration, Timestamp, UTC};
468
469    #[test]
470    fn test_timestamp_creation() {
471        assert_eq!(
472            Timestamp::<UTC>::from_seconds_nanos_since_unix_epoch(100, 0).0,
473            100 << 64
474        );
475        assert_eq!(
476            Timestamp::<UTC>::from_seconds_nanos_since_unix_epoch(0, 250_000_000).0,
477            1 << 62
478        );
479    }
480
481    #[test]
482    fn test_timestamp_math() {
483        assert_eq!(
484            Timestamp::<UTC>::from_seconds_nanos_since_unix_epoch(100, 0)
485                - Timestamp::<UTC>::from_seconds_nanos_since_unix_epoch(50, 0),
486            Duration(50 << 64)
487        );
488        assert_eq!(
489            Timestamp::<UTC>::from_seconds_nanos_since_unix_epoch(100, 0)
490                - Timestamp::<UTC>::from_seconds_nanos_since_unix_epoch(150, 0),
491            Duration(-50 << 64)
492        );
493
494        assert_eq!(
495            Timestamp::<UTC>::from_seconds_nanos_since_unix_epoch(50, 0)
496                + Duration::from_seconds_nanos(50, 0),
497            Timestamp::<UTC>::from_seconds_nanos_since_unix_epoch(100, 0)
498        );
499        assert_eq!(
500            Timestamp::<UTC>::from_seconds_nanos_since_unix_epoch(150, 0)
501                - Duration::from_seconds_nanos(50, 0),
502            Timestamp::<UTC>::from_seconds_nanos_since_unix_epoch(100, 0)
503        );
504
505        let mut ts = Timestamp::<UTC>::from_seconds_nanos_since_unix_epoch(50, 0);
506        ts += Duration::from_seconds_nanos(50, 0);
507        assert_eq!(
508            ts,
509            Timestamp::<UTC>::from_seconds_nanos_since_unix_epoch(100, 0)
510        );
511
512        let mut ts = Timestamp::<UTC>::from_seconds_nanos_since_unix_epoch(150, 0);
513        ts -= Duration::from_seconds_nanos(50, 0);
514        assert_eq!(
515            ts,
516            Timestamp::<UTC>::from_seconds_nanos_since_unix_epoch(100, 0)
517        );
518    }
519
520    #[test]
521    fn test_timestamp_clone() {
522        let ts = Timestamp::<UTC>::from_seconds_nanos_since_unix_epoch(500, 0);
523        #[expect(
524            clippy::clone_on_copy,
525            reason = "Clone implementation of type is under test"
526        )]
527        let ts_clone = ts.clone();
528        assert_eq!(ts.0, ts_clone.0);
529    }
530
531    #[test]
532    fn test_timestamp_hash() {
533        fn hash<A: core::hash::Hash>(a: A) -> u64 {
534            let mut s = DefaultHasher::new();
535            a.hash(&mut s);
536            s.finish()
537        }
538
539        assert_eq!(
540            hash(Timestamp::<UTC>::from_seconds_nanos_since_unix_epoch(
541                100,
542                125_000_000
543            )),
544            hash(Timestamp::<UTC>::from_seconds_nanos_since_unix_epoch(
545                100,
546                125_000_000
547            ))
548        );
549        assert_ne!(
550            hash(Timestamp::<UTC>::from_seconds_nanos_since_unix_epoch(
551                100,
552                125_000_000
553            )),
554            hash(Timestamp::<UTC>::from_seconds_nanos_since_unix_epoch(
555                150,
556                250_000_000
557            ))
558        );
559    }
560
561    #[test]
562    fn test_timestamp_debug_formatting() {
563        assert_eq!(
564            std::format!(
565                "{:?}",
566                Timestamp::<UTC>::from_seconds_nanos_since_unix_epoch(5, 500_000_000)
567            ),
568            std::format!("Timestamp<{}>(5.5s)", core::any::type_name::<UTC>())
569        );
570    }
571
572    #[test]
573    fn test_duration_creation() {
574        assert_eq!(Duration::from_seconds_nanos(100, 0).0, 100 << 64);
575        assert_eq!(Duration::from_seconds_nanos(0, 250_000_000).0, 1 << 62);
576        assert_eq!(Duration::from_seconds_nanos(-1, 500_000_000).0, -1 << 63);
577    }
578
579    #[test]
580    fn test_duration_as_seconds() {
581        assert_eq!(Duration(1 << 64).as_seconds(), 1.0);
582        assert_eq!(Duration(10 << 64).as_seconds(), 10.0);
583        assert_eq!(Duration(5 << 62).as_seconds(), 1.25);
584    }
585
586    #[test]
587    fn test_duration_debug_formatting() {
588        assert_eq!(
589            std::format!("{:?}", Duration::from_seconds_nanos(-5, 500_000_000)),
590            "Duration(-4.5s)"
591        );
592    }
593
594    #[test]
595    #[expect(clippy::too_many_lines, reason = "Test code")]
596    fn test_duration_math() {
597        assert_eq!(
598            Duration::from_seconds_nanos(50, 0) + Duration::from_seconds_nanos(75, 0),
599            Duration::from_seconds_nanos(125, 0)
600        );
601        assert_eq!(
602            Duration::from_seconds_nanos(200, 0) - Duration::from_seconds_nanos(75, 0),
603            Duration::from_seconds_nanos(125, 0)
604        );
605
606        let mut duration = Duration::from_seconds_nanos(50, 0);
607        duration += Duration::from_seconds_nanos(75, 0);
608        assert_eq!(duration, Duration::from_seconds_nanos(125, 0));
609
610        let mut duration = Duration::from_seconds_nanos(200, 0);
611        duration -= Duration::from_seconds_nanos(75, 0);
612        assert_eq!(duration, Duration::from_seconds_nanos(125, 0));
613
614        assert_eq!(
615            Duration::from_seconds_nanos(0, 250_000_000) * 4u8,
616            Duration::from_seconds_nanos(1, 0)
617        );
618        assert_eq!(
619            6u8 * Duration::from_seconds_nanos(0, 250_000_000),
620            Duration::from_seconds_nanos(1, 500_000_000)
621        );
622
623        assert_eq!(
624            Duration::from_seconds_nanos(0, 250_000_000) * 4u16,
625            Duration::from_seconds_nanos(1, 0)
626        );
627        assert_eq!(
628            6u16 * Duration::from_seconds_nanos(0, 250_000_000),
629            Duration::from_seconds_nanos(1, 500_000_000)
630        );
631
632        assert_eq!(
633            Duration::from_seconds_nanos(0, 250_000_000) * 4u32,
634            Duration::from_seconds_nanos(1, 0)
635        );
636        assert_eq!(
637            6u32 * Duration::from_seconds_nanos(0, 250_000_000),
638            Duration::from_seconds_nanos(1, 500_000_000)
639        );
640
641        assert_eq!(
642            Duration::from_seconds_nanos(0, 250_000_000) * 4u64,
643            Duration::from_seconds_nanos(1, 0)
644        );
645        assert_eq!(
646            6u64 * Duration::from_seconds_nanos(0, 250_000_000),
647            Duration::from_seconds_nanos(1, 500_000_000)
648        );
649
650        assert_eq!(
651            Duration::from_seconds_nanos(0, 250_000_000) * 4i8,
652            Duration::from_seconds_nanos(1, 0)
653        );
654        assert_eq!(
655            6i8 * Duration::from_seconds_nanos(0, 250_000_000),
656            Duration::from_seconds_nanos(1, 500_000_000)
657        );
658
659        assert_eq!(
660            Duration::from_seconds_nanos(0, 250_000_000) * 4i16,
661            Duration::from_seconds_nanos(1, 0)
662        );
663        assert_eq!(
664            6i16 * Duration::from_seconds_nanos(0, 250_000_000),
665            Duration::from_seconds_nanos(1, 500_000_000)
666        );
667
668        assert_eq!(
669            Duration::from_seconds_nanos(0, 250_000_000) * 4i32,
670            Duration::from_seconds_nanos(1, 0)
671        );
672        assert_eq!(
673            6i32 * Duration::from_seconds_nanos(0, 250_000_000),
674            Duration::from_seconds_nanos(1, 500_000_000)
675        );
676
677        assert_eq!(
678            Duration::from_seconds_nanos(0, 250_000_000) * 4i64,
679            Duration::from_seconds_nanos(1, 0)
680        );
681        assert_eq!(
682            6i64 * Duration::from_seconds_nanos(0, 250_000_000),
683            Duration::from_seconds_nanos(1, 500_000_000)
684        );
685
686        let mut duration = Duration::from_seconds_nanos(0, 125_000_000);
687        duration *= 9u8;
688        assert_eq!(duration, Duration::from_seconds_nanos(1, 125_000_000));
689
690        let mut duration = Duration::from_seconds_nanos(0, 125_000_000);
691        duration *= 9u16;
692        assert_eq!(duration, Duration::from_seconds_nanos(1, 125_000_000));
693
694        let mut duration = Duration::from_seconds_nanos(0, 125_000_000);
695        duration *= 9u32;
696        assert_eq!(duration, Duration::from_seconds_nanos(1, 125_000_000));
697
698        let mut duration = Duration::from_seconds_nanos(0, 125_000_000);
699        duration *= 9u64;
700        assert_eq!(duration, Duration::from_seconds_nanos(1, 125_000_000));
701
702        let mut duration = Duration::from_seconds_nanos(0, 125_000_000);
703        duration *= 9i8;
704        assert_eq!(duration, Duration::from_seconds_nanos(1, 125_000_000));
705
706        let mut duration = Duration::from_seconds_nanos(0, 125_000_000);
707        duration *= 9i16;
708        assert_eq!(duration, Duration::from_seconds_nanos(1, 125_000_000));
709
710        let mut duration = Duration::from_seconds_nanos(0, 125_000_000);
711        duration *= 9i32;
712        assert_eq!(duration, Duration::from_seconds_nanos(1, 125_000_000));
713
714        let mut duration = Duration::from_seconds_nanos(0, 125_000_000);
715        duration *= 9i64;
716        assert_eq!(duration, Duration::from_seconds_nanos(1, 125_000_000));
717
718        assert_eq!(
719            Duration::from_seconds_nanos(1, 750_000_000) / 7u8,
720            Duration::from_seconds_nanos(0, 250_000_000)
721        );
722        assert_eq!(
723            Duration::from_seconds_nanos(1, 750_000_000) / 7i8,
724            Duration::from_seconds_nanos(0, 250_000_000)
725        );
726        assert_eq!(
727            Duration::from_seconds_nanos(1, 750_000_000) / 7u16,
728            Duration::from_seconds_nanos(0, 250_000_000)
729        );
730        assert_eq!(
731            Duration::from_seconds_nanos(1, 750_000_000) / 7i16,
732            Duration::from_seconds_nanos(0, 250_000_000)
733        );
734        assert_eq!(
735            Duration::from_seconds_nanos(1, 750_000_000) / 7u32,
736            Duration::from_seconds_nanos(0, 250_000_000)
737        );
738        assert_eq!(
739            Duration::from_seconds_nanos(1, 750_000_000) / 7i32,
740            Duration::from_seconds_nanos(0, 250_000_000)
741        );
742        assert_eq!(
743            Duration::from_seconds_nanos(1, 750_000_000) / 7u64,
744            Duration::from_seconds_nanos(0, 250_000_000)
745        );
746        assert_eq!(
747            Duration::from_seconds_nanos(1, 750_000_000) / 7i64,
748            Duration::from_seconds_nanos(0, 250_000_000)
749        );
750    }
751
752    #[test]
753    fn test_universal_timestamp() {
754        let universal_utc: UniversalTimestamp =
755            Timestamp::<UTC>::from_seconds_nanos_since_unix_epoch(63, 0).into();
756        assert_eq!(
757            universal_utc.as_utc(37),
758            Timestamp::from_seconds_nanos_since_unix_epoch(63, 0)
759        );
760        assert_eq!(
761            universal_utc.as_tai(37),
762            Timestamp::from_seconds_nanos_since_unix_epoch(100, 0)
763        );
764
765        let universal_tai: UniversalTimestamp =
766            Timestamp::<TAI>::from_seconds_nanos_since_unix_epoch(100, 0).into();
767        assert_eq!(
768            universal_tai.as_utc(37),
769            Timestamp::from_seconds_nanos_since_unix_epoch(63, 0)
770        );
771        assert_eq!(
772            universal_tai.as_tai(37),
773            Timestamp::from_seconds_nanos_since_unix_epoch(100, 0)
774        );
775    }
776}