use std::time::{Duration, SystemTime, UNIX_EPOCH};
use cronoq_core::Time;
use tokio::time::Instant;
const FAR_FUTURE: Duration = Duration::from_secs(100 * 365 * 86_400);
#[derive(Copy, Clone, Debug)]
pub struct Clock {
base: Instant,
unix_nanos_at_base: u64,
}
impl Clock {
pub fn start() -> Clock {
Clock {
base: Instant::now(),
unix_nanos_at_base: unix_nanos(SystemTime::now()),
}
}
#[inline]
pub fn now(&self) -> Time {
let elapsed = self.base.elapsed().as_nanos().min(u64::MAX as u128) as u64;
Time(self.unix_nanos_at_base.saturating_add(elapsed))
}
#[inline]
pub fn instant(&self, t: Time) -> Instant {
let ahead = Duration::from_nanos(t.0.saturating_sub(self.unix_nanos_at_base));
self.base
.checked_add(ahead.min(FAR_FUTURE))
.unwrap_or(self.base)
}
pub fn unix_nanos_at_zero(&self) -> i64 {
0
}
pub fn from_system(&self, t: SystemTime) -> Time {
Time(unix_nanos(t))
}
pub fn to_system(&self, t: Time) -> SystemTime {
to_system(t)
}
}
pub(crate) fn unix_nanos(t: SystemTime) -> u64 {
match t.duration_since(UNIX_EPOCH) {
Ok(d) => d.as_nanos().min(u64::MAX as u128) as u64,
Err(_) => 0,
}
}
pub(crate) fn to_system(t: Time) -> SystemTime {
let d = Duration::from_nanos(t.0);
UNIX_EPOCH.checked_add(d).unwrap_or_else(|| {
let mut years = 584u64;
loop {
if let Some(s) = UNIX_EPOCH.checked_add(Duration::from_secs(years * 365 * 86_400)) {
return s;
}
years /= 2;
}
})
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn conversions_saturate() {
let c = Clock::start();
assert_eq!(
c.from_system(UNIX_EPOCH - Duration::from_secs(10)),
Time::ZERO
);
let far = UNIX_EPOCH + Duration::from_secs(9_300_000_000); let t = c.from_system(far);
assert!(t > c.now());
assert_eq!(c.to_system(t), far);
assert!(c.to_system(Time::MAX) > SystemTime::now());
assert!(c.instant(Time::MAX) > Instant::now() + Duration::from_secs(3600));
assert!(c.instant(Time::ZERO) <= Instant::now());
}
#[test]
fn now_is_unix_time() {
let c = Clock::start();
let wall = unix_nanos(SystemTime::now());
assert!(c.now().0.abs_diff(wall) < 1_000_000_000);
}
}