pacenotes_installer/
utc_time.rs1pub fn unix_now() -> i64 {
5 std::time::SystemTime::now()
6 .duration_since(std::time::UNIX_EPOCH)
7 .map(|elapsed| elapsed.as_secs() as i64)
8 .unwrap_or(0)
9}
10
11pub fn file_stamp(unix_seconds: i64) -> String {
14 let civil = Civil::of(unix_seconds);
15 format!(
16 "{}T{:02}-{:02}-{:02}Z",
17 civil.date(),
18 civil.hours,
19 civil.minutes,
20 civil.seconds
21 )
22}
23
24pub fn iso_stamp(unix_seconds: i64) -> String {
26 let civil = Civil::of(unix_seconds);
27 format!(
28 "{}T{:02}:{:02}:{:02}Z",
29 civil.date(),
30 civil.hours,
31 civil.minutes,
32 civil.seconds
33 )
34}
35
36struct Civil {
37 year: i64,
38 month: i64,
39 day: i64,
40 hours: i64,
41 minutes: i64,
42 seconds: i64,
43}
44
45impl Civil {
46 fn of(unix_seconds: i64) -> Civil {
47 let days = unix_seconds.div_euclid(86_400);
48 let of_day = unix_seconds.rem_euclid(86_400);
49
50 let z = days + 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 month = if mp < 10 { mp + 3 } else { mp - 9 };
57 Civil {
58 year: yoe + era * 400 + i64::from(month <= 2),
59 month,
60 day: doy - (153 * mp + 2) / 5 + 1,
61 hours: of_day / 3600,
62 minutes: (of_day % 3600) / 60,
63 seconds: of_day % 60,
64 }
65 }
66
67 fn date(&self) -> String {
68 format!("{:04}-{:02}-{:02}", self.year, self.month, self.day)
69 }
70}
71
72#[cfg(test)]
73mod tests {
74 #[test]
77 fn stamps_match_the_utc_calendar() {
78 assert_eq!(super::file_stamp(0), "1970-01-01T00-00-00Z");
79 assert_eq!(super::file_stamp(1_761_000_000), "2025-10-20T22-40-00Z");
80 assert_eq!(super::file_stamp(1_709_164_800), "2024-02-29T00-00-00Z");
81 assert_eq!(super::iso_stamp(1_761_000_000), "2025-10-20T22:40:00Z");
82 assert_eq!(super::iso_stamp(1_709_164_799), "2024-02-28T23:59:59Z");
83 }
84
85 #[test]
86 fn file_stamps_sort_chronologically_and_are_legal_on_windows() {
87 let earlier = super::file_stamp(1_761_000_000);
88 let later = super::file_stamp(1_761_000_001);
89 assert!(earlier < later);
90 for forbidden in [':', '/', '\\', '*', '?', '"', '<', '>', '|'] {
91 assert!(!later.contains(forbidden), "{later}");
92 }
93 }
94}