1use 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
16pub 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
67pub struct UTC;
69pub struct TAI;
71
72#[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#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
117pub enum UniversalTimestamp {
118 Utc(Timestamp<UTC>),
120 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 #[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 #[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 pub const UNIX_EPOCH: Timestamp<A> = Timestamp(0, PhantomData);
169
170 #[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 pub const ZERO: Duration = Duration(0);
218
219 #[must_use]
221 pub fn abs(self) -> Duration {
222 Self(self.0.saturating_abs())
223 }
224
225 #[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 #[must_use]
240 pub fn as_raw_steps(self) -> i128 {
241 self.0
242 }
243
244 #[must_use]
246 pub fn as_nanos(self) -> i128 {
247 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 #[must_use]
257 pub fn from_raw_steps(steps: i128) -> Self {
258 Self(steps)
259 }
260
261 #[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 #[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 #[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}