1use std::sync::{Arc, Mutex};
17use std::time::{Duration, Instant};
18
19use chrono::{DateTime, Utc};
20
21pub type ClockHandle = Arc<dyn Clock>;
22
23pub trait Clock: Send + Sync {
24 fn now(&self) -> DateTime<Utc>;
25 fn monotonic_instant(&self) -> Instant;
26}
27
28pub struct SystemClock;
29
30impl SystemClock {
31 pub fn new() -> Self {
32 Self
33 }
34}
35
36impl Default for SystemClock {
37 fn default() -> Self {
38 Self::new()
39 }
40}
41
42impl Clock for SystemClock {
43 fn now(&self) -> DateTime<Utc> {
44 Utc::now()
45 }
46
47 fn monotonic_instant(&self) -> Instant {
48 Instant::now()
49 }
50}
51
52pub struct FakeClock {
53 state: Mutex<FakeState>,
54}
55
56struct FakeState {
57 wall: DateTime<Utc>,
58 monotonic: Instant,
59}
60
61impl FakeClock {
62 pub fn new(initial_wall: DateTime<Utc>) -> Self {
63 Self {
64 state: Mutex::new(FakeState {
65 wall: initial_wall,
66 monotonic: Instant::now(),
67 }),
68 }
69 }
70
71 pub fn advance(&self, by: Duration) {
73 let mut s = self.state.lock().expect("FakeClock state poisoned");
74 s.wall += chrono::Duration::from_std(by).expect("FakeClock advance overflow");
75 s.monotonic += by;
76 }
77
78 pub fn set(&self, wall: DateTime<Utc>) {
81 let mut s = self.state.lock().expect("FakeClock state poisoned");
82 s.wall = wall;
83 }
84}
85
86impl Clock for FakeClock {
87 fn now(&self) -> DateTime<Utc> {
88 self.state.lock().expect("FakeClock state poisoned").wall
89 }
90
91 fn monotonic_instant(&self) -> Instant {
92 self.state
93 .lock()
94 .expect("FakeClock state poisoned")
95 .monotonic
96 }
97}
98
99#[cfg(test)]
100mod tests {
101 use super::*;
102
103 fn t0() -> DateTime<Utc> {
104 "2026-05-16T00:00:00Z".parse().unwrap()
105 }
106
107 #[test]
108 fn advance_moves_wall_and_monotonic_together() {
109 let c = FakeClock::new(t0());
110 let m0 = c.monotonic_instant();
111 c.advance(Duration::from_secs(60));
112 assert_eq!(c.now() - t0(), chrono::Duration::seconds(60));
113 assert_eq!(c.monotonic_instant() - m0, Duration::from_secs(60));
114 }
115
116 #[test]
117 fn set_moves_wall_only() {
118 let c = FakeClock::new(t0());
119 let m0 = c.monotonic_instant();
120 let later = t0() + chrono::Duration::hours(1);
121 c.set(later);
122 assert_eq!(c.now(), later);
123 assert_eq!(c.monotonic_instant(), m0);
124 }
125
126 #[test]
127 fn monotonic_never_regresses_across_advances() {
128 let c = FakeClock::new(t0());
129 let m0 = c.monotonic_instant();
130 c.advance(Duration::from_secs(30));
131 let m1 = c.monotonic_instant();
132 c.advance(Duration::from_secs(30));
133 let m2 = c.monotonic_instant();
134 assert!(m1 > m0);
135 assert!(m2 > m1);
136 }
137
138 #[test]
139 fn system_clock_now_tracks_real_now() {
140 let c = SystemClock::new();
141 let drift = (Utc::now() - c.now()).num_milliseconds().abs();
142 assert!(drift < 1000, "SystemClock drift {drift}ms");
143 }
144
145 #[test]
146 fn fake_clock_dispatches_via_trait_object() {
147 let fake = Arc::new(FakeClock::new(t0()));
148 let handle: ClockHandle = fake.clone();
149 assert_eq!(handle.now(), t0());
150 fake.advance(Duration::from_secs(10));
151 assert_eq!(handle.now() - t0(), chrono::Duration::seconds(10));
152 }
153}