use std::time::{SystemTime, UNIX_EPOCH};
pub trait Clock: Send + Sync {
fn now(&self) -> SystemTime;
}
#[derive(Debug, Clone, Copy, Default)]
pub struct SystemClock;
impl Clock for SystemClock {
fn now(&self) -> SystemTime {
SystemTime::now()
}
}
#[cfg(any(test, feature = "fake"))]
#[derive(Debug, Clone, Copy)]
pub struct FixedClock(pub SystemTime);
#[cfg(any(test, feature = "fake"))]
impl Clock for FixedClock {
fn now(&self) -> SystemTime {
self.0
}
}
pub(crate) fn unix_seconds(time: SystemTime) -> u64 {
time.duration_since(UNIX_EPOCH).map_or(0, |d| d.as_secs())
}
#[must_use]
pub fn rfc3339(time: SystemTime) -> String {
let secs = unix_seconds(time);
let (hour, minute, second) = ((secs % 86_400) / 3600, (secs % 3600) / 60, secs % 60);
let (year, month, day) = civil(secs / 86_400);
format!("{year:04}-{month:02}-{day:02}T{hour:02}:{minute:02}:{second:02}Z")
}
fn civil(days: u64) -> (i64, i64, i64) {
let z = days.cast_signed() + 719_468;
let era = z.div_euclid(146_097);
let doe = z.rem_euclid(146_097);
let yoe = (doe - doe / 1460 + doe / 36_524 - doe / 146_096) / 365;
let doy = doe - (365 * yoe + yoe / 4 - yoe / 100);
let mp = (5 * doy + 2) / 153;
let d = doy - (153 * mp + 2) / 5 + 1;
let mo = if mp < 10 { mp + 3 } else { mp - 9 };
let y = yoe + era * 400 + i64::from(mo <= 2);
(y, mo, d)
}
#[cfg(test)]
mod tests {
use std::time::Duration;
use super::*;
#[test]
fn times_read_as_rfc3339_in_utc() {
assert_eq!(rfc3339(UNIX_EPOCH), "1970-01-01T00:00:00Z");
assert_eq!(rfc3339(UNIX_EPOCH + Duration::from_secs(1_792_065_600)), "2026-10-15T12:00:00Z");
assert_eq!(rfc3339(UNIX_EPOCH + Duration::from_secs(951_782_400)), "2000-02-29T00:00:00Z");
assert_eq!(rfc3339(UNIX_EPOCH - Duration::from_secs(5)), "1970-01-01T00:00:00Z");
}
#[test]
fn a_fixed_clock_stands_still() {
let clock = FixedClock(UNIX_EPOCH + Duration::from_secs(60));
assert_eq!(clock.now(), clock.now());
assert_eq!(rfc3339(clock.now()), "1970-01-01T00:01:00Z");
}
}