Skip to main content

elfo_utils/time/
system.rs

1//! Provides the [`SystemTime`] type.
2//!
3//! The main purpose is to provide a way to mock system time in tests.
4
5use std::time::{Duration, SystemTime as StdSystemTime};
6
7/// A measurement of a system clock.
8#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
9pub struct SystemTime(u64 /* unix time nanos */); // TODO: make it `NonZeroU64`?
10
11impl SystemTime {
12    /// Represents "1970-01-01 00:00:00 UTC".
13    pub const UNIX_EPOCH: Self = Self(0);
14
15    /// Returns the current system time.
16    #[inline]
17    pub fn now() -> Self {
18        #[cfg(any(test, feature = "test-util"))]
19        if let Some(now_ns) = mock::NOW_NS.with(|t| t.get()) {
20            return Self(now_ns);
21        }
22
23        StdSystemTime::now().into()
24    }
25
26    /// Creates an instance based on nanoseconds since the unix epoch.
27    #[inline]
28    pub fn from_unix_time_nanos(nanos: u64) -> Self {
29        Self(nanos)
30    }
31
32    /// Returns the number of seconds since the unix epoch.
33    #[inline]
34    pub fn to_unix_time_secs(&self) -> u64 {
35        self.to_unix_time_nanos() / 1_000_000_000
36    }
37
38    /// Returns the number of nanoseconds since the unix epoch.
39    #[inline]
40    pub fn to_unix_time_nanos(&self) -> u64 {
41        self.0
42    }
43}
44
45impl From<StdSystemTime> for SystemTime {
46    fn from(sys_time: StdSystemTime) -> Self {
47        let unix_time_ns = sys_time
48            .duration_since(StdSystemTime::UNIX_EPOCH)
49            .unwrap_or_default()
50            .as_nanos() as u64;
51
52        Self(unix_time_ns)
53    }
54}
55
56impl From<SystemTime> for StdSystemTime {
57    fn from(sys_time: SystemTime) -> Self {
58        StdSystemTime::UNIX_EPOCH + Duration::from_nanos(sys_time.0)
59    }
60}
61
62#[cfg(any(test, feature = "test-util"))]
63pub use mock::{with_system_time_mock, SystemTimeMock};
64
65#[cfg(any(test, feature = "test-util"))]
66mod mock {
67    use std::cell::Cell;
68
69    use super::*;
70
71    thread_local! {
72        pub(super) static NOW_NS: Cell<Option<u64>> = const { Cell::new(None) };
73    }
74
75    /// Mocks `SystemTime`, see [`SystemTimeMock`].
76    pub fn with_system_time_mock(f: impl FnOnce(SystemTimeMock)) {
77        NOW_NS.with(|t| t.set(Some(0)));
78        f(SystemTimeMock);
79        NOW_NS.with(|t| t.set(None));
80    }
81
82    /// Controllable time source for use in tests.
83    #[non_exhaustive]
84    pub struct SystemTimeMock;
85
86    impl SystemTimeMock {
87        /// Increase the time by the given duration.
88        pub fn advance(&self, duration: Duration) {
89            NOW_NS.with(|t| {
90                let mut now_ns = t.get().expect("use of moved system time mock");
91                now_ns += duration.as_nanos() as u64;
92                t.set(Some(now_ns));
93            })
94        }
95    }
96}