1use std::time::{SystemTime, UNIX_EPOCH};
5
6pub fn utc_timestamp_iso() -> String {
8 fmt_unix_secs(unix_secs_now())
9}
10
11pub fn unix_secs_now() -> u64 {
13 SystemTime::now()
14 .duration_since(UNIX_EPOCH)
15 .unwrap_or_default()
16 .as_secs()
17}
18
19pub fn fmt_unix_secs(s: u64) -> String {
21 let sec = s % 60;
22 let min = (s / 60) % 60;
23 let hour = (s / 3600) % 24;
24 let days = s / 86400;
25 let year_400 = days / 146097;
26 let rem = days % 146097;
27 let year_100 = rem / 36524;
28 let rem = rem % 36524;
29 let year_4 = rem / 1461;
30 let rem = rem % 1461;
31 let year_1 = rem / 365;
32 let year = 1970 + year_400 * 400 + year_100 * 100 + year_4 * 4 + year_1;
33 let doy = rem % 365;
34 let (month, day) = doy_to_md(doy, is_leap(year));
35 format!("{year:04}-{month:02}-{day:02}T{hour:02}:{min:02}:{sec:02}Z")
36}
37
38fn is_leap(y: u64) -> bool {
39 (y.is_multiple_of(4) && !y.is_multiple_of(100)) || y.is_multiple_of(400)
40}
41
42fn doy_to_md(doy: u64, leap: bool) -> (u64, u64) {
43 let days_in_month: [u64; 12] = if leap {
44 [31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
45 } else {
46 [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
47 };
48 let mut rem = doy;
49 for (i, &dim) in days_in_month.iter().enumerate() {
50 if rem < dim {
51 return (i as u64 + 1, rem + 1);
52 }
53 rem -= dim;
54 }
55 (12, 31)
56}
57
58#[cfg(test)]
59mod tests {
60 use super::*;
61
62 #[test]
63 fn epoch_formats_correctly() {
64 assert_eq!(fmt_unix_secs(0), "1970-01-01T00:00:00Z");
65 }
66
67 #[test]
68 fn known_date_formats_correctly() {
69 assert_eq!(fmt_unix_secs(1704067200), "2024-01-01T00:00:00Z");
71 }
72
73 #[test]
74 fn unix_secs_now_is_reasonable() {
75 let t = unix_secs_now();
76 assert!(t > 1_700_000_000, "timestamp must be after 2023");
77 }
78
79 #[test]
80 fn utc_timestamp_iso_parses() {
81 let s = utc_timestamp_iso();
82 assert_eq!(s.len(), 20);
83 assert!(s.ends_with('Z'));
84 }
85}