Skip to main content

actionqueue_core/time/
clock.rs

1//! Unified clock trait and implementations for the ActionQueue system.
2//!
3//! This module defines a single clock abstraction used across all ActionQueue
4//! crates. Time is represented as a 64-bit Unix timestamp (seconds since
5//! epoch).
6
7/// A trait for obtaining the current time.
8///
9/// This trait abstracts time sources to enable deterministic testing.
10/// Implementations should provide monotonic time progression for
11/// scheduling decisions.
12///
13/// Time is represented as a 64-bit Unix timestamp (seconds since epoch).
14pub trait Clock: Send + Sync {
15    /// Returns the current time as seconds since Unix epoch.
16    fn now(&self) -> u64;
17}
18
19/// A wall-clock implementation that uses the actual system time.
20///
21/// This implementation reads from the system clock and is suitable for
22/// production use.
23#[derive(Debug, Clone, Copy, Default)]
24pub struct SystemClock;
25
26impl Clock for SystemClock {
27    /// Returns the current time as seconds since Unix epoch.
28    ///
29    /// # Panics
30    ///
31    /// Panics if the system clock reports a time before the Unix epoch
32    /// (January 1, 1970). This is unreachable on all supported platforms.
33    fn now(&self) -> u64 {
34        std::time::SystemTime::now()
35            .duration_since(std::time::UNIX_EPOCH)
36            .expect("system time before Unix epoch")
37            .as_secs()
38    }
39}
40
41impl SystemClock {
42    /// Returns the current time as a `std::time::SystemTime`.
43    pub fn now_system_time(&self) -> std::time::SystemTime {
44        std::time::SystemTime::now()
45    }
46}
47
48/// A mock clock implementation for deterministic testing.
49///
50/// This clock allows time to be advanced explicitly, enabling deterministic
51/// scheduling tests without relying on real time progression.
52#[derive(Debug, Clone)]
53pub struct MockClock {
54    /// The current time in seconds since Unix epoch.
55    current_time: u64,
56}
57
58impl MockClock {
59    /// Creates a new mock clock initialized to the given time.
60    pub fn new(current_time: u64) -> Self {
61        Self { current_time }
62    }
63
64    /// Creates a new mock clock initialized to the current system time.
65    ///
66    /// # Panics
67    ///
68    /// Panics if the system clock reports a time before the Unix epoch
69    /// (January 1, 1970). This is unreachable on all supported platforms.
70    pub fn with_system_time() -> Self {
71        Self::new(
72            std::time::SystemTime::now()
73                .duration_since(std::time::UNIX_EPOCH)
74                .expect("system time before Unix epoch")
75                .as_secs(),
76        )
77    }
78
79    /// Advances the clock by the specified number of seconds.
80    pub fn advance_by(&mut self, seconds: u64) {
81        self.current_time = self.current_time.saturating_add(seconds);
82    }
83
84    /// Sets the clock to a specific time.
85    pub fn set(&mut self, time: u64) {
86        self.current_time = time;
87    }
88
89    /// Returns the current time as a `std::time::SystemTime`.
90    pub fn now_system_time(&self) -> std::time::SystemTime {
91        std::time::SystemTime::UNIX_EPOCH
92            .checked_add(std::time::Duration::from_secs(self.current_time))
93            .expect("time overflow")
94    }
95}
96
97impl Default for MockClock {
98    fn default() -> Self {
99        Self::with_system_time()
100    }
101}
102
103impl Clock for MockClock {
104    fn now(&self) -> u64 {
105        self.current_time
106    }
107}
108
109/// Shared clock handle type for use in multi-threaded contexts.
110pub type SharedClock = std::sync::Arc<dyn Clock>;
111
112#[cfg(test)]
113mod tests {
114    use super::*;
115
116    #[test]
117    fn system_clock_returns_monotonically_increasing_time() {
118        let clock = SystemClock;
119        let time1 = clock.now();
120        let time2 = clock.now();
121        assert!(time2 >= time1);
122    }
123
124    #[test]
125    fn mock_clock_can_be_initialized() {
126        let clock = MockClock::new(1000);
127        assert_eq!(clock.now(), 1000);
128    }
129
130    #[test]
131    fn mock_clock_can_be_advanced() {
132        let mut clock = MockClock::new(1000);
133        clock.advance_by(100);
134        assert_eq!(clock.now(), 1100);
135    }
136
137    #[test]
138    fn mock_clock_can_be_set() {
139        let mut clock = MockClock::new(1000);
140        clock.set(2000);
141        assert_eq!(clock.now(), 2000);
142    }
143
144    #[test]
145    fn mock_clock_system_time_conversion() {
146        let clock = MockClock::new(1000);
147        let system_time = clock.now_system_time();
148        let duration = system_time.duration_since(std::time::UNIX_EPOCH).expect("time overflow");
149        assert_eq!(duration.as_secs(), 1000);
150    }
151}