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#[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 #[inline]
21 #[must_use]
22 pub fn now() -> Self {
23 Self(get_monotonic_time())
24 }
25
26 #[must_use]
29 pub fn elapsed(self) -> Duration {
30 sub_ts_dur(Self::now().0, self.0)
31 }
32
33 #[must_use]
35 #[inline]
36 pub fn as_instant(self) -> Instant {
37 Instant(self.0)
38 }
39}
40
41#[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 #[inline]
56 #[must_use]
57 pub fn elapsed(self) -> Option<Duration> {
58 Self::now().duration_since(self)
59 }
60
61 #[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#[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 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 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#[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 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 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 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}