tiny_std/
time.rs

1use core::time::Duration;
2
3use rusl::platform::TimeSpec;
4
5#[cfg(test)]
6mod test;
7
8pub const UNIX_TIME: SystemTime = SystemTime(TimeSpec::new_zeroed());
9
10/// Some monotonic, ever increasing, instant in time. Cannot be manipulated and is only good
11/// for comparing elapsed time.
12#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
13pub struct MonotonicInstant(pub(crate) TimeSpec);
14
15const NANOS_A_SECOND: i64 = 1_000_000_000;
16
17impl MonotonicInstant {
18    pub const ZERO: MonotonicInstant = MonotonicInstant(TimeSpec::new_zeroed());
19    /// Create a new instant
20    #[inline]
21    #[must_use]
22    pub fn now() -> Self {
23        Self(get_monotonic_time())
24    }
25
26    /// Get the time that has passed since this instant
27    /// Will always yield a valid `Duration` and never panic
28    #[must_use]
29    pub fn elapsed(self) -> Duration {
30        sub_ts_dur(Self::now().0, self.0)
31    }
32
33    /// Converts this `MonotonicInstant` into a regular `Instant`
34    #[must_use]
35    #[inline]
36    pub fn as_instant(self) -> Instant {
37        Instant(self.0)
38    }
39}
40
41/// Some instant in time, ever increasing but able to be manipulated.
42/// The manipulations carries a risk of over/underflow,
43#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
44pub struct Instant(pub(crate) TimeSpec);
45
46impl Instant {
47    #[inline]
48    #[must_use]
49    pub fn now() -> Self {
50        Self(get_monotonic_time())
51    }
52
53    /// Get the time that has passed since this instant.
54    /// If this `Instant` is by some manipulation after `now`, returns `None`
55    #[inline]
56    #[must_use]
57    pub fn elapsed(self) -> Option<Duration> {
58        Self::now().duration_since(self)
59    }
60
61    /// Get the duration since some other `Instant`.
62    /// If this `Instant` is before `other` in monotonic time, returns `None`
63    #[must_use]
64    pub fn duration_since(self, other: Self) -> Option<Duration> {
65        sub_ts_checked_dur(self.0, other.0)
66    }
67}
68
69impl core::ops::Add<Duration> for Instant {
70    type Output = Option<Self>;
71
72    fn add(self, rhs: Duration) -> Self::Output {
73        checked_add_dur(self.0, rhs).map(Self)
74    }
75}
76
77impl core::ops::Sub<Duration> for Instant {
78    type Output = Option<Self>;
79
80    fn sub(self, rhs: Duration) -> Self::Output {
81        checked_sub_dur(self.0, rhs).map(Self)
82    }
83}
84
85impl core::ops::Sub for Instant {
86    type Output = Option<Duration>;
87
88    fn sub(self, rhs: Self) -> Self::Output {
89        sub_ts_checked_dur(self.0, rhs.0)
90    }
91}
92
93/// Some instant in time since the unix epoch as seen by the system
94/// The passage of time may not be linear
95#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
96pub struct SystemTime(TimeSpec);
97
98impl SystemTime {
99    #[inline]
100    #[must_use]
101    pub fn now() -> Self {
102        Self(get_real_time())
103    }
104
105    #[inline]
106    #[must_use]
107    pub fn elapsed(self) -> Option<Duration> {
108        Self::now().duration_since(self)
109    }
110
111    #[must_use]
112    pub fn duration_since(self, other: Self) -> Option<Duration> {
113        sub_ts_checked_dur(self.0, other.0)
114    }
115
116    #[must_use]
117    pub fn duration_since_unix_time(self) -> Duration {
118        sub_ts_dur(self.0, UNIX_TIME.0)
119    }
120}
121
122impl core::ops::Add<Duration> for SystemTime {
123    type Output = Option<Self>;
124
125    fn add(self, rhs: Duration) -> Self::Output {
126        checked_add_dur(self.0, rhs).map(Self)
127    }
128}
129
130impl core::ops::Sub<Duration> for SystemTime {
131    type Output = Option<Self>;
132
133    fn sub(self, rhs: Duration) -> Self::Output {
134        checked_sub_dur(self.0, rhs).map(Self)
135    }
136}
137
138impl core::ops::Sub for SystemTime {
139    type Output = Option<Duration>;
140
141    fn sub(self, rhs: Self) -> Self::Output {
142        sub_ts_checked_dur(self.0, rhs.0)
143    }
144}
145
146impl From<TimeSpec> for SystemTime {
147    #[inline]
148    fn from(value: TimeSpec) -> Self {
149        Self(value)
150    }
151}
152
153#[inline]
154fn checked_add_dur(timespec: TimeSpec, duration: Duration) -> Option<TimeSpec> {
155    // tv_nsec are < `NANOS_A_SECOND`, this cannot overflow
156    let mut total_nanos = timespec
157        .nanoseconds()
158        .checked_add(duration.subsec_nanos().into())?;
159    let mut seconds = duration.as_secs();
160    if total_nanos >= NANOS_A_SECOND {
161        total_nanos -= NANOS_A_SECOND;
162        seconds = seconds.checked_add(1)?;
163    };
164    Some(TimeSpec::new(
165        timespec.seconds().checked_add(seconds.try_into().ok()?)?,
166        total_nanos,
167    ))
168}
169
170#[inline]
171fn checked_sub_dur(timespec: TimeSpec, duration: Duration) -> Option<TimeSpec> {
172    let mut total_nanos = timespec
173        .nanoseconds()
174        .checked_sub(duration.subsec_nanos().into())?;
175    let mut seconds = duration.as_secs();
176    if total_nanos < 0 {
177        // tv_nsec is always < `NANOS_A_SECOND`, so this won't get wonky
178        total_nanos += NANOS_A_SECOND;
179        seconds = seconds.checked_add(1)?;
180    }
181    let tv_sec = timespec.seconds().checked_sub(seconds.try_into().ok()?)?;
182
183    Some(TimeSpec::new(tv_sec.ge(&0).then_some(tv_sec)?, total_nanos))
184}
185
186/// Can panic if left is not bigger than right
187#[inline]
188#[expect(clippy::cast_sign_loss, clippy::cast_possible_truncation)]
189fn sub_ts_dur(lhs: TimeSpec, rhs: TimeSpec) -> Duration {
190    let mut total_nanos = lhs.nanoseconds() - rhs.nanoseconds();
191    let sub_sec = if total_nanos < 0 {
192        // tv_nsec are < `NANOS_A_SECOND`, so this won't get wonky
193        total_nanos += NANOS_A_SECOND;
194        1
195    } else {
196        0
197    };
198    let secs = (lhs.seconds() - rhs.seconds() - sub_sec) as u64;
199    let nanos = total_nanos as u32;
200    Duration::new(secs, nanos)
201}
202
203#[inline]
204fn sub_ts_checked_dur(lhs: TimeSpec, rhs: TimeSpec) -> Option<Duration> {
205    let mut total_nanos = lhs.nanoseconds().checked_sub(rhs.nanoseconds())?;
206    let sub_sec = if total_nanos < 0 {
207        // tv_nsec are < `NANOS_A_SECOND`, so this won't get wonky
208        total_nanos += NANOS_A_SECOND;
209        1
210    } else {
211        0
212    };
213    let secs = u64::try_from(
214        lhs.seconds()
215            .checked_sub(rhs.seconds())?
216            .checked_sub(sub_sec)?,
217    )
218    .ok()?;
219    let nanos = u32::try_from(total_nanos).ok()?;
220    Some(Duration::new(secs, nanos))
221}
222
223#[cfg(feature = "vdso")]
224fn get_monotonic_time() -> TimeSpec {
225    if let Some(vdso_get_time) = unsafe { crate::elf::vdso::VDSO_CLOCK_GET_TIME } {
226        let mut ts = core::mem::MaybeUninit::<TimeSpec>::zeroed();
227        vdso_get_time(
228            rusl::platform::ClockId::CLOCK_MONOTONIC.into_i32(),
229            ts.as_mut_ptr(),
230        );
231        unsafe {
232            return ts.assume_init();
233        }
234    }
235    rusl::time::clock_get_monotonic_time()
236}
237
238#[cfg(feature = "vdso")]
239fn get_real_time() -> TimeSpec {
240    if let Some(vdso_get_time) = unsafe { crate::elf::vdso::VDSO_CLOCK_GET_TIME } {
241        let mut ts = core::mem::MaybeUninit::<TimeSpec>::zeroed();
242        vdso_get_time(
243            rusl::platform::ClockId::CLOCK_REALTIME.into_i32(),
244            ts.as_mut_ptr(),
245        );
246        unsafe {
247            return ts.assume_init();
248        }
249    }
250    rusl::time::clock_get_real_time()
251}
252
253impl AsRef<TimeSpec> for Instant {
254    #[inline]
255    fn as_ref(&self) -> &TimeSpec {
256        &self.0
257    }
258}
259
260#[inline]
261#[cfg(not(feature = "vdso"))]
262fn get_monotonic_time() -> TimeSpec {
263    rusl::time::clock_get_monotonic_time()
264}
265
266#[inline]
267#[cfg(not(feature = "vdso"))]
268fn get_real_time() -> TimeSpec {
269    rusl::time::clock_get_real_time()
270}
271
272#[cfg(test)]
273mod tests {
274    use super::*;
275    use core::ops::Add;
276    #[test]
277    fn instant_now() {
278        // We're using monotonic time, Linux implementation is time since boot.
279        let instant = Instant::now();
280        let since_start = instant
281            .duration_since(Instant(TimeSpec::new_zeroed()))
282            .unwrap();
283        assert!(since_start > Duration::from_secs(1));
284        assert!(instant.elapsed().unwrap().as_nanos() > 0);
285    }
286
287    #[test]
288    fn system_time_now() {
289        let system_time = SystemTime::now();
290        let since_start = system_time.duration_since_unix_time();
291        assert!(
292            since_start.as_secs() > 1_694_096_772,
293            "Test has failed or this machine's clock is off"
294        );
295        assert!(system_time.elapsed().unwrap().as_nanos() > 0);
296    }
297
298    #[test]
299    fn monotonic_to_instant() {
300        let now = MonotonicInstant::now();
301        let instant = now.as_instant();
302        assert_eq!(now.0, instant.0);
303        let dur = instant.duration_since(instant).unwrap();
304        assert_eq!(0, dur.as_secs());
305    }
306
307    #[test]
308    fn instant_arithmetic() {
309        let now = Instant::now().add(Duration::from_millis(100)).unwrap();
310        let before = (now - Duration::from_millis(10)).unwrap();
311        let diff = (now - before).unwrap();
312        assert_eq!(Duration::from_millis(10), diff);
313        let back_to_now = (before + diff).unwrap();
314        assert_eq!(now, back_to_now);
315    }
316
317    #[test]
318    fn system_time_arithmetic() {
319        let now = SystemTime::now();
320        let before = (now - Duration::from_millis(10)).unwrap();
321        let diff = (now - before).unwrap();
322        assert_eq!(Duration::from_millis(10), diff);
323        let back_to_now = (before + diff).unwrap();
324        assert_eq!(now, back_to_now);
325    }
326
327    #[test]
328    fn nano_overflow_adds() {
329        let overflow_nanos = TimeSpec::new(0, 999_999_999);
330        let add_by = Duration::from_nanos(2);
331        let dur = checked_add_dur(overflow_nanos, add_by).unwrap();
332        assert_eq!(TimeSpec::new(1, 1), dur);
333    }
334
335    #[test]
336    fn nano_underflow_subs() {
337        let underflow_nanos = TimeSpec::new(1, 0);
338        let sub_by = Duration::from_nanos(1);
339        let dur = checked_sub_dur(underflow_nanos, sub_by).unwrap();
340        assert_eq!(TimeSpec::new(0, 999_999_999), dur);
341    }
342
343    #[test]
344    fn ts_underflow_sub() {
345        let left = TimeSpec::new(1, 0);
346        let right = TimeSpec::new(0, 999_999_999);
347        let res = sub_ts_dur(left, right);
348        assert_eq!(Duration::from_nanos(1), res);
349    }
350
351    #[test]
352    fn ts_underflow_checked() {
353        let left = TimeSpec::new(1, 0);
354        let right = TimeSpec::new(0, 999_999_999);
355        let res = sub_ts_checked_dur(left, right).unwrap();
356        assert_eq!(Duration::from_nanos(1), res);
357        assert!(sub_ts_checked_dur(right, left).is_none());
358    }
359}