Skip to main content

datex_core/stub/
time.rs

1pub use core::time::Duration;
2
3use core::{
4    cmp::Ordering,
5    ops::{Add, AddAssign, Sub, SubAssign},
6};
7
8/// A `no_std`-friendly stand-in for `std::time::Instant`.
9///
10/// - If the target has 64-bit atomics, `Instant::now()` is monotonic-ish (a counter).
11/// - Otherwise, `Instant::now()` is always the same instant (so elapsed = 0).
12///
13/// This is meant as a compile-time fallback, not a real clock.
14#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
15pub struct Instant {
16    ticks: u64,
17}
18
19// A "tick" is an abstract unit; we treat 1 tick == 1ns for Duration conversions.
20const TICK_NANOS: u64 = 1;
21
22impl Instant {
23    /// Returns an `Instant` corresponding to "now".
24    #[inline]
25    pub fn now() -> Self {
26        Self {
27            ticks: monotonic_ticks(),
28        }
29    }
30
31    /// Returns the amount of time elapsed since this instant was created.
32    #[inline]
33    pub fn elapsed(&self) -> Duration {
34        Self::now().saturating_duration_since(*self)
35    }
36
37    /// Returns the duration between two instants, or panics if `earlier` is later than `self`.
38    #[inline]
39    pub fn duration_since(&self, earlier: Instant) -> Duration {
40        match self.ticks.cmp(&earlier.ticks) {
41            Ordering::Less => {
42                panic!("stub::time::Instant::duration_since: earlier > self")
43            }
44            _ => ticks_to_duration(self.ticks - earlier.ticks),
45        }
46    }
47
48    /// Returns the duration between two instants, or `None` if `earlier` is later than `self`.
49    #[inline]
50    pub fn checked_duration_since(&self, earlier: Instant) -> Option<Duration> {
51        self.ticks.checked_sub(earlier.ticks).map(ticks_to_duration)
52    }
53
54    /// Returns the duration between two instants, saturating at zero if `earlier` is later than `self`.
55    #[inline]
56    pub fn saturating_duration_since(&self, earlier: Instant) -> Duration {
57        match self.ticks.checked_sub(earlier.ticks) {
58            Some(dt) => ticks_to_duration(dt),
59            None => Duration::from_secs(0),
60        }
61    }
62
63    /// Returns `Some(t)` where `t` is the time `self + duration` if it does not overflow.
64    #[inline]
65    pub fn checked_add(&self, duration: Duration) -> Option<Instant> {
66        let dt = duration_to_ticks(duration)?;
67        self.ticks.checked_add(dt).map(|t| Instant { ticks: t })
68    }
69
70    /// Returns `Some(t)` where `t` is the time `self - duration` if it does not underflow.
71    #[inline]
72    pub fn checked_sub(&self, duration: Duration) -> Option<Instant> {
73        let dt = duration_to_ticks(duration)?;
74        self.ticks.checked_sub(dt).map(|t| Instant { ticks: t })
75    }
76}
77
78impl Ord for Instant {
79    #[inline]
80    fn cmp(&self, other: &Self) -> Ordering {
81        self.ticks.cmp(&other.ticks)
82    }
83}
84
85impl PartialOrd for Instant {
86    #[inline]
87    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
88        Some(self.cmp(other))
89    }
90}
91
92impl Add<Duration> for Instant {
93    type Output = Instant;
94    #[inline]
95    fn add(self, rhs: Duration) -> Instant {
96        self.checked_add(rhs)
97            .expect("stub::time::Instant + Duration overflow")
98    }
99}
100
101impl AddAssign<Duration> for Instant {
102    #[inline]
103    fn add_assign(&mut self, rhs: Duration) {
104        *self = *self + rhs;
105    }
106}
107
108impl Sub<Duration> for Instant {
109    type Output = Instant;
110    #[inline]
111    fn sub(self, rhs: Duration) -> Instant {
112        self.checked_sub(rhs)
113            .expect("stub::time::Instant - Duration underflow")
114    }
115}
116
117impl SubAssign<Duration> for Instant {
118    #[inline]
119    fn sub_assign(&mut self, rhs: Duration) {
120        *self = *self - rhs;
121    }
122}
123
124impl Sub<Instant> for Instant {
125    type Output = Duration;
126    #[inline]
127    fn sub(self, rhs: Instant) -> Duration {
128        self.duration_since(rhs)
129    }
130}
131
132/// A conservative stand-in for `std::time::SystemTime`.
133///
134/// In this stub:
135/// - `SystemTime::now()` returns `UNIX_EPOCH` (so it compiles deterministically).
136/// - `duration_since` / `elapsed` behave consistently based on that.
137#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
138pub struct SystemTime {
139    // represent as duration since UNIX_EPOCH
140    since_epoch: Duration,
141}
142
143#[derive(Copy, Clone, Eq, PartialEq, Debug)]
144pub struct SystemTimeError {
145    _priv: (),
146}
147
148impl SystemTimeError {
149    #[inline]
150    pub fn duration(&self) -> Duration {
151        Duration::from_secs(0)
152    }
153}
154
155pub const UNIX_EPOCH: SystemTime = SystemTime {
156    since_epoch: Duration::from_secs(0),
157};
158
159impl SystemTime {
160    #[inline]
161    pub fn now() -> SystemTime {
162        // Deterministic fallback: "time does not advance".
163        UNIX_EPOCH
164    }
165
166    #[inline]
167    pub fn duration_since(
168        &self,
169        earlier: SystemTime,
170    ) -> Result<Duration, SystemTimeError> {
171        match self.since_epoch.checked_sub(earlier.since_epoch) {
172            Some(d) => Ok(d),
173            None => Err(SystemTimeError { _priv: () }),
174        }
175    }
176
177    #[inline]
178    pub fn elapsed(&self) -> Result<Duration, SystemTimeError> {
179        SystemTime::now().duration_since(*self)
180    }
181
182    #[inline]
183    pub fn checked_add(&self, duration: Duration) -> Option<SystemTime> {
184        self.since_epoch
185            .checked_add(duration)
186            .map(|d| SystemTime { since_epoch: d })
187    }
188
189    #[inline]
190    pub fn checked_sub(&self, duration: Duration) -> Option<SystemTime> {
191        self.since_epoch
192            .checked_sub(duration)
193            .map(|d| SystemTime { since_epoch: d })
194    }
195}
196
197// ---- internals ----
198
199#[inline]
200fn ticks_to_duration(ticks: u64) -> Duration {
201    // 1 tick = 1ns (arbitrary but convenient)
202    let nanos = ticks.saturating_mul(TICK_NANOS);
203    Duration::from_nanos(nanos)
204}
205
206#[inline]
207fn duration_to_ticks(d: Duration) -> Option<u64> {
208    let secs = d.as_secs();
209    let sub = d.subsec_nanos() as u64;
210
211    // total_nanos = secs * 1_000_000_000 + sub
212    let a = secs.checked_mul(1_000_000_000)?;
213    let total = a.checked_add(sub)?;
214    Some(total / TICK_NANOS)
215}
216
217#[inline]
218fn monotonic_ticks() -> u64 {
219    // If we have atomics, make `now()` monotonic-ish via a global counter.
220    // Otherwise: deterministic "frozen time".
221    #[cfg(target_has_atomic = "64")]
222    {
223        use core::sync::atomic::{AtomicU64, Ordering};
224        static COUNTER: AtomicU64 = AtomicU64::new(0);
225        // Start at 1 to avoid "all zeros" if you care.
226        COUNTER.fetch_add(1, Ordering::Relaxed) + 1
227    }
228
229    #[cfg(not(target_has_atomic = "64"))]
230    {
231        0
232    }
233}