#[cfg(any(test, feature = "test-clock"))]
use std::sync::Arc;
#[cfg(any(test, feature = "test-clock"))]
use std::time::Duration;
use std::time::{Instant, SystemTime, UNIX_EPOCH};
use chrono::{DateTime, Utc};
pub trait Clock: Send + Sync {
fn now(&self) -> Instant;
fn unix_timestamp(&self) -> u64;
fn unix_timestamp_nanos(&self) -> u64;
fn now_datetime(&self) -> DateTime<Utc> {
DateTime::from_timestamp_nanos(self.unix_timestamp_nanos() as i64)
}
}
#[derive(Debug, Clone, Copy)]
pub struct SystemClock;
impl Clock for SystemClock {
fn now(&self) -> Instant {
Instant::now()
}
fn unix_timestamp(&self) -> u64 {
SystemTime::now()
.duration_since(UNIX_EPOCH)
.map(|d| d.as_secs())
.unwrap_or(0)
}
fn unix_timestamp_nanos(&self) -> u64 {
SystemTime::now()
.duration_since(UNIX_EPOCH)
.map(|d| d.as_nanos() as u64)
.unwrap_or(0)
}
fn now_datetime(&self) -> DateTime<Utc> {
Utc::now()
}
}
#[cfg(any(test, feature = "test-clock"))]
pub struct MockClock {
current_time: parking_lot::RwLock<Instant>,
unix_nanos: parking_lot::RwLock<u128>,
}
#[cfg(any(test, feature = "test-clock"))]
impl Clone for MockClock {
fn clone(&self) -> Self {
Self {
current_time: parking_lot::RwLock::new(*self.current_time.read()),
unix_nanos: parking_lot::RwLock::new(*self.unix_nanos.read()),
}
}
}
#[cfg(any(test, feature = "test-clock"))]
impl MockClock {
pub fn new() -> Self {
Self {
current_time: parking_lot::RwLock::new(Instant::now()),
unix_nanos: parking_lot::RwLock::new(
SystemTime::now()
.duration_since(UNIX_EPOCH)
.map(|d| d.as_nanos())
.unwrap_or(0),
),
}
}
pub fn with_instant(instant: Instant, unix_ts: u64) -> Self {
Self {
current_time: parking_lot::RwLock::new(instant),
unix_nanos: parking_lot::RwLock::new((unix_ts as u128) * 1_000_000_000),
}
}
pub fn advance(&self, duration: Duration) {
{
let mut time = self.current_time.write();
*time = time.checked_add(duration).unwrap_or(*time);
}
let mut nanos = self.unix_nanos.write();
*nanos = nanos.saturating_add(duration.as_nanos());
}
pub fn set_time(&self, instant: Instant, unix_ts: u64) {
{
let mut time = self.current_time.write();
*time = instant;
}
let mut nanos = self.unix_nanos.write();
*nanos = (unix_ts as u128) * 1_000_000_000;
}
pub fn as_arc(self) -> Arc<dyn Clock> {
Arc::new(self)
}
}
#[cfg(any(test, feature = "test-clock"))]
impl Default for MockClock {
fn default() -> Self {
Self::new()
}
}
#[cfg(any(test, feature = "test-clock"))]
impl Clock for MockClock {
fn now(&self) -> Instant {
*self.current_time.read()
}
fn unix_timestamp(&self) -> u64 {
(*self.unix_nanos.read() / 1_000_000_000) as u64
}
fn unix_timestamp_nanos(&self) -> u64 {
u64::try_from(*self.unix_nanos.read()).unwrap_or(u64::MAX)
}
}
#[cfg(test)]
mod tests {
use super::*;
fn system_clock() -> Arc<dyn Clock> {
Arc::new(SystemClock)
}
#[test]
fn test_system_clock_now() {
let clock = SystemClock;
let now = clock.now();
let later = clock.now();
assert!(later >= now);
}
#[test]
fn test_system_clock_unix_timestamp() {
let clock = SystemClock;
let ts = clock.unix_timestamp();
assert!(ts > 1_700_000_000);
assert!(ts < 2_000_000_000);
}
#[test]
fn test_system_clock_unix_timestamp_nanos() {
let clock = SystemClock;
let ts_nanos = clock.unix_timestamp_nanos();
let ts_secs = clock.unix_timestamp();
let expected_nanos = ts_secs * 1_000_000_000;
assert!(ts_nanos >= expected_nanos);
assert!(ts_nanos < expected_nanos + 2_000_000_000);
}
#[test]
fn test_mock_clock_advance() {
let clock = MockClock::new();
let start = clock.now();
clock.advance(Duration::from_secs(10));
let elapsed = clock.now().duration_since(start);
assert_eq!(elapsed, Duration::from_secs(10));
}
#[test]
fn test_mock_clock_set_time() {
let clock = MockClock::new();
let custom_instant = Instant::now() + Duration::from_secs(100);
let custom_unix_ts = 1_700_000_000;
clock.set_time(custom_instant, custom_unix_ts);
assert_eq!(clock.now(), custom_instant);
assert_eq!(clock.unix_timestamp(), custom_unix_ts);
}
#[test]
fn test_mock_clock_unix_timestamp_nanos() {
let clock = MockClock::with_instant(Instant::now(), 1_700_000_000);
clock.advance(Duration::from_secs(5));
let ts_secs = clock.unix_timestamp();
let ts_nanos = clock.unix_timestamp_nanos();
assert_eq!(ts_secs, 1_700_000_005);
assert_eq!(ts_nanos, 1_700_000_005_000_000_000);
}
#[test]
fn test_mock_clock_thread_safety() {
let clock = Arc::new(MockClock::new());
let mut handles = vec![];
for _ in 0..10 {
let clock_clone = Arc::clone(&clock);
handles.push(std::thread::spawn(move || {
clock_clone.advance(Duration::from_secs(1));
clock_clone.now()
}));
}
for handle in handles {
handle.join().unwrap();
}
let elapsed = clock.now().duration_since(Instant::now());
let _ = elapsed;
}
#[test]
fn test_system_clock_arc() {
let clock = system_clock();
let ts = clock.unix_timestamp();
assert!(ts > 1_700_000_000);
}
#[test]
fn test_mock_clock_with_instant() {
let instant = Instant::now();
let unix_ts = 1_700_000_000;
let clock = MockClock::with_instant(instant, unix_ts);
assert_eq!(clock.now(), instant);
assert_eq!(clock.unix_timestamp(), unix_ts);
}
#[test]
fn test_mock_clock_default() {
let clock = MockClock::default();
let ts = clock.unix_timestamp();
assert!(ts > 1_700_000_000);
}
#[test]
fn test_mock_clock_clone() {
let clock = MockClock::new();
clock.advance(Duration::from_secs(42));
let cloned = clock.clone();
assert_eq!(cloned.unix_timestamp(), clock.unix_timestamp());
assert_eq!(clock.now(), cloned.now());
}
#[test]
fn test_mock_clock_as_arc() {
let clock = MockClock::new();
let arc_clock: Arc<dyn Clock> = clock.as_arc();
let ts = arc_clock.unix_timestamp();
assert!(ts > 1_700_000_000);
let now = arc_clock.now();
assert!(now.elapsed() < Duration::from_secs(5));
}
#[test]
fn test_mock_clock_clone_independent() {
let clock = MockClock::new();
clock.advance(Duration::from_secs(10));
let cloned = clock.clone();
clock.advance(Duration::from_secs(20));
assert_eq!(clock.unix_timestamp(), cloned.unix_timestamp() + 20);
}
#[test]
fn test_system_clock_copy_and_clone() {
let clock1 = SystemClock;
let clock2 = clock1;
let ts1 = clock1.unix_timestamp();
let ts2 = clock2.unix_timestamp();
assert!(ts1 <= ts2);
}
#[test]
fn test_mock_clock_advance_then_nanos() {
let clock = MockClock::with_instant(Instant::now(), 100);
clock.advance(Duration::from_secs(5));
let nanos = clock.unix_timestamp_nanos();
assert_eq!(nanos, 105 * 1_000_000_000);
}
#[test]
fn test_mock_clock_advance_subsecond_consistency() {
let clock = MockClock::with_instant(Instant::now(), 1_000);
let start = clock.now();
clock.advance(Duration::from_millis(900));
assert_eq!(
clock.unix_timestamp(),
1_000,
"a sub-second advance must not carry into seconds"
);
assert_eq!(
clock.unix_timestamp_nanos(),
1_000 * 1_000_000_000 + 900_000_000,
"unix_timestamp_nanos must carry the sub-second part"
);
clock.advance(Duration::from_millis(600)); assert_eq!(clock.unix_timestamp(), 1_001, "cross-second carry");
assert_eq!(
clock.unix_timestamp_nanos(),
1_000 * 1_000_000_000 + 1_500_000_000
);
assert_eq!(
clock.now().duration_since(start),
Duration::from_millis(1500)
);
}
#[test]
fn test_mock_clock_set_time_subsecond_zeroed() {
let clock = MockClock::new();
clock.set_time(Instant::now(), 1_700_000_000);
assert_eq!(
clock.unix_timestamp_nanos(),
1_700_000_000u64 * 1_000_000_000
);
}
#[test]
fn test_system_clock_now_datetime_close_to_utc_now() {
let clock = SystemClock;
let via_clock = clock.now_datetime();
let via_utc = Utc::now();
let delta = (via_clock - via_utc).num_milliseconds().abs();
assert!(
delta < 1000,
"SystemClock::now_datetime vs Utc::now difference must be < 1s, got {delta}ms"
);
}
#[test]
fn test_mock_clock_now_datetime_advance_10s() {
let clock = MockClock::with_instant(Instant::now(), 1_700_000_000);
let before = clock.now_datetime();
clock.advance(Duration::from_secs(10));
let after = clock.now_datetime();
assert_eq!(after - before, chrono::Duration::seconds(10));
}
#[test]
fn test_mock_clock_now_datetime_advance_subsecond() {
let clock = MockClock::with_instant(Instant::now(), 1_700_000_000);
let before = clock.now_datetime();
clock.advance(Duration::from_millis(1500));
let after = clock.now_datetime();
assert_eq!(after - before, chrono::Duration::milliseconds(1500));
}
}