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