use std::sync::Arc;
use std::sync::atomic::{AtomicU64, Ordering};
use std::time::{SystemTime, UNIX_EPOCH};
pub trait Clock: std::fmt::Debug + Send + Sync + 'static {
fn now_unix_secs(&self) -> u64;
fn utc_day_number(&self) -> u64 {
self.now_unix_secs() / 86_400
}
}
#[derive(Debug, Default, Clone, Copy)]
pub struct SystemClock;
impl Clock for SystemClock {
fn now_unix_secs(&self) -> u64 {
SystemTime::now()
.duration_since(UNIX_EPOCH)
.expect("system clock is before UNIX epoch")
.as_secs()
}
}
#[derive(Debug, Default)]
pub struct ManualClock {
now: AtomicU64,
}
impl ManualClock {
pub fn new(start_unix_secs: u64) -> Self {
Self {
now: AtomicU64::new(start_unix_secs),
}
}
pub fn set(&self, unix_secs: u64) {
self.now.store(unix_secs, Ordering::SeqCst);
}
pub fn advance_secs(&self, secs: u64) -> u64 {
self.now.fetch_add(secs, Ordering::SeqCst) + secs
}
}
impl Clock for ManualClock {
fn now_unix_secs(&self) -> u64 {
self.now.load(Ordering::SeqCst)
}
}
impl<C: Clock + ?Sized> Clock for Arc<C> {
fn now_unix_secs(&self) -> u64 {
(**self).now_unix_secs()
}
fn utc_day_number(&self) -> u64 {
(**self).utc_day_number()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn system_clock_advances() {
let c = SystemClock;
let t1 = c.now_unix_secs();
std::thread::sleep(std::time::Duration::from_millis(1100));
let t2 = c.now_unix_secs();
assert!(t2 > t1, "expected clock to advance, t1={t1} t2={t2}");
}
#[test]
fn manual_clock_set_and_advance() {
let c = ManualClock::new(100);
assert_eq!(c.now_unix_secs(), 100);
let after = c.advance_secs(50);
assert_eq!(after, 150);
assert_eq!(c.now_unix_secs(), 150);
c.set(1_000);
assert_eq!(c.now_unix_secs(), 1_000);
}
#[test]
fn manual_clock_default_day_number() {
let c = ManualClock::new(86_400 * 3 + 42);
assert_eq!(c.utc_day_number(), 3);
}
#[test]
fn arc_clock_delegates() {
let c = Arc::new(ManualClock::new(500));
assert_eq!(c.now_unix_secs(), 500);
c.advance_secs(1);
assert_eq!(Clock::now_unix_secs(&c), 501);
}
}