use std::fmt;
use std::sync::Mutex;
use chrono::{DateTime, TimeDelta, Utc};
pub trait Clock: Send + Sync + fmt::Debug {
fn now(&self) -> DateTime<Utc>;
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub struct SystemClock;
impl Clock for SystemClock {
fn now(&self) -> DateTime<Utc> {
Utc::now()
}
}
#[derive(Debug)]
pub struct ManualClock {
now: Mutex<DateTime<Utc>>,
}
impl ManualClock {
#[must_use]
pub fn new(start: DateTime<Utc>) -> Self {
Self {
now: Mutex::new(start),
}
}
#[must_use]
pub fn epoch() -> Self {
Self::new(DateTime::<Utc>::UNIX_EPOCH)
}
pub fn set(&self, instant: DateTime<Utc>) {
match self.now.lock() {
Ok(mut guard) => *guard = instant,
Err(poisoned) => *poisoned.into_inner() = instant,
}
}
pub fn advance(&self, delta: TimeDelta) {
let next = self.now() + delta;
self.set(next);
}
}
impl Default for ManualClock {
fn default() -> Self {
Self::epoch()
}
}
impl Clock for ManualClock {
fn now(&self) -> DateTime<Utc> {
match self.now.lock() {
Ok(guard) => *guard,
Err(poisoned) => *poisoned.into_inner(),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn manual_clock_is_stopped_until_moved() {
let clock = ManualClock::epoch();
assert_eq!(clock.now(), DateTime::<Utc>::UNIX_EPOCH);
assert_eq!(clock.now(), clock.now());
clock.advance(TimeDelta::hours(2));
assert_eq!(
clock.now(),
DateTime::<Utc>::UNIX_EPOCH + TimeDelta::hours(2)
);
clock.set(DateTime::<Utc>::UNIX_EPOCH);
assert_eq!(clock.now(), DateTime::<Utc>::UNIX_EPOCH);
}
#[test]
fn system_clock_moves_forward() {
let clock = SystemClock;
assert!(clock.now() <= clock.now());
}
}