sans_io_time/lib.rs
1// Copyright (C) 2025 Matthew Waters <matthew@centricular.com>
2//
3// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
4// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
5// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
6// option. This file may not be copied, modified, or distributed
7// except according to those terms.
8
9#![no_std]
10#![deny(missing_debug_implementations)]
11#![deny(missing_docs)]
12
13//! # sans-io-time
14//!
15//! Represent time as an abstract absolute value based on an arbitrary start point in a
16//! (user-provided) timeline.
17//!
18//! ## Why?
19//!
20//! 1. `no_std` environments may require an implementation of `Instant` and may require
21//! leaving the implementation details to the implementor.
22//! 2. Network protocols may require that time continues during suspend of the OS
23//! which is not something that is guaranteed by `std::time::Instant`.
24//! 3. Other uses of `std::time::Instant` should not count time during suspend e.g.
25//! CPU process time, or process uptime.
26//!
27//! The `Instant` type provided by this crate satisfies all of these constraints by
28//! only specifying the carriage of data. It does not specify how the current time
29//! is acquired or whether time continues during suspend. Both of these questions
30//! are required to be answered (or left unspecified) by the caller by either using
31//! `std::time::Instant` or `std::time::SystemTime` (with the "std" feature), or
32//! converting from another source of time like
33//! [boot-time](https://crates.io/crates/boot-time).
34//!
35//! In a sans-IO library, the decision on the exact clock source can be decided by the
36//! user of the library rather than specifying a particular `Instant` implementation.
37//!
38//! ## Features
39//! - "std" (enabled by default) enables conversion from `std::time::Instant` and
40//! `std::time::SystemTime` into an [`Instant`].
41
42#[cfg(feature = "std")]
43extern crate std;
44
45use core::time::Duration;
46
47/// An absolute point in time.
48///
49/// An [`Instant`] is a wrapper around a signed integer that holds the number of nanoseconds since
50/// an arbitrary point in time, e.g. system startup.
51///
52/// - A value of `0` is arbitrary.
53/// - Values < 0 indicate a time before the start point.
54#[derive(Debug, Copy, Clone, Hash, PartialOrd, Ord, PartialEq, Eq)]
55pub struct Instant {
56 nanos: i64,
57}
58
59impl Instant {
60 /// The zero [`Instant`].
61 ///
62 /// An arbitrary point on the absolute timescale.
63 pub const ZERO: Instant = Instant { nanos: 0 };
64
65 /// Construct an [`Instant`] from a number of nanoseconds.
66 ///
67 /// # Examples
68 ///
69 /// ```
70 /// # use sans_io_time::Instant;
71 /// let instant = Instant::from_nanos(1_234_567_890);
72 /// assert_eq!(instant.as_nanos(), 1_234_567_890);
73 /// ```
74 pub fn from_nanos(nanos: i64) -> Self {
75 Self { nanos }
76 }
77
78 /// The number of nanoseconds that have passed since the `ZERO` point.
79 pub const fn as_nanos(&self) -> i64 {
80 self.nanos
81 }
82
83 /// The number of whole seconds since the `ZERO` point.
84 pub const fn secs(&self) -> i64 {
85 self.nanos / 1_000_000_000
86 }
87
88 /// The number of fractional nanoseconds since the `ZERO` point.
89 pub const fn subsec_nanos(&self) -> i64 {
90 self.nanos % 1_000_000_000
91 }
92
93 /// The amount of time elapsed since an earlier [`Instant`], or `0`.
94 ///
95 /// # Examples
96 ///
97 /// ```
98 /// # use sans_io_time::Instant;
99 /// # use core::time::Duration;
100 /// let earlier = Instant::from_nanos(1_234_567_890);
101 /// let later = Instant::from_nanos(2_234_567_890);
102 /// assert_eq!(later.duration_since(earlier), Duration::from_nanos(1_000_000_000));
103 /// assert_eq!(earlier.duration_since(later), Duration::ZERO);
104 /// assert_eq!(earlier.duration_since(earlier), Duration::ZERO);
105 /// ```
106 pub fn duration_since(&self, earlier: Instant) -> Duration {
107 self.saturating_duration_since(earlier)
108 }
109
110 /// The amount of time elapsed since an earlier [`Instant`], or `None`.
111 ///
112 /// # Examples
113 ///
114 /// ```
115 /// # use sans_io_time::Instant;
116 /// # use core::time::Duration;
117 /// let earlier = Instant::from_nanos(1_234_567_890);
118 /// let later = Instant::from_nanos(2_234_567_890);
119 /// assert_eq!(later.checked_duration_since(earlier), Some(Duration::from_nanos(1_000_000_000)));
120 /// assert_eq!(earlier.checked_duration_since(later), None);
121 /// assert_eq!(earlier.checked_duration_since(earlier), Some(Duration::ZERO));
122 /// ```
123 pub fn checked_duration_since(&self, earlier: Instant) -> Option<Duration> {
124 self.nanos.checked_sub(earlier.nanos).and_then(|nanos| {
125 if nanos < 0 {
126 None
127 } else {
128 Some(Duration::from_nanos(nanos as u64))
129 }
130 })
131 }
132
133 /// The amount of time elapsed since an earlier [`Instant`], or `0`.
134 pub fn saturating_duration_since(&self, earlier: Instant) -> Duration {
135 self.checked_duration_since(earlier)
136 .unwrap_or(Duration::ZERO)
137 }
138
139 /// Returns the result of `self + duration` if the result can be represented by the underlying
140 /// data structure, `None` otherwise.
141 ///
142 /// # Examples
143 ///
144 /// ```
145 /// # use sans_io_time::Instant;
146 /// # use core::time::Duration;
147 /// let instant = Instant::from_nanos(1_234_567_890);
148 /// assert_eq!(instant.checked_add(Duration::from_secs(1)), Some(Instant::from_nanos(2_234_567_890)));
149 /// assert_eq!(instant.checked_add(Duration::ZERO), Some(instant));
150 /// ```
151 pub fn checked_add(&self, duration: Duration) -> Option<Instant> {
152 let dur_nanos: i64 = duration.as_nanos().try_into().ok()?;
153 let nanos = self.nanos.checked_add(dur_nanos)?;
154 Some(Self { nanos })
155 }
156
157 /// Returns the result of `self - duration` if the result can be represented by the underlying
158 /// data structure, `None` otherwise.
159 ///
160 /// # Examples
161 ///
162 /// ```
163 /// # use sans_io_time::Instant;
164 /// # use core::time::Duration;
165 /// let instant = Instant::from_nanos(1_234_567_890);
166 /// assert_eq!(instant.checked_sub(Duration::from_secs(1)), Some(Instant::from_nanos(234_567_890)));
167 /// assert_eq!(instant.checked_sub(Duration::from_secs(2)), Some(Instant::from_nanos(-765_432_110)));
168 /// assert_eq!(instant.checked_sub(Duration::ZERO), Some(instant));
169 /// ```
170 pub fn checked_sub(&self, duration: Duration) -> Option<Instant> {
171 let dur_nanos: i64 = duration.as_nanos().try_into().ok()?;
172 let nanos = self.nanos.checked_sub(dur_nanos)?;
173 Some(Self { nanos })
174 }
175
176 /// Construct an [`Instant`] from a `std::time::Instant` based its the elapsed time.
177 ///
178 /// Can be used if you have a base `std::time::Instant` where you can create [`Instant`]s as
179 /// needed.
180 ///
181 /// # Example
182 ///
183 /// ```
184 /// # use sans_io_time::Instant;
185 /// # use core::time::Duration;
186 /// let std_instant = std::time::Instant::now();
187 /// std::thread::sleep(Duration::from_secs(1));
188 /// assert!(Instant::from_std(std_instant) >= Instant::from_nanos(1_000_000_000));
189 /// ```
190 #[cfg(feature = "std")]
191 pub fn from_std(instant: ::std::time::Instant) -> Self {
192 let elapsed = instant.elapsed();
193 Self {
194 nanos: elapsed
195 .as_nanos()
196 .try_into()
197 .expect("Elapsed time too large to fit into Instant"),
198 }
199 }
200
201 /// Construct an [`Instant`] from a `std::time::SystemTime` based on the distance from the unix
202 /// epoch.
203 ///
204 /// # Example
205 ///
206 /// ```
207 /// # use sans_io_time::Instant;
208 /// # use core::time::Duration;
209 /// let sys_time = std::time::SystemTime::now();
210 /// assert!(Instant::from_system(sys_time) >= Instant::from_nanos(0));
211 /// ```
212 #[cfg(feature = "std")]
213 pub fn from_system(sys_time: ::std::time::SystemTime) -> Self {
214 let dur = sys_time
215 .duration_since(::std::time::UNIX_EPOCH)
216 .expect("start time must not be before the unix epoch");
217 Self {
218 nanos: dur
219 .as_nanos()
220 .try_into()
221 .expect("Elapsed time too large to fit into Instant"),
222 }
223 }
224
225 /// Convert this [`Instant`] to a `std::time::Instant` using a base instant.
226 ///
227 /// This function takes a base `std::time::Instant` and adds the duration represented
228 /// by this [`Instant`] to create a corresponding `std::time::Instant`.
229 ///
230 /// # Example
231 ///
232 /// ```
233 /// # use sans_io_time::Instant;
234 /// # use core::time::Duration;
235 /// let base = std::time::Instant::now();
236 /// let instant = Instant::from_nanos(1_000_000_000); // 1 second
237 /// let std_instant = instant.to_std(base);
238 ///
239 /// // The resulting std_instant should be 1 second after base
240 /// assert!(std_instant.duration_since(base) == Duration::from_secs(1));
241 /// ```
242 #[cfg(feature = "std")]
243 pub fn to_std(&self, base_instant: ::std::time::Instant) -> ::std::time::Instant {
244 let duration_since = ::core::time::Duration::from_nanos(
245 self.nanos
246 .try_into()
247 .expect("Elapsed time too large to fit into Duration"),
248 );
249 base_instant + duration_since
250 }
251
252 /// Convert this [`Instant`] to a `std::time::SystemTime` using a base system time.
253 ///
254 /// This function takes a base `std::time::SystemTime` and adds the duration represented
255 /// by this [`Instant`] to create a corresponding `std::time::SystemTime`.
256 ///
257 /// # Example
258 ///
259 /// ```
260 /// # use sans_io_time::Instant;
261 /// # use core::time::Duration;
262 /// let base = std::time::SystemTime::now();
263 /// let instant = Instant::from_nanos(1_000_000_000); // 1 second
264 /// let sys_time = instant.to_system(base);
265 ///
266 /// // The resulting sys_time should be 1 second after base
267 /// assert!(sys_time.duration_since(base).unwrap() == Duration::from_secs(1));
268 /// ```
269 #[cfg(feature = "std")]
270 pub fn to_system(&self, base_system_time: ::std::time::SystemTime) -> ::std::time::SystemTime {
271 let duration_since = ::core::time::Duration::from_nanos(
272 self.nanos
273 .try_into()
274 .expect("Elapsed time too large to fit into Duration"),
275 );
276 base_system_time + duration_since
277 }
278}
279
280impl core::fmt::Display for Instant {
281 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
282 let secs = self.secs();
283 let nanos = self.subsec_nanos();
284 if secs != 0 {
285 write!(f, "{}", secs)?;
286 if nanos != 0 {
287 write!(f, ".{:0>9}", nanos.abs())?;
288 }
289 write!(f, "s")
290 } else if nanos != 0 {
291 if nanos < 0 {
292 write!(f, "-")?;
293 }
294 let millis = nanos.abs() / 1_000_000;
295 let millis_rem = nanos.abs() % 1_000_000;
296 write!(f, "{}.{:0>6}ms", millis, millis_rem)
297 } else {
298 write!(f, "0s")
299 }
300 }
301}
302
303impl core::ops::Add<Duration> for Instant {
304 type Output = Instant;
305 fn add(self, rhs: Duration) -> Self::Output {
306 self.checked_add(rhs)
307 .expect("Duration too large to fit into Instant")
308 }
309}
310
311impl core::ops::AddAssign<Duration> for Instant {
312 fn add_assign(&mut self, rhs: Duration) {
313 *self = self
314 .checked_add(rhs)
315 .expect("Duration too large to fit into Instant");
316 }
317}
318
319impl core::ops::Sub<Duration> for Instant {
320 type Output = Instant;
321 fn sub(self, rhs: Duration) -> Self::Output {
322 self.checked_sub(rhs)
323 .expect("Duration too large to fit into Instant")
324 }
325}
326
327impl core::ops::SubAssign<Duration> for Instant {
328 fn sub_assign(&mut self, rhs: Duration) {
329 *self = self
330 .checked_sub(rhs)
331 .expect("Duration too large to fit into Instant");
332 }
333}
334
335impl core::ops::Sub<Instant> for Instant {
336 type Output = Duration;
337 fn sub(self, rhs: Instant) -> Self::Output {
338 self.saturating_duration_since(rhs)
339 }
340}
341
342#[cfg(test)]
343mod tests {
344 use super::*;
345
346 extern crate alloc;
347
348 #[test]
349 fn add() {
350 let base = Instant::from_nanos(1_222_333_444);
351 assert_eq!(
352 base + Duration::from_secs(1),
353 Instant::from_nanos(2_222_333_444)
354 );
355 let mut new = base;
356 new += Duration::from_secs(1);
357 assert_eq!(new, Instant::from_nanos(2_222_333_444));
358 }
359
360 #[test]
361 fn sub_duration() {
362 let base = Instant::from_nanos(1_222_333_444);
363 assert_eq!(
364 base - Duration::from_secs(1),
365 Instant::from_nanos(222_333_444)
366 );
367 let mut new = base;
368 new -= Duration::from_secs(1);
369 assert_eq!(new, Instant::from_nanos(222_333_444));
370 }
371
372 #[test]
373 fn sub_instant() {
374 let earlier = Instant::from_nanos(1_222_333_444);
375 let later = Instant::from_nanos(2_333_444_555);
376 assert_eq!(later - earlier, Duration::from_nanos(1_111_111_111));
377 assert_eq!(earlier - later, Duration::ZERO);
378 assert_eq!(earlier - earlier, Duration::ZERO);
379 }
380
381 #[test]
382 fn display() {
383 assert_eq!(&alloc::format!("{}", Instant::ZERO), "0s");
384 assert_eq!(&alloc::format!("{}", Instant::from_nanos(1)), "0.000001ms");
385 assert_eq!(
386 &alloc::format!("{}", Instant::from_nanos(-1)),
387 "-0.000001ms"
388 );
389 assert_eq!(
390 &alloc::format!("{}", Instant::from_nanos(1_000_000_000)),
391 "1s"
392 );
393 assert_eq!(
394 &alloc::format!("{}", Instant::from_nanos(1_000_000_001)),
395 "1.000000001s"
396 );
397 assert_eq!(
398 &alloc::format!("{}", Instant::from_nanos(1_100_000_000)),
399 "1.100000000s"
400 );
401 assert_eq!(
402 &alloc::format!("{}", Instant::from_nanos(-1_000_000_000)),
403 "-1s"
404 );
405 assert_eq!(
406 &alloc::format!("{}", Instant::from_nanos(-1_000_000_001)),
407 "-1.000000001s"
408 );
409 assert_eq!(
410 &alloc::format!("{}", Instant::from_nanos(-1_100_000_000)),
411 "-1.100000000s"
412 );
413 }
414}