use crate::Deadline;
#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub struct Now {
pub monotonic_ms: u64,
pub unix_seconds: Option<u64>,
}
impl Now {
pub const MAX_MONOTONIC_MS: u64 = u64::MAX - 1;
pub const fn from_millis(monotonic_ms: u64) -> Self {
Self {
monotonic_ms,
unix_seconds: None,
}
}
pub const fn new(monotonic_ms: u64, unix_seconds: u64) -> Self {
Self {
monotonic_ms,
unix_seconds: Some(unix_seconds),
}
}
pub const fn with_unix_seconds(self, unix_seconds: u64) -> Self {
Self {
unix_seconds: Some(unix_seconds),
..self
}
}
pub const fn saturating_millis_since(self, earlier: Self) -> u64 {
self.monotonic_ms.saturating_sub(earlier.monotonic_ms)
}
pub const fn deadline_after(self, millis: u64) -> Deadline {
Deadline::from_millis(self.monotonic_ms.saturating_add(millis))
}
pub const fn as_deadline(self) -> Deadline {
Deadline::from_millis(self.monotonic_ms)
}
}
pub trait Clock {
fn now(&mut self) -> Now;
}
impl<C: Clock + ?Sized> Clock for &mut C {
fn now(&mut self) -> Now {
(**self).now()
}
}
#[cfg(test)]
mod tests {
use super::*;
use alloc::boxed::Box;
#[test]
fn from_millis_has_no_wall_clock() {
let now = Now::from_millis(42);
assert_eq!(now.monotonic_ms, 42);
assert_eq!(now.unix_seconds, None);
}
#[test]
fn with_unix_seconds_preserves_monotonic() {
let now = Now::from_millis(42).with_unix_seconds(1_700_000_000);
assert_eq!(now.monotonic_ms, 42);
assert_eq!(now.unix_seconds, Some(1_700_000_000));
assert_eq!(now, Now::new(42, 1_700_000_000));
}
#[test]
fn elapsed_saturates_instead_of_wrapping() {
let earlier = Now::from_millis(100);
let later = Now::from_millis(250);
assert_eq!(later.saturating_millis_since(earlier), 150);
assert_eq!(earlier.saturating_millis_since(later), 0);
}
#[test]
fn deadline_after_saturates_at_never() {
let now = Now::from_millis(10);
assert_eq!(now.deadline_after(5), Deadline::from_millis(15));
assert_eq!(now.deadline_after(u64::MAX), Deadline::NEVER);
assert_eq!(now.as_deadline(), Deadline::from_millis(10));
}
#[test]
fn every_permitted_sample_converts_to_a_deadline_that_is_due() {
for millis in [0, 1, 1_000_000, u64::MAX / 2, Now::MAX_MONOTONIC_MS] {
let now = Now::from_millis(millis);
assert!(
now.as_deadline().is_expired_at(now),
"as_deadline was not due at {now:?}"
);
}
assert_eq!(Now::MAX_MONOTONIC_MS, u64::MAX - 1);
assert_eq!(Now::from_millis(u64::MAX).as_deadline(), Deadline::NEVER);
}
struct Fake(u64);
impl Clock for Fake {
fn now(&mut self) -> Now {
self.0 += 1;
Now::from_millis(self.0)
}
}
fn sample<C: Clock>(mut clock: C) -> Now {
clock.now()
}
#[test]
fn mutable_reference_forwards_to_inner_clock() {
let mut fake = Fake(0);
assert_eq!(sample(&mut fake).monotonic_ms, 1);
assert_eq!(sample(&mut fake).monotonic_ms, 2);
assert_eq!(fake.0, 2);
}
#[test]
fn trait_is_object_safe() {
let mut clock: Box<dyn Clock> = Box::new(Fake(7));
assert_eq!(clock.now().monotonic_ms, 8);
}
}