Skip to main content

universal_time/
instant.rs

1use core::ops::{Add, Sub};
2use core::time::Duration;
3
4/// Monotonic clock reading.
5#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
6pub struct Instant {
7    ticks: Duration,
8}
9
10impl Instant {
11    /// Creates an `Instant` from monotonic ticks.
12    #[inline]
13    pub const fn from_ticks(ticks: Duration) -> Self {
14        Self { ticks }
15    }
16
17    /// Returns monotonic ticks for this instant.
18    #[inline]
19    pub const fn to_ticks(self) -> Duration {
20        self.ticks
21    }
22
23    /// Returns an instant corresponding to "now".
24    ///
25    /// # Platform behavior
26    ///
27    /// - With `std` feature on supported platforms: uses `std::time::Instant`
28    /// - Without `std` or on WASM unknown: uses the provider defined via `define_time_provider!` macro
29    ///
30    /// If no provider is defined on platforms without std, you'll get a **link error** at compile time.
31    #[inline]
32    pub fn now() -> Self {
33        #[cfg(all(
34            feature = "std",
35            not(all(target_family = "wasm", target_os = "unknown"))
36        ))]
37        {
38            Self::from_ticks(std_now_ticks())
39        }
40
41        #[cfg(any(
42            not(feature = "std"),
43            all(feature = "std", target_family = "wasm", target_os = "unknown")
44        ))]
45        {
46            crate::global::get_time_provider().instant()
47        }
48    }
49
50    /// Returns the amount of time elapsed since this instant.
51    #[inline]
52    pub fn elapsed(&self) -> Duration {
53        Self::now().duration_since(*self)
54    }
55
56    /// Returns the duration since another instant, saturating at zero.
57    #[inline]
58    pub fn duration_since(&self, earlier: Instant) -> Duration {
59        self.ticks.saturating_sub(earlier.ticks)
60    }
61
62    /// Returns `Some(duration)` if `self` is not earlier than `other`.
63    #[inline]
64    pub fn checked_duration_since(&self, earlier: Instant) -> Option<Duration> {
65        self.ticks.checked_sub(earlier.ticks)
66    }
67
68    /// Returns `Some(instant)` if adding the duration does not overflow.
69    #[inline]
70    pub fn checked_add(self, duration: Duration) -> Option<Self> {
71        self.ticks.checked_add(duration).map(Self::from_ticks)
72    }
73
74    /// Returns `Some(instant)` if subtracting the duration does not underflow.
75    #[inline]
76    pub fn checked_sub(self, duration: Duration) -> Option<Self> {
77        self.ticks.checked_sub(duration).map(Self::from_ticks)
78    }
79}
80
81#[cfg(all(
82    feature = "std",
83    not(all(target_family = "wasm", target_os = "unknown"))
84))]
85fn std_now_ticks() -> Duration {
86    use std::sync::OnceLock;
87
88    static START: OnceLock<std::time::Instant> = OnceLock::new();
89
90    START.get_or_init(std::time::Instant::now).elapsed()
91}
92
93impl Add<Duration> for Instant {
94    type Output = Instant;
95
96    fn add(self, other: Duration) -> Instant {
97        self.checked_add(other)
98            .expect("overflow while adding Duration to Instant")
99    }
100}
101
102impl Sub<Duration> for Instant {
103    type Output = Instant;
104
105    fn sub(self, other: Duration) -> Instant {
106        self.checked_sub(other)
107            .expect("underflow while subtracting Duration from Instant")
108    }
109}
110
111impl Sub<Instant> for Instant {
112    type Output = Duration;
113
114    fn sub(self, other: Instant) -> Duration {
115        self.duration_since(other)
116    }
117}
118
119#[cfg(test)]
120mod tests {
121    use super::*;
122
123    #[test]
124    fn roundtrip_ticks() {
125        let ticks = Duration::from_millis(42);
126        let instant = Instant::from_ticks(ticks);
127        assert_eq!(instant.to_ticks(), ticks);
128    }
129
130    #[test]
131    fn duration_since_saturates_at_zero() {
132        let earlier = Instant::from_ticks(Duration::from_secs(10));
133        let later = Instant::from_ticks(Duration::from_secs(3));
134        assert_eq!(later.duration_since(earlier), Duration::ZERO);
135    }
136
137    #[test]
138    fn checked_duration_since_some() {
139        let earlier = Instant::from_ticks(Duration::from_secs(3));
140        let later = Instant::from_ticks(Duration::from_secs(10));
141        assert_eq!(
142            later.checked_duration_since(earlier),
143            Some(Duration::from_secs(7))
144        );
145    }
146
147    #[test]
148    fn checked_duration_since_none() {
149        let earlier = Instant::from_ticks(Duration::from_secs(10));
150        let later = Instant::from_ticks(Duration::from_secs(3));
151        assert_eq!(later.checked_duration_since(earlier), None);
152    }
153
154    #[test]
155    fn checked_add_and_sub_roundtrip() {
156        let start = Instant::from_ticks(Duration::from_secs(5));
157        let delta = Duration::from_secs(2);
158        let end = start.checked_add(delta).expect("must not overflow");
159        assert_eq!(end.to_ticks(), Duration::from_secs(7));
160        assert_eq!(end.checked_sub(delta), Some(start));
161    }
162
163    #[test]
164    fn checked_add_overflow_returns_none() {
165        let start = Instant::from_ticks(Duration::MAX);
166        assert_eq!(start.checked_add(Duration::from_nanos(1)), None);
167    }
168
169    #[test]
170    fn checked_sub_underflow_returns_none() {
171        let start = Instant::from_ticks(Duration::ZERO);
172        assert_eq!(start.checked_sub(Duration::from_nanos(1)), None);
173    }
174
175    #[test]
176    fn add_operator_works() {
177        let start = Instant::from_ticks(Duration::from_secs(5));
178        let end = start + Duration::from_secs(2);
179        assert_eq!(end.to_ticks(), Duration::from_secs(7));
180    }
181
182    #[test]
183    #[should_panic(expected = "overflow while adding Duration to Instant")]
184    fn add_operator_panics_on_overflow() {
185        let _ = Instant::from_ticks(Duration::MAX) + Duration::from_nanos(1);
186    }
187
188    #[test]
189    fn sub_operator_works() {
190        let end = Instant::from_ticks(Duration::from_secs(7));
191        let start = end - Duration::from_secs(2);
192        assert_eq!(start.to_ticks(), Duration::from_secs(5));
193    }
194
195    #[test]
196    #[should_panic(expected = "underflow while subtracting Duration from Instant")]
197    fn sub_operator_panics_on_underflow() {
198        let _ = Instant::from_ticks(Duration::ZERO) - Duration::from_nanos(1);
199    }
200
201    #[test]
202    fn sub_instant_operator_is_saturating() {
203        let a = Instant::from_ticks(Duration::from_secs(9));
204        let b = Instant::from_ticks(Duration::from_secs(4));
205        assert_eq!(a - b, Duration::from_secs(5));
206        assert_eq!(b - a, Duration::ZERO);
207    }
208
209    #[test]
210    #[cfg(all(
211        feature = "std",
212        not(all(target_family = "wasm", target_os = "unknown"))
213    ))]
214    fn now_is_monotonic() {
215        let first = Instant::now();
216        let second = Instant::now();
217        assert!(second >= first);
218    }
219
220    #[test]
221    #[cfg(all(
222        feature = "std",
223        not(all(target_family = "wasm", target_os = "unknown"))
224    ))]
225    fn elapsed_is_non_negative() {
226        let start = Instant::now();
227        assert!(start.elapsed() >= Duration::ZERO);
228    }
229}