1use std::time::{SystemTime, UNIX_EPOCH};
9
10#[must_use]
12pub fn now_iso8601() -> String {
13 let secs = SystemTime::now()
14 .duration_since(UNIX_EPOCH)
15 .unwrap_or_default()
16 .as_secs();
17 epoch_secs_to_iso8601(secs)
18}
19
20#[must_use]
26#[expect(
27 clippy::similar_names,
28 reason = "doe/doy are the standard abbreviations in the Hinnant date algorithm"
29)]
30pub fn epoch_secs_to_iso8601(secs: u64) -> String {
31 let sec = secs % 60;
32 let mins = secs / 60;
33 let min = mins % 60;
34 let hours = mins / 60;
35 let hour = hours % 24;
36 let days = hours / 24;
37
38 let z = days + 719_468;
40 let era = z / 146_097;
41 let doe = z - era * 146_097;
42 let yoe = (doe - doe / 1_460 + doe / 36_524 - doe / 146_096) / 365;
43 let y = yoe + era * 400;
44 let doy = doe - (365 * yoe + yoe / 4 - yoe / 100);
45 let mp = (5 * doy + 2) / 153;
46 let d = doy - (153 * mp + 2) / 5 + 1;
47 let m = if mp < 10 { mp + 3 } else { mp - 9 };
48 let y = if m <= 2 { y + 1 } else { y };
49
50 format!("{y:04}-{m:02}-{d:02}T{hour:02}:{min:02}:{sec:02}Z")
51}
52
53#[cfg(test)]
54mod tests {
55 use super::epoch_secs_to_iso8601;
56
57 #[test]
58 fn epoch_secs_to_iso8601_unix_epoch() {
59 assert_eq!(epoch_secs_to_iso8601(0), "1970-01-01T00:00:00Z");
60 }
61
62 #[test]
63 fn epoch_secs_to_iso8601_known_date() {
64 assert_eq!(epoch_secs_to_iso8601(1_775_952_000), "2026-04-12T00:00:00Z");
68 }
69}