Skip to main content

es_entity/clock/
global.rs

1use chrono::{DateTime, Utc};
2
3use std::sync::OnceLock;
4use std::time::Duration;
5
6use super::{ClockController, ClockHandle, ClockSleep, ClockTimeout};
7
8struct GlobalState {
9    handle: ClockHandle,
10    controller: Option<ClockController>,
11}
12
13static GLOBAL: OnceLock<GlobalState> = OnceLock::new();
14
15/// Global clock access - like `Utc::now()` but testable.
16pub struct Clock;
17
18impl Clock {
19    /// Get current time from the global clock.
20    ///
21    /// Lazily initializes to realtime if not already set.
22    pub fn now() -> DateTime<Utc> {
23        Self::handle().now()
24    }
25
26    /// Get the current date (without time component).
27    ///
28    /// Lazily initializes to realtime if not already set.
29    pub fn today() -> chrono::NaiveDate {
30        Self::handle().today()
31    }
32
33    /// Sleep using the global clock.
34    pub fn sleep(duration: Duration) -> ClockSleep {
35        Self::handle().sleep(duration)
36    }
37
38    /// Sleep using the global clock with coalesceable wake-up behavior.
39    ///
40    /// See [`ClockHandle::sleep_coalesce`] for details.
41    pub fn sleep_coalesce(duration: Duration) -> ClockSleep {
42        Self::handle().sleep_coalesce(duration)
43    }
44
45    /// Timeout using the global clock.
46    pub fn timeout<F: std::future::Future>(duration: Duration, future: F) -> ClockTimeout<F> {
47        Self::handle().timeout(duration, future)
48    }
49
50    /// Get a reference to the global clock handle.
51    pub fn handle() -> &'static ClockHandle {
52        &GLOBAL
53            .get_or_init(|| GlobalState {
54                handle: ClockHandle::realtime(),
55                controller: None,
56            })
57            .handle
58    }
59
60    /// Install a manual clock globally.
61    ///
62    /// - If not initialized: installs manual clock, returns controller
63    /// - If already manual: returns existing controller (idempotent)
64    /// - If already realtime: panics
65    ///
66    /// Must be called before any `Clock::now()` calls if you want manual time.
67    pub fn install_manual() -> ClockController {
68        Self::install_manual_at(Utc::now())
69    }
70
71    /// Install a manual clock globally starting at a specific time.
72    ///
73    /// See [`install_manual`](Self::install_manual) for details.
74    pub fn install_manual_at(start_at: DateTime<Utc>) -> ClockController {
75        // Check if already initialized
76        if let Some(state) = GLOBAL.get() {
77            return state
78                .controller
79                .clone()
80                .expect("Cannot install manual clock: realtime clock already initialized");
81        }
82
83        // Try to initialize
84        let (handle, ctrl) = ClockHandle::manual_at(start_at);
85
86        match GLOBAL.set(GlobalState {
87            handle,
88            controller: Some(ctrl.clone()),
89        }) {
90            Ok(()) => ctrl,
91            Err(_) => {
92                // Race: someone else initialized between our check and set
93                GLOBAL
94                    .get()
95                    .unwrap()
96                    .controller
97                    .clone()
98                    .expect("Cannot install manual clock: realtime clock already initialized")
99            }
100        }
101    }
102
103    /// Check if a manual clock is installed.
104    pub fn is_manual() -> bool {
105        GLOBAL
106            .get()
107            .map(|s| s.controller.is_some())
108            .unwrap_or(false)
109    }
110
111    /// Get the current manual time, if a manual clock is installed.
112    ///
113    /// Returns:
114    /// - `None` if no clock is initialized (doesn't initialize one)
115    /// - `None` for realtime clocks
116    /// - `Some(time)` for manual clocks
117    pub fn manual_now() -> Option<DateTime<Utc>> {
118        GLOBAL.get().and_then(|s| s.handle.manual_now())
119    }
120}