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}