calendrier/
date.rs

1use crate::*;
2
3#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
4pub struct Date {
5    year0: i64,
6    month0: i64,
7    day0: i64,
8}
9
10impl Date {
11    pub fn from_timestamp(timestamp: Timestamp) -> Self {
12        let year0 = ts_to_year0(timestamp.seconds);
13        let seconds_in_year = timestamp.seconds - get_year_start0(year0);
14
15        let month0 = seconds_in_year.div_euclid(SECONDS_PER_MONTH);
16        let seconds_in_month = seconds_in_year.rem_euclid(SECONDS_PER_MONTH);
17
18        let day0 = seconds_in_month.div_euclid(SECONDS_PER_DAY);
19
20        Self {
21            year0,
22            month0,
23            day0,
24        }
25    }
26
27    /// # Panics
28    ///
29    /// Panics if:
30    /// - year is 0,
31    /// - month is not in [1, 13],
32    /// - day is not in [1, 30].
33    pub fn from_ymd(year: i64, month: i64, day: i64) -> Self {
34        let year0 = match year.cmp(&0) {
35            std::cmp::Ordering::Greater => year - 1,
36            std::cmp::Ordering::Less => year,
37            std::cmp::Ordering::Equal => panic!("year cannot be 0"),
38        };
39        assert!((1..=13).contains(&month), "month must be in [1, 13]");
40        assert!((1..=30).contains(&day), "day must be in [1, 30]");
41        let month0 = month - 1;
42        let day0 = day - 1;
43        Self {
44            year0,
45            month0,
46            day0,
47        }
48    }
49
50    /// # Panics
51    ///
52    /// Panics if:
53    /// - month is not in [0, 12],
54    /// - day is not in [0, 29].
55    pub fn from_ymd0(year0: i64, month0: i64, day0: i64) -> Self {
56        assert!((0..=12).contains(&month0), "month0 must be in [0, 12]");
57        assert!((0..=29).contains(&day0), "day0 must be in [0, 29]");
58        Self {
59            year0,
60            month0,
61            day0,
62        }
63    }
64
65    /// Returns the franciade number starting from 0.
66    ///
67    /// A franciade is defined as 4 years, the first franciade ending in year 3.
68    /// It is *not* defined as a period of years ending with a sextile year.
69    /// Not all franciades are `365*4+1` days long.
70    pub fn franciade0(&self) -> i64 {
71        ((self.year0 + 2) / 4) - 1
72    }
73
74    /// Returns the franciade number starting from 1.
75    ///
76    /// A franciade is defined as 4 years, the first franciade ending in year 3.
77    /// It is *not* defined as a period of years ending with a sextile year.
78    /// Not all franciades are `365*4+1` days long.
79    pub fn franciade(&self) -> i64 {
80        let franciade0 = self.franciade0();
81        if franciade0 >= 0 {
82            franciade0 + 1
83        } else {
84            franciade0
85        }
86    }
87
88    /// Returns the year but starting from 0.
89    pub fn year0(&self) -> i64 {
90        self.year0
91    }
92
93    /// Returns the year but starting from 1.
94    pub fn year(&self) -> i64 {
95        if self.year0 >= 0 {
96            self.year0 + 1
97        } else {
98            self.year0
99        }
100    }
101
102    /// Returns the month but starting from 0.
103    /// A 13th month of 5 or 6 days is added at the end of the year.
104    pub fn num_month0(&self) -> i64 {
105        self.month0
106    }
107
108    /// Returns the month, starting from 1.
109    /// A 13th month of 5 or 6 days is added at the end of the year.
110    pub fn num_month(&self) -> i64 {
111        self.num_month0() + 1
112    }
113
114    /// Returns the month.
115    pub fn month(&self) -> Month {
116        Month::from_num0(self.month0)
117    }
118
119    /// Returns the day of the month but starting from 0.
120    pub fn day0(&self) -> i64 {
121        self.day0
122    }
123
124    /// Returns the day of the month, starting from 1.
125    pub fn day(&self) -> i64 {
126        self.day0() + 1
127    }
128
129    /// Returns the name of the day
130    pub fn day_name(&self) -> &'static str {
131        day_name(self.month(), self.day())
132    }
133
134    /// Returns the name of the day preceded by the appropriate article
135    pub fn day_name_with_article(&self) -> &'static str {
136        day_name_with_article(self.month(), self.day())
137    }
138
139    /// Returns the decade but starting from 0.
140    pub fn decade0(&self) -> i64 {
141        self.day0().div_euclid(DAYS_PER_DECADE)
142    }
143
144    /// Returns the decade, starting from 1.
145    pub fn decade(&self) -> i64 {
146        self.decade0() + 1
147    }
148
149    /// Returns the day of the decade, starting from 0.
150    pub fn num_decade_day0(&self) -> i64 {
151        self.day0().rem_euclid(DAYS_PER_DECADE)
152    }
153
154    /// Returns the day of the decade, starting from 1.
155    pub fn num_decade_day(&self) -> i64 {
156        self.num_decade_day0() + 1
157    }
158
159    pub fn decade_day(&self) -> Day {
160        if self.month0 == 12 {
161            Day::Sansculottide(SansculottideDay::from_num0(self.num_decade_day0()))
162        } else {
163            Day::Regular(RegularDay::from_num0(self.num_decade_day0()))
164        }
165    }
166
167    /// Returns the timestamp
168    pub fn timestamp(&self) -> Timestamp {
169        Timestamp {
170            seconds: get_year_start0(self.year0)
171                + self.month0 * SECONDS_PER_MONTH
172                + self.day0 * SECONDS_PER_DAY,
173        }
174    }
175
176    fn fmt_default(&self, f: &mut impl std::io::Write) -> std::io::Result<()> {
177        write!(
178            f,
179            "{} {} {} {}",
180            self.decade_day(),
181            self.day(),
182            self.month(),
183            self.year(),
184        )
185    }
186
187    fn fmt_traditional(&self, f: &mut impl std::io::Write) -> std::io::Result<()> {
188        let mut remaining_years = self.year();
189        let thousand_years = remaining_years.div_euclid(1000);
190        remaining_years -= thousand_years * 1000;
191        let thousand_years = "M".repeat(thousand_years as usize);
192        let five_hundred_years = remaining_years.div_euclid(500);
193        remaining_years -= five_hundred_years * 500;
194        let five_hundred_years = match five_hundred_years {
195            0 => "",
196            1 => "D",
197            _ => unreachable!(),
198        };
199        let hundred_years = remaining_years.div_euclid(100);
200        remaining_years -= hundred_years * 100;
201        let hundred_years = match hundred_years {
202            0 => "",
203            1 => "C",
204            2 => "CC",
205            3 => "CCC",
206            4 => "CD",
207            _ => unreachable!(),
208        };
209        let fifty_years = remaining_years.div_euclid(50);
210        remaining_years -= fifty_years * 50;
211        let fifty_years = match fifty_years {
212            0 => "",
213            1 => "L",
214            _ => unreachable!(),
215        };
216        let ten_years = remaining_years.div_euclid(10);
217        remaining_years -= ten_years * 10;
218        let ten_years = match ten_years {
219            0 => "",
220            1 => "X",
221            2 => "XX",
222            3 => "XXX",
223            4 => "XL",
224            _ => unreachable!(),
225        };
226        let five_years = remaining_years.div_euclid(5);
227        remaining_years -= five_years * 5;
228        let five_years = match five_years {
229            0 => "",
230            1 => "V",
231            _ => unreachable!(),
232        };
233        let one_year = remaining_years;
234        let one_year = match one_year {
235            0 => "",
236            1 => "I",
237            2 => "II",
238            3 => "III",
239            4 => "IV",
240            _ => unreachable!(),
241        };
242
243        write!(
244            f,
245            "{} {} {} an {}{}{}{}{}{}{}",
246            self.decade_day(),
247            self.day(),
248            self.month(),
249            thousand_years,
250            five_hundred_years,
251            hundred_years,
252            fifty_years,
253            ten_years,
254            five_years,
255            one_year
256        )
257    }
258
259    pub fn to_string_default(&self) -> String {
260        let mut s = Vec::new();
261        self.fmt_default(&mut s).unwrap();
262        String::from_utf8(s).unwrap()
263    }
264
265    pub fn to_string_traditional(&self) -> String {
266        let mut s = Vec::new();
267        self.fmt_traditional(&mut s).unwrap();
268        String::from_utf8(s).unwrap()
269    }
270}