fresh/services/
time_source.rs1use chrono::{NaiveDate, Utc};
10use std::sync::atomic::{AtomicU64, Ordering};
11use std::sync::Arc;
12use std::time::{Duration, Instant};
13
14pub trait TimeSource: Send + Sync + std::fmt::Debug {
19 fn now(&self) -> Instant;
21
22 fn sleep(&self, duration: Duration);
26
27 fn today_date(&self) -> NaiveDate;
29
30 fn elapsed_since(&self, earlier: Instant) -> Duration {
32 self.now().saturating_duration_since(earlier)
33 }
34}
35
36pub type SharedTimeSource = Arc<dyn TimeSource>;
38
39#[derive(Debug, Clone, Copy, Default)]
41pub struct RealTimeSource;
42
43impl RealTimeSource {
44 pub fn new() -> Self {
46 Self
47 }
48
49 pub fn shared() -> SharedTimeSource {
51 Arc::new(Self)
52 }
53}
54
55impl TimeSource for RealTimeSource {
56 fn now(&self) -> Instant {
57 Instant::now()
58 }
59
60 fn sleep(&self, duration: Duration) {
61 std::thread::sleep(duration);
62 }
63
64 fn today_date(&self) -> NaiveDate {
65 Utc::now().date_naive()
66 }
67}
68
69#[derive(Debug)]
91pub struct TestTimeSource {
92 logical_nanos: AtomicU64,
94 base_instant: Instant,
96 base_date: NaiveDate,
98}
99
100impl Default for TestTimeSource {
101 fn default() -> Self {
102 Self::new()
103 }
104}
105
106impl TestTimeSource {
107 pub fn new() -> Self {
109 Self {
110 logical_nanos: AtomicU64::new(0),
111 base_instant: Instant::now(),
112 base_date: Utc::now().date_naive(),
113 }
114 }
115
116 pub fn shared() -> Arc<Self> {
118 Arc::new(Self::new())
119 }
120
121 pub fn advance(&self, duration: Duration) {
125 self.logical_nanos
126 .fetch_add(duration.as_nanos() as u64, Ordering::SeqCst);
127 }
128
129 pub fn elapsed(&self) -> Duration {
131 Duration::from_nanos(self.logical_nanos.load(Ordering::SeqCst))
132 }
133
134 pub fn reset(&self) {
136 self.logical_nanos.store(0, Ordering::SeqCst);
137 }
138
139 pub fn nanos(&self) -> u64 {
141 self.logical_nanos.load(Ordering::SeqCst)
142 }
143}
144
145impl TimeSource for TestTimeSource {
146 fn now(&self) -> Instant {
147 self.base_instant + self.elapsed()
150 }
151
152 fn sleep(&self, duration: Duration) {
153 self.advance(duration);
156 }
157
158 fn today_date(&self) -> NaiveDate {
159 let elapsed_days = (self.elapsed().as_secs() / 86400) as i64;
161 self.base_date
162 .checked_add_signed(chrono::Duration::days(elapsed_days))
163 .unwrap_or(self.base_date)
164 }
165}
166
167#[cfg(test)]
168mod tests {
169 use super::*;
170
171 #[test]
172 fn real_time_source_now_advances() {
173 let ts = RealTimeSource::new();
174 let t1 = ts.now();
175 std::thread::sleep(Duration::from_millis(1));
176 let t2 = ts.now();
177 assert!(t2 > t1);
178 }
179
180 #[test]
181 fn test_time_source_starts_at_zero() {
182 let ts = TestTimeSource::new();
183 assert_eq!(ts.nanos(), 0);
184 assert_eq!(ts.elapsed(), Duration::ZERO);
185 }
186
187 #[test]
188 fn test_time_source_advance() {
189 let ts = TestTimeSource::new();
190 let start = ts.now();
191
192 ts.advance(Duration::from_secs(5));
193
194 assert_eq!(ts.elapsed(), Duration::from_secs(5));
195 assert!(ts.elapsed_since(start) >= Duration::from_secs(5));
196 }
197
198 #[test]
199 fn test_time_source_sleep_advances_time() {
200 let ts = TestTimeSource::new();
201 let start = ts.now();
202
203 ts.sleep(Duration::from_millis(100));
204
205 assert_eq!(ts.elapsed(), Duration::from_millis(100));
206 assert!(ts.elapsed_since(start) >= Duration::from_millis(100));
207 }
208
209 #[test]
210 fn test_time_source_reset() {
211 let ts = TestTimeSource::new();
212 ts.advance(Duration::from_secs(10));
213 assert_eq!(ts.elapsed(), Duration::from_secs(10));
214
215 ts.reset();
216 assert_eq!(ts.elapsed(), Duration::ZERO);
217 }
218
219 #[test]
220 fn test_time_source_thread_safe() {
221 use std::thread;
222
223 let ts = Arc::new(TestTimeSource::new());
224 let ts_clone = ts.clone();
225
226 let handle = thread::spawn(move || {
227 for _ in 0..100 {
228 ts_clone.advance(Duration::from_millis(1));
229 }
230 });
231
232 for _ in 0..100 {
233 ts.advance(Duration::from_millis(1));
234 }
235
236 handle.join().unwrap();
237
238 assert_eq!(ts.elapsed(), Duration::from_millis(200));
239 }
240
241 #[test]
242 fn shared_time_source_works() {
243 let real: SharedTimeSource = RealTimeSource::shared();
244 let test: SharedTimeSource = TestTimeSource::shared();
245
246 let _ = real.now();
248 let _ = test.now();
249 }
250}