universal_time/
instant.rs1use core::ops::{Add, Sub};
2use core::time::Duration;
3
4#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
6pub struct Instant {
7 ticks: Duration,
8}
9
10impl Instant {
11 #[inline]
13 pub const fn from_ticks(ticks: Duration) -> Self {
14 Self { ticks }
15 }
16
17 #[inline]
19 pub const fn to_ticks(self) -> Duration {
20 self.ticks
21 }
22
23 #[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 #[inline]
52 pub fn elapsed(&self) -> Duration {
53 Self::now().duration_since(*self)
54 }
55
56 #[inline]
58 pub fn duration_since(&self, earlier: Instant) -> Duration {
59 self.ticks.saturating_sub(earlier.ticks)
60 }
61
62 #[inline]
64 pub fn checked_duration_since(&self, earlier: Instant) -> Option<Duration> {
65 self.ticks.checked_sub(earlier.ticks)
66 }
67
68 #[inline]
70 pub fn checked_add(self, duration: Duration) -> Option<Self> {
71 self.ticks.checked_add(duration).map(Self::from_ticks)
72 }
73
74 #[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}