Skip to main content

atomic_time/
lib.rs

1//! Atomic time types
2#![cfg_attr(not(feature = "std"), no_std)]
3#![cfg_attr(docsrs, feature(doc_cfg))]
4#![cfg_attr(docsrs, allow(unused_attributes))]
5#![deny(missing_docs, warnings)]
6#![forbid(unsafe_code)]
7
8pub use core::sync::atomic::Ordering;
9
10use portable_atomic::AtomicU128;
11
12mod duration;
13pub use duration::AtomicDuration;
14mod option_duration;
15pub use option_duration::AtomicOptionDuration;
16
17/// Utility functions for encoding/decoding [`Duration`] to other types.
18pub mod utils {
19  #[cfg(feature = "std")]
20  use std::time::{Duration, Instant, SystemTime};
21
22  #[cfg(feature = "std")]
23  fn init() -> (Duration, Instant) {
24    static ONCE: std::sync::OnceLock<(Duration, Instant)> = std::sync::OnceLock::new();
25
26    *ONCE.get_or_init(|| {
27      let epoch_dur = SystemTime::now()
28        .duration_since(SystemTime::UNIX_EPOCH)
29        .unwrap();
30      let instant_now = Instant::now();
31      (epoch_dur, instant_now)
32    })
33  }
34
35  /// Encode an [`Instant`] into a [`Duration`].
36  #[cfg(feature = "std")]
37  #[cfg_attr(docsrs, doc(cfg(feature = "std")))]
38  #[cfg_attr(not(tarpaulin), inline(always))]
39  pub fn encode_instant_to_duration(instant: Instant) -> Duration {
40    let (epoch_dur, instant_now) = init();
41    if instant <= instant_now {
42      epoch_dur - (instant_now - instant)
43    } else {
44      epoch_dur + (instant - instant_now)
45    }
46  }
47
48  /// Decode an [`Instant`] from a [`Duration`].
49  ///
50  /// Accepts non-canonical input without panicking. If the encoded
51  /// Duration is so large that `instant_now + delta` would overflow
52  /// `Instant`'s internal representation, the result saturates at
53  /// `instant_now` rather than panicking. This is important because
54  /// the function sits on the serde deserialization path — a malformed
55  /// or adversarial encoded value must not crash the process.
56  #[cfg(feature = "std")]
57  #[cfg_attr(docsrs, doc(cfg(feature = "std")))]
58  #[cfg_attr(not(tarpaulin), inline(always))]
59  pub fn decode_instant_from_duration(duration: Duration) -> Instant {
60    let (epoch_dur, instant_now) = init();
61    if duration >= epoch_dur {
62      let delta = duration - epoch_dur;
63      // `Instant::checked_add` returns `None` when the result
64      // would overflow the platform's monotonic-clock range.
65      // Fall back to `instant_now` — the decoded Instant is
66      // "wrong" for such extreme inputs, but the alternative
67      // (panicking) propagates into serde `Deserialize` impls
68      // and crashes the caller.
69      instant_now.checked_add(delta).unwrap_or(instant_now)
70    } else {
71      let delta = epoch_dur - duration;
72      instant_now.checked_sub(delta).unwrap_or(instant_now)
73    }
74  }
75
76  pub use super::duration::{decode_duration, encode_duration};
77  pub use super::option_duration::{decode_option_duration, encode_option_duration};
78}
79
80#[cfg(feature = "std")]
81mod system_time;
82
83#[cfg(feature = "std")]
84#[cfg_attr(docsrs, doc(cfg(feature = "std")))]
85pub use system_time::AtomicSystemTime;
86
87#[cfg(feature = "std")]
88mod option_system_time;
89#[cfg(feature = "std")]
90#[cfg_attr(docsrs, doc(cfg(feature = "std")))]
91pub use option_system_time::AtomicOptionSystemTime;
92
93#[cfg(feature = "std")]
94mod instant;
95#[cfg(feature = "std")]
96#[cfg_attr(docsrs, doc(cfg(feature = "std")))]
97pub use instant::AtomicInstant;
98
99#[cfg(feature = "std")]
100mod option_instant;
101#[cfg(feature = "std")]
102#[cfg_attr(docsrs, doc(cfg(feature = "std")))]
103pub use option_instant::AtomicOptionInstant;
104
105#[cfg(feature = "std")]
106use utils::{decode_instant_from_duration, encode_instant_to_duration};