use chrono::{DateTime, Utc};
use std::time::Instant;
pub trait Clock {
fn now(&self) -> DateTime<Utc>;
}
#[derive(Debug, Default, Clone, Copy)]
pub struct Wall;
impl Clock for Wall {
fn now(&self) -> DateTime<Utc> {
Utc::now()
}
}
#[derive(Debug)]
pub struct Monotonic {
anchor_wall: DateTime<Utc>,
anchor_mono: Instant,
}
impl Monotonic {
pub fn new() -> Self {
Self {
anchor_wall: Utc::now(),
anchor_mono: Instant::now(),
}
}
}
impl Default for Monotonic {
fn default() -> Self {
Self::new()
}
}
impl Clock for Monotonic {
fn now(&self) -> DateTime<Utc> {
let elapsed = self.anchor_mono.elapsed();
self.anchor_wall + chrono::Duration::from_std(elapsed).unwrap_or(chrono::Duration::zero())
}
}
#[derive(Debug, Clone, Copy)]
pub struct Fixed {
instant: DateTime<Utc>,
}
impl Fixed {
pub fn new(instant: DateTime<Utc>) -> Self {
Self { instant }
}
}
impl Clock for Fixed {
fn now(&self) -> DateTime<Utc> {
self.instant
}
}
#[cfg(test)]
mod tests {
use super::*;
use chrono::TimeZone;
#[test]
fn fixed_clock_is_deterministic() {
let target = Utc.with_ymd_and_hms(2026, 5, 22, 14, 30, 45).unwrap();
let clock = Fixed::new(target);
assert_eq!(clock.now(), target);
assert_eq!(clock.now(), target);
}
#[test]
fn wall_clock_is_monotonic_ish() {
let clock = Wall;
let a = clock.now();
let b = clock.now();
assert!(b >= a, "wall clock went backwards: {a} -> {b}");
}
#[test]
fn monotonic_clock_advances() {
let clock = Monotonic::new();
let a = clock.now();
std::thread::sleep(std::time::Duration::from_millis(2));
let b = clock.now();
assert!(b > a, "monotonic clock did not advance: {a} -> {b}");
}
}