use std::sync::Mutex;
use chrono::{DateTime, Duration, Utc};
pub trait Clock: Send + Sync {
fn now(&self) -> DateTime<Utc>;
}
#[derive(Debug, Default)]
pub struct SystemClock;
impl Clock for SystemClock {
fn now(&self) -> DateTime<Utc> {
Utc::now()
}
}
#[derive(Debug)]
pub struct MockClock {
instant: Mutex<DateTime<Utc>>,
}
impl MockClock {
#[must_use]
pub fn new(at: DateTime<Utc>) -> Self {
Self {
instant: Mutex::new(at),
}
}
pub fn advance(&self, delta: Duration) {
if let Ok(mut g) = self.instant.lock() {
*g += delta;
}
}
pub fn set(&self, at: DateTime<Utc>) {
if let Ok(mut g) = self.instant.lock() {
*g = at;
}
}
}
impl Clock for MockClock {
fn now(&self) -> DateTime<Utc> {
self.instant
.lock()
.map_or_else(|poisoned| *poisoned.into_inner(), |g| *g)
}
}
#[cfg(test)]
mod tests {
use super::*;
use chrono::TimeZone;
#[test]
fn system_clock_returns_recent_time() {
let c = SystemClock;
let before = Utc::now();
let t = c.now();
let after = Utc::now();
assert!(t >= before && t <= after);
}
#[test]
fn mock_clock_starts_at_given_instant() {
let at = Utc.with_ymd_and_hms(2026, 4, 21, 7, 0, 0).unwrap();
let c = MockClock::new(at);
assert_eq!(c.now(), at);
}
#[test]
fn mock_clock_advance_moves_forward() {
let at = Utc.with_ymd_and_hms(2026, 4, 21, 7, 0, 0).unwrap();
let c = MockClock::new(at);
c.advance(Duration::minutes(30));
assert_eq!(c.now(), at + Duration::minutes(30));
}
#[test]
fn mock_clock_advance_accepts_negative_delta() {
let at = Utc.with_ymd_and_hms(2026, 4, 21, 7, 0, 0).unwrap();
let c = MockClock::new(at);
c.advance(-Duration::hours(1));
assert_eq!(c.now(), at - Duration::hours(1));
}
#[test]
fn mock_clock_set_jumps_to_instant() {
let a = Utc.with_ymd_and_hms(2026, 1, 1, 0, 0, 0).unwrap();
let b = Utc.with_ymd_and_hms(2026, 12, 31, 23, 59, 59).unwrap();
let c = MockClock::new(a);
c.set(b);
assert_eq!(c.now(), b);
}
#[test]
fn clock_is_object_safe() {
fn takes_dyn(_c: &dyn Clock) {}
let c = MockClock::new(Utc::now());
takes_dyn(&c);
takes_dyn(&SystemClock);
}
#[test]
fn mock_clock_is_send_sync() {
fn assert_send_sync<T: Send + Sync>() {}
assert_send_sync::<MockClock>();
assert_send_sync::<SystemClock>();
}
}