Skip to main content

es_entity/clock/
handle.rs

1use chrono::{DateTime, Utc};
2
3use std::{sync::Arc, time::Duration};
4
5use super::{
6    controller::ClockController,
7    inner::ClockInner,
8    manual::ManualClock,
9    realtime::RealtimeClock,
10    sleep::{ClockSleep, ClockTimeout},
11};
12
13pub use super::sleep::Elapsed;
14
15/// A handle to a clock for getting time and performing time-based operations.
16///
17/// This is the main interface for time operations. It's cheap to clone and
18/// can be shared across tasks and threads. All clones share the same underlying
19/// clock, so they see consistent time.
20///
21/// # Creating a Clock
22///
23/// ```rust
24/// use es_entity::clock::ClockHandle;
25///
26/// // Real-time clock for production
27/// let clock = ClockHandle::realtime();
28///
29/// // Manual clock for testing - returns (handle, controller)
30/// let (clock, ctrl) = ClockHandle::manual();
31/// ```
32///
33/// # Basic Operations
34///
35/// ```rust
36/// use es_entity::clock::ClockHandle;
37/// use std::time::Duration;
38///
39/// # async fn example() {
40/// let clock = ClockHandle::realtime();
41///
42/// // Get current time
43/// let now = clock.now();
44///
45/// // Sleep for a duration
46/// clock.sleep(Duration::from_secs(1)).await;
47///
48/// // Timeout a future
49/// match clock.timeout(Duration::from_secs(5), some_async_operation()).await {
50///     Ok(result) => println!("Completed: {:?}", result),
51///     Err(_) => println!("Timed out"),
52/// }
53/// # }
54/// # async fn some_async_operation() -> i32 { 42 }
55/// ```
56#[derive(Clone)]
57pub struct ClockHandle {
58    inner: Arc<ClockInner>,
59}
60
61impl ClockHandle {
62    /// Create a real-time clock that uses the system clock and tokio timers.
63    pub fn realtime() -> Self {
64        Self {
65            inner: Arc::new(ClockInner::Realtime(RealtimeClock)),
66        }
67    }
68
69    /// Create a manual clock starting at the current time.
70    ///
71    /// Returns a tuple of `(ClockHandle, ClockController)`. The handle provides
72    /// the common time interface, while the controller provides operations
73    /// for advancing time.
74    ///
75    /// # Example
76    ///
77    /// ```rust
78    /// use es_entity::clock::ClockHandle;
79    ///
80    /// let (clock, ctrl) = ClockHandle::manual();
81    /// ```
82    pub fn manual() -> (Self, ClockController) {
83        let clock = Arc::new(ManualClock::new());
84        let handle = Self {
85            inner: Arc::new(ClockInner::Manual(Arc::clone(&clock))),
86        };
87        let controller = ClockController { clock };
88        (handle, controller)
89    }
90
91    /// Create a manual clock starting at a specific time.
92    ///
93    /// Returns a tuple of `(ClockHandle, ClockController)`. The handle provides
94    /// the common time interface, while the controller provides operations
95    /// for advancing time.
96    ///
97    /// # Example
98    ///
99    /// ```rust
100    /// use es_entity::clock::ClockHandle;
101    /// use chrono::Utc;
102    ///
103    /// let (clock, ctrl) = ClockHandle::manual_at(Utc::now() - chrono::Duration::days(30));
104    /// ```
105    pub fn manual_at(start_at: DateTime<Utc>) -> (Self, ClockController) {
106        let clock = Arc::new(ManualClock::new_at(start_at));
107        let handle = Self {
108            inner: Arc::new(ClockInner::Manual(Arc::clone(&clock))),
109        };
110        let controller = ClockController { clock };
111        (handle, controller)
112    }
113
114    /// Get the current time.
115    ///
116    /// This is a fast, synchronous operation regardless of clock type.
117    ///
118    /// For real-time clocks, this returns `Utc::now()`.
119    /// For manual clocks, this returns the current manual time.
120    #[inline]
121    pub fn now(&self) -> DateTime<Utc> {
122        match &*self.inner {
123            ClockInner::Realtime(rt) => rt.now(),
124            ClockInner::Manual(clock) => clock.now(),
125        }
126    }
127
128    /// Sleep for the given duration.
129    ///
130    /// For real-time clocks, this delegates to `tokio::time::sleep`.
131    /// For manual clocks, this waits until time is advanced.
132    pub fn sleep(&self, duration: Duration) -> ClockSleep {
133        ClockSleep::new(&self.inner, duration)
134    }
135
136    /// Apply a timeout to a future.
137    ///
138    /// Returns `Ok(output)` if the future completes before the timeout,
139    /// or `Err(Elapsed)` if the timeout expires first.
140    pub fn timeout<F>(&self, duration: Duration, future: F) -> ClockTimeout<F>
141    where
142        F: std::future::Future,
143    {
144        ClockTimeout::new(&self.inner, duration, future)
145    }
146
147    /// Check if this clock is manual (as opposed to realtime).
148    pub fn is_manual(&self) -> bool {
149        matches!(&*self.inner, ClockInner::Manual(_))
150    }
151
152    /// Get the current date (without time component).
153    ///
154    /// This is equivalent to `clock.now().date_naive()`.
155    #[inline]
156    pub fn today(&self) -> chrono::NaiveDate {
157        self.now().date_naive()
158    }
159
160    /// Get the current manual time, if this is a manual clock.
161    ///
162    /// Returns:
163    /// - `None` for realtime clocks
164    /// - `Some(time)` for manual clocks
165    ///
166    /// This is useful for code that needs to cache time when running under
167    /// manual clocks but use fresh time for realtime clocks.
168    pub fn manual_now(&self) -> Option<DateTime<Utc>> {
169        match &*self.inner {
170            ClockInner::Realtime(_) => None,
171            ClockInner::Manual(clock) => Some(clock.now()),
172        }
173    }
174}
175
176impl std::fmt::Debug for ClockHandle {
177    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
178        match &*self.inner {
179            ClockInner::Realtime(_) => f.debug_struct("ClockHandle::Realtime").finish(),
180            ClockInner::Manual(clock) => f
181                .debug_struct("ClockHandle::Manual")
182                .field("now", &clock.now())
183                .finish(),
184        }
185    }
186}