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}