1use std::time::{SystemTime, UNIX_EPOCH};
4
5pub trait Clock: Send + Sync {
7 fn now(&self) -> SystemTime;
9}
10
11#[derive(Debug, Clone, Copy, Default)]
13pub struct SystemClock;
14
15impl Clock for SystemClock {
16 fn now(&self) -> SystemTime {
17 SystemTime::now()
18 }
19}
20
21#[cfg(any(test, feature = "fake"))]
23#[derive(Debug, Clone, Copy)]
24pub struct FixedClock(pub SystemTime);
25
26#[cfg(any(test, feature = "fake"))]
27impl Clock for FixedClock {
28 fn now(&self) -> SystemTime {
29 self.0
30 }
31}
32
33pub(crate) fn unix_seconds(time: SystemTime) -> u64 {
35 time.duration_since(UNIX_EPOCH).map_or(0, |d| d.as_secs())
36}
37
38#[must_use]
40pub fn rfc3339(time: SystemTime) -> String {
41 let secs = unix_seconds(time);
42 let (hour, minute, second) = ((secs % 86_400) / 3600, (secs % 3600) / 60, secs % 60);
43 let (year, month, day) = civil(secs / 86_400);
44 format!("{year:04}-{month:02}-{day:02}T{hour:02}:{minute:02}:{second:02}Z")
45}
46
47fn civil(days: u64) -> (i64, i64, i64) {
50 let z = days.cast_signed() + 719_468;
51 let era = z.div_euclid(146_097);
52 let doe = z.rem_euclid(146_097);
53 let yoe = (doe - doe / 1460 + doe / 36_524 - doe / 146_096) / 365;
54 let doy = doe - (365 * yoe + yoe / 4 - yoe / 100);
55 let mp = (5 * doy + 2) / 153;
56 let d = doy - (153 * mp + 2) / 5 + 1;
57 let mo = if mp < 10 { mp + 3 } else { mp - 9 };
58 let y = yoe + era * 400 + i64::from(mo <= 2);
59 (y, mo, d)
60}
61
62#[cfg(test)]
63mod tests {
64 use std::time::Duration;
65
66 use super::*;
67
68 #[test]
69 fn times_read_as_rfc3339_in_utc() {
70 assert_eq!(rfc3339(UNIX_EPOCH), "1970-01-01T00:00:00Z");
71 assert_eq!(rfc3339(UNIX_EPOCH + Duration::from_secs(1_792_065_600)), "2026-10-15T12:00:00Z");
72 assert_eq!(rfc3339(UNIX_EPOCH + Duration::from_secs(951_782_400)), "2000-02-29T00:00:00Z");
73 assert_eq!(rfc3339(UNIX_EPOCH - Duration::from_secs(5)), "1970-01-01T00:00:00Z");
74 }
75
76 #[test]
77 fn a_fixed_clock_stands_still() {
78 let clock = FixedClock(UNIX_EPOCH + Duration::from_secs(60));
79 assert_eq!(clock.now(), clock.now());
80 assert_eq!(rfc3339(clock.now()), "1970-01-01T00:01:00Z");
81 }
82}