1#![allow(dead_code)]
13
14use std::time::SystemTime;
15
16#[derive(Debug, Clone, Copy, Eq, PartialEq)]
18pub struct CivilUtc {
19 pub year: i64,
20 pub month: u64,
21 pub day: u64,
22 pub hour: u64,
23 pub minute: u64,
24 pub second: u64,
25}
26
27impl CivilUtc {
28 pub fn format_date(&self) -> String {
30 format!("{:04}-{:02}-{:02}", self.year, self.month, self.day)
31 }
32
33 pub fn format_iso_utc(&self) -> String {
35 format!(
36 "{:04}-{:02}-{:02}T{:02}:{:02}:{:02}Z",
37 self.year, self.month, self.day, self.hour, self.minute, self.second
38 )
39 }
40}
41
42pub fn now_utc() -> CivilUtc {
47 from_system_time(SystemTime::now())
48}
49
50pub fn from_system_time(t: SystemTime) -> CivilUtc {
51 let duration = t.duration_since(SystemTime::UNIX_EPOCH).unwrap_or_default();
52 from_unix_seconds(duration.as_secs())
53}
54
55pub fn from_unix_seconds(secs: u64) -> CivilUtc {
58 let days = secs / 86_400;
59 let sod = secs % 86_400;
60
61 let z = days as i64 + 719_468;
63 let era = if z >= 0 { z } else { z - 146_096 } / 146_097;
64 let doe = (z - era * 146_097) as u64;
65 let yoe = (doe - doe / 1_460 + doe / 36_524 - doe / 146_096) / 365;
66 let y = (yoe as i64) + era * 400;
67 let doy = doe - (365 * yoe + yoe / 4 - yoe / 100);
68 let mp = (5 * doy + 2) / 153;
69 let d = doy - (153 * mp + 2) / 5 + 1;
70 let m = if mp < 10 { mp + 3 } else { mp - 9 };
71 let y = if m <= 2 { y + 1 } else { y };
72
73 CivilUtc {
74 year: y,
75 month: m,
76 day: d,
77 hour: sod / 3_600,
78 minute: (sod % 3_600) / 60,
79 second: sod % 60,
80 }
81}
82
83#[cfg(test)]
84mod tests {
85 use super::*;
86
87 #[test]
88 fn unix_epoch_is_1970_01_01() {
89 let c = from_unix_seconds(0);
90 assert_eq!(c.format_date(), "1970-01-01");
91 assert_eq!(c.format_iso_utc(), "1970-01-01T00:00:00Z");
92 }
93
94 #[test]
95 fn pinned_midday_instant_formats_correctly() {
96 let c = from_unix_seconds(1_710_506_096);
101 assert_eq!(c.year, 2024);
102 assert_eq!(c.month, 3);
103 assert_eq!(c.day, 15);
104 assert_eq!(c.hour, 12);
105 assert_eq!(c.minute, 34);
106 assert_eq!(c.second, 56);
107 assert_eq!(c.format_iso_utc(), "2024-03-15T12:34:56Z");
108 }
109
110 #[test]
111 fn now_utc_is_plausible_shape() {
112 let s = now_utc().format_iso_utc();
113 assert_eq!(s.len(), 20);
114 assert!(s.ends_with('Z'));
115 assert!(s.contains('T'));
116 }
117}