use std::sync::{atomic::AtomicI64, Arc};
use chrono::{DateTime, TimeZone, Utc};
pub trait Clock: Sync {
fn now(&self) -> DateTime<Utc>;
}
impl<C: Clock + Send + ?Sized> Clock for Arc<C> {
fn now(&self) -> DateTime<Utc> {
(**self).now()
}
}
impl<C: Clock + ?Sized> Clock for Box<C> {
fn now(&self) -> DateTime<Utc> {
(**self).now()
}
}
#[derive(Clone, Default)]
pub struct SystemClock {
_private: (),
}
impl Clock for SystemClock {
fn now(&self) -> DateTime<Utc> {
#[allow(clippy::disallowed_methods)]
Utc::now()
}
}
pub struct MockClock {
timestamp: AtomicI64,
}
impl Default for MockClock {
fn default() -> Self {
let datetime = Utc.with_ymd_and_hms(2022, 1, 16, 14, 40, 0).unwrap();
Self::new(datetime)
}
}
impl MockClock {
#[must_use]
pub fn new(datetime: DateTime<Utc>) -> Self {
let timestamp = AtomicI64::new(datetime.timestamp());
Self { timestamp }
}
pub fn advance(&self, duration: chrono::Duration) {
self.timestamp
.fetch_add(duration.num_seconds(), std::sync::atomic::Ordering::Relaxed);
}
}
impl Clock for MockClock {
fn now(&self) -> DateTime<Utc> {
let timestamp = self.timestamp.load(std::sync::atomic::Ordering::Relaxed);
chrono::TimeZone::timestamp_opt(&Utc, timestamp, 0).unwrap()
}
}
#[cfg(test)]
mod tests {
use chrono::Duration;
use super::*;
#[test]
fn test_mocked_clock() {
let clock = MockClock::default();
let first = clock.now();
std::thread::sleep(std::time::Duration::from_millis(10));
let second = clock.now();
assert_eq!(first, second);
clock.advance(Duration::microseconds(10 * 1000 * 1000));
let third = clock.now();
assert_eq!(first + Duration::microseconds(10 * 1000 * 1000), third);
}
#[test]
fn test_real_clock() {
let clock = SystemClock::default();
let first = clock.now();
std::thread::sleep(std::time::Duration::from_millis(10));
let second = clock.now();
assert_ne!(first, second);
assert!(first < second);
}
}