use std::time::Duration as StdDuration;
use chrono::{
DateTime,
Duration,
Utc,
};
use parking_lot::{
Mutex,
MutexGuard,
};
use std::sync::Arc;
use crate::{
Clock,
ControllableClock,
MockTimeError,
MockTimeline,
NanoClock,
};
const NANOS_PER_MILLISECOND: i128 = 1_000_000;
const NANOS_PER_SECOND: i128 = 1_000_000_000;
#[derive(Debug, Clone)]
pub struct MockClock {
timeline: MockTimeline,
anchor: Arc<Mutex<MockClockAnchor>>,
}
#[derive(Debug)]
struct MockClockAnchor {
initial_wall_origin_nanos: i128,
wall_origin_nanos: i128,
}
impl MockClock {
#[must_use]
pub fn new() -> Self {
Self::at(Utc::now())
}
#[must_use]
pub fn at(start: DateTime<Utc>) -> Self {
Self::with_timeline(start, MockTimeline::new())
}
#[must_use]
pub fn with_timeline(start: DateTime<Utc>, timeline: MockTimeline) -> Self {
let wall_origin_nanos = datetime_to_nanos(start).saturating_sub(timeline_elapsed_i128(&timeline));
Self {
timeline,
anchor: Arc::new(Mutex::new(MockClockAnchor {
initial_wall_origin_nanos: wall_origin_nanos,
wall_origin_nanos,
})),
}
}
#[inline]
pub fn timeline(&self) -> MockTimeline {
self.timeline.clone()
}
#[inline]
pub fn advance(&self, duration: StdDuration) {
self.timeline.advance(duration);
}
pub fn try_reset(&self) -> Result<(), MockTimeError> {
self.timeline.reset()?;
self.reset_wall_anchor();
Ok(())
}
pub(crate) fn reset_wall_anchor(&self) {
let mut anchor = self.lock_anchor();
anchor.wall_origin_nanos = anchor.initial_wall_origin_nanos;
}
fn current_nanos(&self) -> i128 {
let anchor = self.lock_anchor();
anchor
.wall_origin_nanos
.saturating_add(timeline_elapsed_i128(&self.timeline))
}
fn lock_anchor(&self) -> MutexGuard<'_, MockClockAnchor> {
self.anchor.lock()
}
}
impl Default for MockClock {
fn default() -> Self {
Self::new()
}
}
impl Clock for MockClock {
fn millis(&self) -> i64 {
millis_from_nanos(self.current_nanos())
}
fn time(&self) -> DateTime<Utc> {
datetime_from_nanos(self.current_nanos())
}
}
impl NanoClock for MockClock {
fn nanos(&self) -> i128 {
self.current_nanos()
}
}
impl ControllableClock for MockClock {
fn set_time(&self, instant: DateTime<Utc>) {
let mut anchor = self.lock_anchor();
anchor.wall_origin_nanos = datetime_to_nanos(instant).saturating_sub(timeline_elapsed_i128(&self.timeline));
}
fn add_duration(&self, duration: Duration) {
let duration = duration
.to_std()
.expect("mock time can only be advanced by a non-negative duration");
self.timeline.advance(duration);
}
fn reset(&self) {
self.try_reset()
.expect("mock clock reset should not run with active waiters");
}
}
fn datetime_to_nanos(instant: DateTime<Utc>) -> i128 {
(instant.timestamp() as i128)
.saturating_mul(NANOS_PER_SECOND)
.saturating_add(instant.timestamp_subsec_nanos() as i128)
}
fn datetime_from_nanos(nanos: i128) -> DateTime<Utc> {
let seconds = nanos.div_euclid(NANOS_PER_SECOND);
let sub_nanos = nanos.rem_euclid(NANOS_PER_SECOND) as u32;
let seconds = match i64::try_from(seconds) {
Ok(seconds) => seconds,
Err(_) if nanos < 0 => return DateTime::<Utc>::MIN_UTC,
Err(_) => return DateTime::<Utc>::MAX_UTC,
};
DateTime::from_timestamp(seconds, sub_nanos).unwrap_or({
if nanos < 0 {
DateTime::<Utc>::MIN_UTC
} else {
DateTime::<Utc>::MAX_UTC
}
})
}
fn millis_from_nanos(nanos: i128) -> i64 {
let millis = nanos.div_euclid(NANOS_PER_MILLISECOND);
match i64::try_from(millis) {
Ok(value) => value,
Err(_) if millis < 0 => i64::MIN,
Err(_) => i64::MAX,
}
}
fn timeline_elapsed_i128(timeline: &MockTimeline) -> i128 {
i128::try_from(timeline.elapsed_nanos()).unwrap_or(i128::MAX)
}