use std::sync::LazyLock;
use std::time::{Duration, SystemTime, UNIX_EPOCH};
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub struct MonotonicInstant(u128);
impl MonotonicInstant {
pub const fn from_nanos(nanos: u128) -> Self {
MonotonicInstant(nanos)
}
pub fn now() -> Self {
static EPOCH: LazyLock<std::time::Instant> = LazyLock::new(std::time::Instant::now);
let elapsed = EPOCH.elapsed();
MonotonicInstant(elapsed.as_nanos())
}
pub fn duration_since(&self, earlier: MonotonicInstant) -> Duration {
Duration::from_nanos(self.0.saturating_sub(earlier.0) as u64)
}
pub fn checked_add(&self, duration: Duration) -> Option<MonotonicInstant> {
self.0
.checked_add(duration.as_nanos())
.map(MonotonicInstant)
}
pub fn checked_sub(&self, duration: Duration) -> Option<MonotonicInstant> {
self.0
.checked_sub(duration.as_nanos())
.map(MonotonicInstant)
}
}
impl std::ops::Add<Duration> for MonotonicInstant {
type Output = MonotonicInstant;
fn add(self, rhs: Duration) -> Self::Output {
MonotonicInstant(self.0 + rhs.as_nanos())
}
}
impl std::ops::Sub<Duration> for MonotonicInstant {
type Output = MonotonicInstant;
fn sub(self, rhs: Duration) -> Self::Output {
MonotonicInstant(self.0 - rhs.as_nanos())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub struct WallClockInstant {
pub secs: i64,
pub micros: u32,
}
const MICROS_PER_SEC: u32 = 1_000_000;
impl WallClockInstant {
pub fn now() -> Self {
let duration = SystemTime::now()
.duration_since(UNIX_EPOCH)
.expect("system time before Unix epoch");
WallClockInstant {
secs: duration.as_secs() as i64,
micros: duration.subsec_micros(),
}
}
pub fn to_system_time(self) -> SystemTime {
if self.secs >= 0 {
UNIX_EPOCH + Duration::new(self.secs as u64, self.micros * 1000)
} else {
let positive_secs = (-self.secs) as u64;
if self.micros > 0 {
let nanos_to_subtract = (1_000_000 - self.micros) * 1000;
UNIX_EPOCH - Duration::new(positive_secs - 1, nanos_to_subtract)
} else {
UNIX_EPOCH - Duration::new(positive_secs, 0)
}
}
}
pub fn checked_add_duration(&self, other: &Duration) -> Option<WallClockInstant> {
let mut secs = self.secs.checked_add_unsigned(other.as_secs())?;
let mut micros = other.subsec_micros() + self.micros;
if micros >= MICROS_PER_SEC {
micros -= MICROS_PER_SEC;
secs = secs.checked_add(1)?;
}
Some(Self { secs, micros })
}
pub fn checked_sub_duration(&self, other: &Duration) -> Option<WallClockInstant> {
let mut secs = self.secs.checked_sub_unsigned(other.as_secs())?;
let mut micros = self.micros as i32 - other.subsec_micros() as i32;
if micros < 0 {
micros += MICROS_PER_SEC as i32;
secs = secs.checked_sub(1)?;
}
Some(Self {
secs,
micros: micros as u32,
})
}
}
impl std::ops::Add<Duration> for WallClockInstant {
type Output = WallClockInstant;
fn add(self, rhs: Duration) -> Self::Output {
self.checked_add_duration(&rhs)
.expect("duration addition overflow")
}
}
impl std::ops::Sub<Duration> for WallClockInstant {
type Output = WallClockInstant;
fn sub(self, rhs: Duration) -> Self::Output {
self.checked_sub_duration(&rhs)
.expect("duration subtraction underflow")
}
}
impl<T: chrono::TimeZone> From<chrono::DateTime<T>> for WallClockInstant {
fn from(value: chrono::DateTime<T>) -> Self {
WallClockInstant {
secs: value.timestamp(),
micros: value.timestamp_subsec_micros(),
}
}
}
pub trait Clock {
fn current_time_monotonic(&self) -> MonotonicInstant;
fn current_time_wall_clock(&self) -> WallClockInstant;
}
pub struct DefaultClock;
impl Clock for DefaultClock {
fn current_time_monotonic(&self) -> MonotonicInstant {
MonotonicInstant::now()
}
fn current_time_wall_clock(&self) -> WallClockInstant {
WallClockInstant::now()
}
}