Skip to main content

allow_core/
date.rs

1use std::fmt;
2use std::time::{SystemTime, UNIX_EPOCH};
3
4#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
5pub struct SimpleDate {
6    pub year: i32,
7    pub month: u32,
8    pub day: u32,
9}
10
11impl SimpleDate {
12    pub fn parse(input: &str) -> Option<Self> {
13        let mut parts = input.trim().split('-');
14        let year = parts.next()?.parse().ok()?;
15        let month = parts.next()?.parse().ok()?;
16        let day = parts.next()?.parse().ok()?;
17        if parts.next().is_some() || !valid_ymd(year, month, day) {
18            return None;
19        }
20        Some(Self { year, month, day })
21    }
22
23    pub fn days_until(self, other: Self) -> i64 {
24        other.days_since_unix_epoch() - self.days_since_unix_epoch()
25    }
26
27    pub fn add_days(self, days: i64) -> Self {
28        Self::from_days_since_unix_epoch(self.days_since_unix_epoch() + days)
29    }
30
31    pub(crate) fn days_since_unix_epoch(self) -> i64 {
32        // Howard Hinnant's civil date algorithm. This keeps lifecycle validation
33        // deterministic without adding a date dependency to the core crate.
34        let mut year = i64::from(self.year);
35        let month = i64::from(self.month);
36        let day = i64::from(self.day);
37        if month <= 2 {
38            year -= 1;
39        }
40        let era = if year >= 0 { year } else { year - 399 } / 400;
41        let year_of_era = year - era * 400;
42        let month_prime = month + if month > 2 { -3 } else { 9 };
43        let day_of_year = (153 * month_prime + 2) / 5 + day - 1;
44        let day_of_era = year_of_era * 365 + year_of_era / 4 - year_of_era / 100 + day_of_year;
45        era * 146_097 + day_of_era - 719_468
46    }
47
48    pub(crate) fn from_days_since_unix_epoch(days: i64) -> Self {
49        // Inverse of the civil date algorithm above.
50        let z = days + 719_468;
51        let era = if z >= 0 { z } else { z - 146_096 } / 146_097;
52        let day_of_era = z - era * 146_097;
53        let year_of_era =
54            (day_of_era - day_of_era / 1460 + day_of_era / 36_524 - day_of_era / 146_096) / 365;
55        let mut year = year_of_era + era * 400;
56        let day_of_year = day_of_era - (365 * year_of_era + year_of_era / 4 - year_of_era / 100);
57        let month_prime = (5 * day_of_year + 2) / 153;
58        let day = day_of_year - (153 * month_prime + 2) / 5 + 1;
59        let month = month_prime + if month_prime < 10 { 3 } else { -9 };
60        if month <= 2 {
61            year += 1;
62        }
63        Self {
64            year: year as i32,
65            month: month as u32,
66            day: day as u32,
67        }
68    }
69
70    pub fn today_utc_approx() -> Self {
71        let seconds = SystemTime::now()
72            .duration_since(UNIX_EPOCH)
73            .map(|duration| duration.as_secs())
74            .unwrap_or(0);
75        Self::from_days_since_unix_epoch((seconds / 86_400) as i64)
76    }
77}
78
79fn valid_ymd(year: i32, month: u32, day: u32) -> bool {
80    let max_day = match month {
81        1 | 3 | 5 | 7 | 8 | 10 | 12 => 31,
82        4 | 6 | 9 | 11 => 30,
83        2 if leap_year(year) => 29,
84        2 => 28,
85        _ => return false,
86    };
87    day > 0 && day <= max_day
88}
89
90fn leap_year(year: i32) -> bool {
91    (year % 4 == 0 && year % 100 != 0) || year % 400 == 0
92}
93
94impl fmt::Display for SimpleDate {
95    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
96        write!(f, "{:04}-{:02}-{:02}", self.year, self.month, self.day)
97    }
98}