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    /// Timeout using the global clock.
39    pub fn timeout<F: std::future::Future>(duration: Duration, future: F) -> ClockTimeout<F> {
40        Self::handle().timeout(duration, future)
41    }
42
43    /// Get a reference to the global clock handle.
44    pub fn handle() -> &'static ClockHandle {
45        &GLOBAL
46            .get_or_init(|| GlobalState {
47                handle: ClockHandle::realtime(),
48                controller: None,
49            })
50            .handle
51    }
52
53    /// Install a manual clock globally.
54    ///
55    /// - If not initialized: installs manual clock, returns controller
56    /// - If already manual: returns existing controller (idempotent)
57    /// - If already realtime: panics
58    ///
59    /// Must be called before any `Clock::now()` calls if you want manual time.
60    pub fn install_manual() -> ClockController {
61        Self::install_manual_at(Utc::now())
62    }
63
64    /// Install a manual clock globally starting at a specific time.
65    ///
66    /// See [`install_manual`](Self::install_manual) for details.
67    pub fn install_manual_at(start_at: DateTime<Utc>) -> ClockController {
68        // Check if already initialized
69        if let Some(state) = GLOBAL.get() {
70            return state
71                .controller
72                .clone()
73                .expect("Cannot install manual clock: realtime clock already initialized");
74        }
75
76        // Try to initialize
77        let (handle, ctrl) = ClockHandle::manual_at(start_at);
78
79        match GLOBAL.set(GlobalState {
80            handle,
81            controller: Some(ctrl.clone()),
82        }) {
83            Ok(()) => ctrl,
84            Err(_) => {
85                // Race: someone else initialized between our check and set
86                GLOBAL
87                    .get()
88                    .unwrap()
89                    .controller
90                    .clone()
91                    .expect("Cannot install manual clock: realtime clock already initialized")
92            }
93        }
94    }
95
96    /// Check if a manual clock is installed.
97    pub fn is_manual() -> bool {
98        GLOBAL
99            .get()
100            .map(|s| s.controller.is_some())
101            .unwrap_or(false)
102    }
103
104    /// Get the current manual time, if a manual clock is installed.
105    ///
106    /// Returns:
107    /// - `None` if no clock is initialized (doesn't initialize one)
108    /// - `None` for realtime clocks
109    /// - `Some(time)` for manual clocks
110    pub fn manual_now() -> Option<DateTime<Utc>> {
111        GLOBAL.get().and_then(|s| s.handle.manual_now())
112    }
113}