Skip to main content

omne_cli/
clock.rs

1//! Wall-clock helpers — civil calendar without a `chrono` dependency.
2//!
3//! `omne-cli` needs two stamp formats: `YYYY-MM-DD` for the volume
4//! README (`omne.md` frontmatter) and `YYYY-MM-DDTHH:MM:SSZ` for every
5//! entry in `events.jsonl`. Both are cheap if you have Howard Hinnant's
6//! public-domain civil-calendar algorithm, and we already do — but
7//! Unit 11 duplicated the 15-line arithmetic into `executor.rs` without
8//! sharing with `commands::init::chrono_today`. This module is the
9//! single implementation; the two callers compose the format string
10//! they need from the returned parts.
11
12#![allow(dead_code)]
13
14use std::time::SystemTime;
15
16/// Broken-down UTC date/time from a Unix timestamp.
17#[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    /// `YYYY-MM-DD`.
29    pub fn format_date(&self) -> String {
30        format!("{:04}-{:02}-{:02}", self.year, self.month, self.day)
31    }
32
33    /// `YYYY-MM-DDTHH:MM:SSZ`.
34    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
42/// Current UTC instant as a [`CivilUtc`]. Falls back to the Unix epoch
43/// if the system clock is before `1970-01-01` (no `Result` — a VM with
44/// a broken clock should see `1970-01-01T00:00:00Z`, which is loud
45/// enough to be noticed).
46pub 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
55/// Decompose a Unix second count into civil UTC parts. Public for
56/// deterministic unit tests that pin a specific instant.
57pub fn from_unix_seconds(secs: u64) -> CivilUtc {
58    let days = secs / 86_400;
59    let sod = secs % 86_400;
60
61    // Howard Hinnant civil calendar (public domain).
62    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        // 2024-03-15T12:34:56Z = 1_710_506_096 seconds since epoch.
97        // Derivation: 2024-01-01T00:00Z = 1_704_067_200s;
98        //   +31 (Jan) +29 (Feb leap) +14 (Mar pre-15) = 74 days = 6_393_600s;
99        //   +12h34m56s = 45_296s; total = 1_710_506_096s.
100        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}