1use std::time::{Duration, SystemTime, UNIX_EPOCH};
2
3pub trait Clock: Send + Sync {
4 fn now_unix_ms(&self) -> u64;
5 fn sleep(&self, d: Duration);
6}
7
8pub struct SystemClock;
9
10impl Clock for SystemClock {
11 fn now_unix_ms(&self) -> u64 {
12 SystemTime::now()
13 .duration_since(UNIX_EPOCH)
14 .map(|d| d.as_millis() as u64)
15 .unwrap_or(0)
16 }
17
18 fn sleep(&self, d: Duration) {
19 std::thread::sleep(d);
20 }
21}
22
23pub fn rfc3339_millis(unix_ms: u64) -> String {
31 let secs = unix_ms / 1000;
32 let millis = unix_ms % 1000;
33 let days = (secs / 86_400) as i64;
34 let secs_of_day = secs % 86_400;
35
36 let z = days + 719_468;
38 let era = z.div_euclid(146_097);
39 let doe = z - era * 146_097;
40 let yoe = (doe - doe / 1460 + doe / 36_524 - doe / 146_096) / 365;
41 let y = yoe + era * 400;
42 let doy = doe - (365 * yoe + yoe / 4 - yoe / 100);
43 let mp = (5 * doy + 2) / 153;
44 let d = doy - (153 * mp + 2) / 5 + 1;
45 let m = if mp < 10 { mp + 3 } else { mp - 9 };
46 let y = if m <= 2 { y + 1 } else { y };
47
48 format!(
49 "{:04}-{:02}-{:02}T{:02}:{:02}:{:02}.{:03}Z",
50 y,
51 m,
52 d,
53 secs_of_day / 3600,
54 (secs_of_day % 3600) / 60,
55 secs_of_day % 60,
56 millis
57 )
58}
59
60#[cfg(test)]
61mod tests {
62 use super::*;
63
64 #[test]
65 fn epoch_formats_with_zero_millis() {
66 assert_eq!(rfc3339_millis(0), "1970-01-01T00:00:00.000Z");
67 }
68
69 #[test]
70 fn whole_seconds_gain_zero_millis() {
71 assert_eq!(
73 rfc3339_millis(1_758_672_000_000),
74 "2025-09-24T00:00:00.000Z"
75 );
76 }
77
78 #[test]
79 fn sub_second_millis_survive() {
80 assert_eq!(
81 rfc3339_millis(1_758_672_000_123),
82 "2025-09-24T00:00:00.123Z"
83 );
84 }
85
86 #[test]
87 fn leap_day_and_day_rollover() {
88 assert_eq!(
90 rfc3339_millis(1_709_164_800_000),
91 "2024-02-29T00:00:00.000Z"
92 );
93 assert_eq!(
94 rfc3339_millis(1_709_251_200_000),
95 "2024-03-01T00:00:00.000Z"
96 );
97 }
98}