Skip to main content

clt_database/io/
clock.rs

1use std::sync::LazyLock;
2use std::time::{Duration, SystemTime, UNIX_EPOCH};
3
4/// A monotonic instant in time, backed by `std::time::Instant`.
5#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
6pub struct MonotonicInstant(u128);
7
8impl MonotonicInstant {
9    pub const fn from_nanos(nanos: u128) -> Self {
10        MonotonicInstant(nanos)
11    }
12
13    pub fn now() -> Self {
14        static EPOCH: LazyLock<std::time::Instant> = LazyLock::new(std::time::Instant::now);
15        let elapsed = EPOCH.elapsed();
16        MonotonicInstant(elapsed.as_nanos())
17    }
18
19    pub fn duration_since(&self, earlier: MonotonicInstant) -> Duration {
20        Duration::from_nanos(self.0.saturating_sub(earlier.0) as u64)
21    }
22
23    pub fn checked_add(&self, duration: Duration) -> Option<MonotonicInstant> {
24        self.0
25            .checked_add(duration.as_nanos())
26            .map(MonotonicInstant)
27    }
28
29    pub fn checked_sub(&self, duration: Duration) -> Option<MonotonicInstant> {
30        self.0
31            .checked_sub(duration.as_nanos())
32            .map(MonotonicInstant)
33    }
34}
35
36impl std::ops::Add<Duration> for MonotonicInstant {
37    type Output = MonotonicInstant;
38
39    fn add(self, rhs: Duration) -> Self::Output {
40        MonotonicInstant(self.0 + rhs.as_nanos())
41    }
42}
43
44impl std::ops::Sub<Duration> for MonotonicInstant {
45    type Output = MonotonicInstant;
46
47    fn sub(self, rhs: Duration) -> Self::Output {
48        MonotonicInstant(self.0 - rhs.as_nanos())
49    }
50}
51
52/// Wall-clock time as seconds and microseconds since Unix epoch.
53#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
54pub struct WallClockInstant {
55    pub secs: i64,
56    pub micros: u32,
57}
58
59const MICROS_PER_SEC: u32 = 1_000_000;
60
61impl WallClockInstant {
62    pub fn now() -> Self {
63        let duration = SystemTime::now()
64            .duration_since(UNIX_EPOCH)
65            .expect("system time before Unix epoch");
66        WallClockInstant {
67            secs: duration.as_secs() as i64,
68            micros: duration.subsec_micros(),
69        }
70    }
71
72    pub fn to_system_time(self) -> SystemTime {
73        if self.secs >= 0 {
74            UNIX_EPOCH + Duration::new(self.secs as u64, self.micros * 1000)
75        } else {
76            let positive_secs = (-self.secs) as u64;
77            if self.micros > 0 {
78                let nanos_to_subtract = (1_000_000 - self.micros) * 1000;
79                UNIX_EPOCH - Duration::new(positive_secs - 1, nanos_to_subtract)
80            } else {
81                UNIX_EPOCH - Duration::new(positive_secs, 0)
82            }
83        }
84    }
85
86    pub fn checked_add_duration(&self, other: &Duration) -> Option<WallClockInstant> {
87        let mut secs = self.secs.checked_add_unsigned(other.as_secs())?;
88        let mut micros = other.subsec_micros() + self.micros;
89        if micros >= MICROS_PER_SEC {
90            micros -= MICROS_PER_SEC;
91            secs = secs.checked_add(1)?;
92        }
93        Some(Self { secs, micros })
94    }
95
96    pub fn checked_sub_duration(&self, other: &Duration) -> Option<WallClockInstant> {
97        let mut secs = self.secs.checked_sub_unsigned(other.as_secs())?;
98        let mut micros = self.micros as i32 - other.subsec_micros() as i32;
99        if micros < 0 {
100            micros += MICROS_PER_SEC as i32;
101            secs = secs.checked_sub(1)?;
102        }
103        Some(Self {
104            secs,
105            micros: micros as u32,
106        })
107    }
108}
109
110impl std::ops::Add<Duration> for WallClockInstant {
111    type Output = WallClockInstant;
112
113    fn add(self, rhs: Duration) -> Self::Output {
114        self.checked_add_duration(&rhs)
115            .expect("duration addition overflow")
116    }
117}
118
119impl std::ops::Sub<Duration> for WallClockInstant {
120    type Output = WallClockInstant;
121
122    fn sub(self, rhs: Duration) -> Self::Output {
123        self.checked_sub_duration(&rhs)
124            .expect("duration subtraction underflow")
125    }
126}
127
128impl<T: chrono::TimeZone> From<chrono::DateTime<T>> for WallClockInstant {
129    fn from(value: chrono::DateTime<T>) -> Self {
130        WallClockInstant {
131            secs: value.timestamp(),
132            micros: value.timestamp_subsec_micros(),
133        }
134    }
135}
136
137pub trait Clock {
138    /// Monotonic time for timeout checking and elapsed time measurement.
139    /// Cheap on real systems (reads TSC), controllable in simulation.
140    fn current_time_monotonic(&self) -> MonotonicInstant;
141
142    /// Wall-clock time for timestamps (WAL, datetime functions).
143    /// Controllable in simulation for deterministic behavior.
144    fn current_time_wall_clock(&self) -> WallClockInstant;
145}
146
147pub struct DefaultClock;
148
149impl Clock for DefaultClock {
150    fn current_time_monotonic(&self) -> MonotonicInstant {
151        MonotonicInstant::now()
152    }
153
154    fn current_time_wall_clock(&self) -> WallClockInstant {
155        WallClockInstant::now()
156    }
157}