Skip to main content

rskit_util/time/
clock.rs

1//! Clock abstractions for deterministic time-dependent code.
2
3use std::sync::Arc;
4use std::time::{Instant, SystemTime, UNIX_EPOCH};
5
6/// Shared clock handle.
7pub type SharedClock = Arc<dyn Clock>;
8
9/// Clock used for wall-clock timestamps and monotonic elapsed durations.
10///
11/// Production code normally uses [`SystemClock`]. Tests and reproducible
12/// harnesses can inject [`FixedClock`] or a domain-specific implementation.
13pub trait Clock: Send + Sync {
14    /// Monotonic milliseconds used for elapsed-duration measurements.
15    fn monotonic_millis(&self) -> u64;
16
17    /// Unix epoch seconds used for wall-clock timestamps.
18    fn epoch_seconds(&self) -> u64;
19}
20
21/// System-backed clock.
22#[derive(Debug)]
23pub struct SystemClock {
24    started_at: Instant,
25}
26
27impl Default for SystemClock {
28    fn default() -> Self {
29        Self::new()
30    }
31}
32
33impl SystemClock {
34    /// Create a new system-backed clock.
35    #[must_use]
36    pub fn new() -> Self {
37        Self {
38            started_at: Instant::now(),
39        }
40    }
41}
42
43impl Clock for SystemClock {
44    fn monotonic_millis(&self) -> u64 {
45        u64::try_from(self.started_at.elapsed().as_millis()).unwrap_or(u64::MAX)
46    }
47
48    fn epoch_seconds(&self) -> u64 {
49        SystemTime::now()
50            .duration_since(UNIX_EPOCH)
51            .ok()
52            .map_or(0, |duration| duration.as_secs())
53    }
54}
55
56/// Deterministic clock for tests and reproducible harnesses.
57#[derive(Debug, Clone)]
58pub struct FixedClock {
59    epoch_seconds: u64,
60    monotonic_millis: u64,
61}
62
63impl FixedClock {
64    /// Create a fixed clock snapshot.
65    #[must_use]
66    pub const fn new(epoch_seconds: u64, monotonic_millis: u64) -> Self {
67        Self {
68            epoch_seconds,
69            monotonic_millis,
70        }
71    }
72}
73
74impl Clock for FixedClock {
75    fn monotonic_millis(&self) -> u64 {
76        self.monotonic_millis
77    }
78
79    fn epoch_seconds(&self) -> u64 {
80        self.epoch_seconds
81    }
82}
83
84/// Return a shared system-backed clock.
85#[must_use]
86pub fn system_clock() -> SharedClock {
87    Arc::new(SystemClock::new())
88}
89
90#[cfg(test)]
91mod tests {
92    use super::{Clock, FixedClock};
93
94    #[test]
95    fn fixed_clock_returns_injected_values() {
96        let clock = FixedClock::new(1_700_000_000, 42);
97
98        assert_eq!(clock.epoch_seconds(), 1_700_000_000);
99        assert_eq!(clock.monotonic_millis(), 42);
100    }
101}