Skip to main content

t2t_core/interop/
system_time.rs

1//! A [`Timestamp`] as std's `SystemTime`, and back, on 64-bit Linux and macOS.
2
3use core::time::Duration;
4use std::time::{SystemTime, UNIX_EPOCH};
5
6use crate::errors::narrow;
7use crate::{OutOfRangeError, Timestamp};
8
9/// A `SystemTime` here counts 64-bit seconds, which reach past both ends of a [`Timestamp`].
10impl From<Timestamp> for SystemTime {
11    #[inline]
12    fn from(instant: Timestamp) -> Self {
13        let span = Duration::from_nanos(instant.0.unsigned_abs());
14        let system_time =
15            if instant.0 < 0 { UNIX_EPOCH.checked_sub(span) } else { UNIX_EPOCH.checked_add(span) };
16        system_time.unwrap_or(UNIX_EPOCH)
17    }
18}
19
20/// Refuses a `SystemTime` outside 1677-09-21 to 2262-04-11.
21impl TryFrom<SystemTime> for Timestamp {
22    type Error = OutOfRangeError;
23
24    #[inline]
25    fn try_from(instant: SystemTime) -> Result<Self, Self::Error> {
26        let nanos = match instant.duration_since(UNIX_EPOCH) {
27            Ok(span) => narrow(span.as_nanos()),
28            Err(error) => narrow(0_i128.saturating_sub_unsigned(error.duration().as_nanos())),
29        };
30        nanos.map(Self)
31    }
32}
33
34#[cfg(test)]
35mod tests {
36    use core::time::Duration;
37    use std::time::{SystemTime, UNIX_EPOCH};
38
39    use rstest::rstest;
40
41    use crate::{OutOfRangeError, Timestamp};
42
43    #[rstest]
44    #[case::the_epoch(0)]
45    #[case::a_nanosecond_after(1)]
46    #[case::a_nanosecond_before(-1)]
47    #[case::the_earliest(i64::MIN)]
48    #[case::the_latest(i64::MAX)]
49    fn an_instant_crosses_both_ways(#[case] nanos: i64) {
50        let instant = Timestamp::from_nanos(nanos);
51        assert_eq!(Timestamp::try_from(SystemTime::from(instant)), Ok(instant), "and back");
52    }
53
54    #[test]
55    fn a_system_time_outside_the_range_is_refused() {
56        let span = Duration::from_secs(1 << 40);
57        let far_future = UNIX_EPOCH.checked_add(span).expect("a moment 35,000 years on");
58        let distant_past = UNIX_EPOCH.checked_sub(span).expect("a moment 35,000 years back");
59        assert_eq!(Timestamp::try_from(far_future), Err(OutOfRangeError), "after 2262");
60        assert_eq!(Timestamp::try_from(distant_past), Err(OutOfRangeError), "before 1677");
61    }
62}