Skip to main content

rapport_temporal/
clock.rs

1//! For controlling time in programs, to make them testable over time periods.
2
3use chrono::Utc;
4use parking_lot::RwLock;
5use std::sync::Arc;
6
7use crate::{date::Date, time::Instant};
8
9#[derive(Debug, Clone, derive_more::Display)]
10pub enum Clock {
11    #[display("system time")]
12    System,
13    #[display("{_0}")]
14    Fake(FakeClock),
15}
16
17impl Clock {
18    #[must_use]
19    pub fn now(&self) -> Instant {
20        match self {
21            Clock::System => Instant::from_utc_datetime(Utc::now()),
22            Clock::Fake(provider) => provider.now(),
23        }
24    }
25
26    #[must_use]
27    pub fn today(&self) -> Date {
28        self.now().into_date()
29    }
30}
31
32/// A substute for the real clock for testing purposes, can be cloned but still refer to the same
33/// shared time, which can be updated centrally from tests.
34#[derive(Debug, Clone, derive_more::Display)]
35#[display("fake time (for testing), current time: {}", time.read().to_string())]
36pub struct FakeClock {
37    time: Arc<RwLock<Instant>>,
38}
39
40impl Default for FakeClock {
41    fn default() -> Self {
42        // use the system time as a default, in order to not force clients to deal with time
43        Self::new(Instant::from_utc_datetime(Utc::now()))
44    }
45}
46
47impl FakeClock {
48    #[must_use]
49    pub fn new(time: Instant) -> Self {
50        Self {
51            time: Arc::new(RwLock::new(time)),
52        }
53    }
54
55    pub fn add_days(&self, value: u32) {
56        let now = self.now();
57        let add_seconds = value * 24 * 60 * 60;
58        let added = Instant {
59            seconds: now.seconds.saturating_add(add_seconds.into()),
60            nanos: now.nanos,
61        };
62        self.set_time(added);
63    }
64
65    pub fn set_time(&self, time: Instant) {
66        *self.time.write() = time;
67    }
68
69    #[must_use]
70    pub fn now(&self) -> Instant {
71        *self.time.read()
72    }
73}
74
75impl From<FakeClock> for Clock {
76    fn from(value: FakeClock) -> Self {
77        Self::Fake(value)
78    }
79}
80
81impl From<&FakeClock> for Clock {
82    fn from(value: &FakeClock) -> Self {
83        value.clone().into()
84    }
85}