1use std::sync::{Arc, Mutex, PoisonError};
4
5use chrono::{DateTime, Utc};
6
7pub trait Clock: Send + Sync + 'static {
12 fn now(&self) -> DateTime<Utc>;
14}
15
16#[derive(Clone, Copy, Debug, Default)]
18pub struct SystemClock;
19
20impl Clock for SystemClock {
21 fn now(&self) -> DateTime<Utc> {
22 Utc::now()
23 }
24}
25
26impl<F> Clock for F
27where
28 F: Fn() -> DateTime<Utc> + Send + Sync + 'static,
29{
30 fn now(&self) -> DateTime<Utc> {
31 self()
32 }
33}
34
35#[derive(Clone, Debug)]
37pub struct ManualClock {
38 now: Arc<Mutex<DateTime<Utc>>>,
39}
40
41impl ManualClock {
42 pub fn new(start: DateTime<Utc>) -> Self {
44 Self {
45 now: Arc::new(Mutex::new(start)),
46 }
47 }
48
49 pub fn set(&self, instant: DateTime<Utc>) {
51 *self.now.lock().unwrap_or_else(PoisonError::into_inner) = instant;
52 }
53
54 pub fn advance(&self, by: std::time::Duration) {
56 let mut now = self.now.lock().unwrap_or_else(PoisonError::into_inner);
57 let delta = chrono::TimeDelta::from_std(by).unwrap_or(chrono::TimeDelta::MAX);
58 *now = now.checked_add_signed(delta).unwrap_or(DateTime::<Utc>::MAX_UTC);
59 }
60}
61
62impl Default for ManualClock {
63 fn default() -> Self {
64 Self::new(Utc::now())
65 }
66}
67
68impl Clock for ManualClock {
69 fn now(&self) -> DateTime<Utc> {
70 *self.now.lock().unwrap_or_else(PoisonError::into_inner)
71 }
72}
73
74#[cfg(test)]
75mod tests {
76 use std::time::Duration;
77
78 use super::*;
79
80 #[test]
81 fn manual_clock_advances_and_is_shared_between_clones() {
82 let start = DateTime::from_timestamp(1_700_000_000, 0).unwrap();
83 let clock = ManualClock::new(start);
84 let other = clock.clone();
85 clock.advance(Duration::from_secs(90));
86 assert_eq!(other.now(), start + chrono::TimeDelta::seconds(90));
87 other.set(start);
88 assert_eq!(clock.now(), start);
89 }
90
91 #[test]
92 fn manual_clock_saturates() {
93 let clock = ManualClock::new(DateTime::<Utc>::MAX_UTC - chrono::TimeDelta::seconds(1));
94 clock.advance(Duration::from_secs(u64::MAX));
95 assert_eq!(clock.now(), DateTime::<Utc>::MAX_UTC);
96 }
97
98 #[test]
99 fn closures_are_clocks() {
100 let fixed = DateTime::from_timestamp(42, 0).unwrap();
101 let clock = move || fixed;
102 assert_eq!(Clock::now(&clock), fixed);
103 }
104}