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 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 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}