icu_calendar/cal/
gregorian.rs

1// This file is part of ICU4X. For terms of use, please see the file
2// called LICENSE at the top level of the ICU4X source tree
3// (online at: https://github.com/unicode-org/icu4x/blob/main/LICENSE ).
4
5use crate::cal::abstract_gregorian::{impl_with_abstract_gregorian, GregorianYears};
6use crate::calendar_arithmetic::ArithmeticDate;
7use crate::error::UnknownEraError;
8use crate::preferences::CalendarAlgorithm;
9use crate::{types, Date, DateError, RangeError};
10use tinystr::tinystr;
11
12impl_with_abstract_gregorian!(crate::cal::Gregorian, GregorianDateInner, CeBce, _x, CeBce);
13
14#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
15pub(crate) struct CeBce;
16
17impl GregorianYears for CeBce {
18    fn extended_from_era_year(
19        &self,
20        era: Option<&[u8]>,
21        year: i32,
22    ) -> Result<i32, UnknownEraError> {
23        match era {
24            None => Ok(year),
25            Some(b"ad" | b"ce") => Ok(year),
26            Some(b"bce" | b"bc") => Ok(1 - year),
27            Some(_) => Err(UnknownEraError),
28        }
29    }
30
31    fn era_year_from_extended(&self, extended_year: i32, _month: u8, _day: u8) -> types::EraYear {
32        if extended_year > 0 {
33            types::EraYear {
34                era: tinystr!(16, "ce"),
35                era_index: Some(1),
36                year: extended_year,
37                extended_year,
38                ambiguity: match extended_year {
39                    ..=999 => types::YearAmbiguity::EraAndCenturyRequired,
40                    1000..=1949 => types::YearAmbiguity::CenturyRequired,
41                    1950..=2049 => types::YearAmbiguity::Unambiguous,
42                    2050.. => types::YearAmbiguity::CenturyRequired,
43                },
44            }
45        } else {
46            types::EraYear {
47                era: tinystr!(16, "bce"),
48                era_index: Some(0),
49                year: 1 - extended_year,
50                extended_year,
51                ambiguity: types::YearAmbiguity::EraAndCenturyRequired,
52            }
53        }
54    }
55
56    fn debug_name(&self) -> &'static str {
57        "Gregorian"
58    }
59
60    fn calendar_algorithm(&self) -> Option<CalendarAlgorithm> {
61        Some(CalendarAlgorithm::Gregory)
62    }
63}
64
65/// The [Gregorian Calendar](https://en.wikipedia.org/wiki/Gregorian_calendar)
66///
67/// The Gregorian calendar is an improvement over the [`Julian`](super::Julian) calendar.
68/// It was adopted under Pope Gregory XIII in 1582 CE by much of Roman Catholic Europe,
69/// and over the following centuries by all other countries that had been using
70/// the Julian calendar. Eventually even countries that had been using other calendars
71/// adopted this calendar, and today it is used as the international civil calendar.
72///
73/// This implementation extends proleptically for dates before the calendar's creation.
74///
75/// This corresponds to the `"gregory"` [CLDR calendar](https://unicode.org/reports/tr35/#UnicodeCalendarIdentifier).
76///
77/// # Era codes
78///
79/// This calendar uses two era codes: `bce` (alias `bc`), and `ce` (alias `ad`), corresponding to the BCE and CE eras.
80///
81/// # Months and days
82///
83/// The 12 months are called January (`M01`, 31 days), February (`M02`, 28 days),
84/// March (`M03`, 31 days), April (`M04`, 30 days), May (`M05`, 31 days), June (`M06`, 30 days),
85/// July (`M07`, 31 days), August (`M08`, 31 days), September (`M09`, 30 days),
86/// October (`M10`, 31 days), November (`M11`, 30 days), December (`M12`, 31 days).
87///
88/// In leap years (years divisible by 4 but not 100 except when divisble by 400), February gains a 29th day.
89///
90/// Standard years thus have 365 days, and leap years 366.
91///
92/// # Calendar drift
93///
94/// The Gregorian calendar has an average year length of 365.2425, slightly longer than
95/// the mean solar year, so this calendar drifts 1 day in ~7700 years with respect
96/// to the seasons.
97///
98/// # Historical accuracy
99///
100/// This type implements the [*proleptic* Gregorian calendar](
101/// https://en.wikipedia.org/wiki/Gregorian_calendar#Proleptic_Gregorian_calendar),
102/// with dates before 1582 CE using the rules projected backwards. Care needs to be taken
103/// when intepreting historical dates before or during the transition from the Julian to
104/// the Gregorian calendar. [Some regions](https://en.wikipedia.org/wiki/Adoption_of_the_Gregorian_calendar)
105/// continued using the Julian calendar as late as the 20th century. Sources often
106/// mark dates as "New Style" (Gregorian) or "Old Style" (Julian) if there is ambiguity.
107///
108/// Historically, the Julian/Gregorian calendars were used with a variety of year reckoning
109/// schemes (see [`Julian`](super::Julian) for more detail). The Gregorian calendar has generally
110/// been used with the [Anno Domini](https://en.wikipedia.org/wiki/Anno_Domini)/[Common era](
111/// https://en.wikipedia.org/wiki/Common_Era) since its inception. However, some countries
112/// that have adopted the Gregorian calendar more recently are still using their traditional
113/// year-reckoning schemes. This crate implements some of these as different types, i.e the Thai
114/// [`Buddhist`](super::Buddhist) calendar, the [`Japanese`](super::Japanese) calendar, and the
115/// Chinese Republican Calendar ([`Roc`](super::Roc)).
116#[derive(Copy, Clone, Debug, Default)]
117#[allow(clippy::exhaustive_structs)] // this type is stable
118pub struct Gregorian;
119
120impl Date<Gregorian> {
121    /// Construct a new Gregorian Date.
122    ///
123    /// Years are specified as ISO years.
124    ///
125    /// ```rust
126    /// use icu::calendar::Date;
127    ///
128    /// // Conversion from ISO to Gregorian
129    /// let date_gregorian = Date::try_new_gregorian(1970, 1, 2)
130    ///     .expect("Failed to initialize Gregorian Date instance.");
131    ///
132    /// assert_eq!(date_gregorian.era_year().year, 1970);
133    /// assert_eq!(date_gregorian.month().ordinal, 1);
134    /// assert_eq!(date_gregorian.day_of_month().0, 2);
135    /// ```
136    pub fn try_new_gregorian(year: i32, month: u8, day: u8) -> Result<Date<Gregorian>, RangeError> {
137        ArithmeticDate::new_gregorian::<CeBce>(year, month, day)
138            .map(GregorianDateInner)
139            .map(|i| Date::from_raw(i, Gregorian))
140    }
141}
142
143impl Gregorian {
144    /// Returns the date of Easter in the given year.
145    pub fn easter(year: i32) -> Date<Self> {
146        Date::from_rata_die(calendrical_calculations::gregorian::easter(year), Self)
147    }
148}
149
150#[cfg(test)]
151mod test {
152    use crate::cal::Iso;
153    use calendrical_calculations::rata_die::RataDie;
154
155    use super::*;
156
157    #[derive(Debug)]
158    struct TestCase {
159        rd: RataDie,
160        iso_year: i32,
161        iso_month: u8,
162        iso_day: u8,
163        expected_year: i32,
164        expected_era: &'static str,
165        expected_month: u8,
166        expected_day: u8,
167    }
168
169    fn check_test_case(case: TestCase) {
170        let iso_from_rd = Date::from_rata_die(case.rd, Iso);
171        let greg_date_from_rd = Date::from_rata_die(case.rd, Gregorian);
172        assert_eq!(greg_date_from_rd.era_year().year, case.expected_year,
173            "Failed year check from RD: {case:?}\nISO: {iso_from_rd:?}\nGreg: {greg_date_from_rd:?}");
174        assert_eq!(
175            greg_date_from_rd.era_year().era,
176            case.expected_era,
177            "Failed era check from RD: {case:?}\nISO: {iso_from_rd:?}\nGreg: {greg_date_from_rd:?}"
178        );
179        assert_eq!(greg_date_from_rd.month().ordinal, case.expected_month,
180            "Failed month check from RD: {case:?}\nISO: {iso_from_rd:?}\nGreg: {greg_date_from_rd:?}");
181        assert_eq!(
182            greg_date_from_rd.day_of_month().0,
183            case.expected_day,
184            "Failed day check from RD: {case:?}\nISO: {iso_from_rd:?}\nGreg: {greg_date_from_rd:?}"
185        );
186
187        let iso_date_man: Date<Iso> =
188            Date::try_new_iso(case.iso_year, case.iso_month, case.iso_day)
189                .expect("Failed to initialize ISO date for {case:?}");
190        let greg_date_man: Date<Gregorian> = Date::new_from_iso(iso_date_man, Gregorian);
191        assert_eq!(iso_from_rd, iso_date_man,
192            "ISO from RD not equal to ISO generated from manually-input ymd\nCase: {case:?}\nRD: {iso_from_rd:?}\nMan: {iso_date_man:?}");
193        assert_eq!(greg_date_from_rd, greg_date_man,
194            "Greg. date from RD not equal to Greg. generated from manually-input ymd\nCase: {case:?}\nRD: {greg_date_from_rd:?}\nMan: {greg_date_man:?}");
195    }
196
197    #[test]
198    fn test_gregorian_ce() {
199        // Tests that the Gregorian calendar gives the correct expected
200        // day, month, and year for positive years (AD/CE/gregory era)
201
202        let cases = [
203            TestCase {
204                rd: RataDie::new(1),
205                iso_year: 1,
206                iso_month: 1,
207                iso_day: 1,
208                expected_year: 1,
209                expected_era: "ce",
210                expected_month: 1,
211                expected_day: 1,
212            },
213            TestCase {
214                rd: RataDie::new(181),
215                iso_year: 1,
216                iso_month: 6,
217                iso_day: 30,
218                expected_year: 1,
219                expected_era: "ce",
220                expected_month: 6,
221                expected_day: 30,
222            },
223            TestCase {
224                rd: RataDie::new(1155),
225                iso_year: 4,
226                iso_month: 2,
227                iso_day: 29,
228                expected_year: 4,
229                expected_era: "ce",
230                expected_month: 2,
231                expected_day: 29,
232            },
233            TestCase {
234                rd: RataDie::new(1344),
235                iso_year: 4,
236                iso_month: 9,
237                iso_day: 5,
238                expected_year: 4,
239                expected_era: "ce",
240                expected_month: 9,
241                expected_day: 5,
242            },
243            TestCase {
244                rd: RataDie::new(36219),
245                iso_year: 100,
246                iso_month: 3,
247                iso_day: 1,
248                expected_year: 100,
249                expected_era: "ce",
250                expected_month: 3,
251                expected_day: 1,
252            },
253        ];
254
255        for case in cases {
256            check_test_case(case);
257        }
258    }
259
260    #[test]
261    fn test_gregorian_bce() {
262        // Tests that the Gregorian calendar gives the correct expected
263        // day, month, and year for negative years (BC/BCE era)
264
265        let cases = [
266            TestCase {
267                rd: RataDie::new(0),
268                iso_year: 0,
269                iso_month: 12,
270                iso_day: 31,
271                expected_year: 1,
272                expected_era: "bce",
273                expected_month: 12,
274                expected_day: 31,
275            },
276            TestCase {
277                rd: RataDie::new(-365), // This is a leap year
278                iso_year: 0,
279                iso_month: 1,
280                iso_day: 1,
281                expected_year: 1,
282                expected_era: "bce",
283                expected_month: 1,
284                expected_day: 1,
285            },
286            TestCase {
287                rd: RataDie::new(-366),
288                iso_year: -1,
289                iso_month: 12,
290                iso_day: 31,
291                expected_year: 2,
292                expected_era: "bce",
293                expected_month: 12,
294                expected_day: 31,
295            },
296            TestCase {
297                rd: RataDie::new(-1461),
298                iso_year: -4,
299                iso_month: 12,
300                iso_day: 31,
301                expected_year: 5,
302                expected_era: "bce",
303                expected_month: 12,
304                expected_day: 31,
305            },
306            TestCase {
307                rd: RataDie::new(-1826),
308                iso_year: -4,
309                iso_month: 1,
310                iso_day: 1,
311                expected_year: 5,
312                expected_era: "bce",
313                expected_month: 1,
314                expected_day: 1,
315            },
316        ];
317
318        for case in cases {
319            check_test_case(case);
320        }
321    }
322
323    #[test]
324    fn check_gregorian_directionality() {
325        // Tests that for a large range of RDs, if a RD
326        // is less than another, the corresponding YMD should also be less
327        // than the other, without exception.
328        for i in -100..100 {
329            for j in -100..100 {
330                let iso_i = Date::from_rata_die(RataDie::new(i), Iso);
331                let iso_j = Date::from_rata_die(RataDie::new(j), Iso);
332
333                let greg_i = iso_i.to_calendar(Gregorian);
334                let greg_j = iso_j.to_calendar(Gregorian);
335
336                assert_eq!(
337                    i.cmp(&j),
338                    iso_i.cmp(&iso_j),
339                    "ISO directionality inconsistent with directionality for i: {i}, j: {j}"
340                );
341                assert_eq!(
342                    i.cmp(&j),
343                    greg_i.cmp(&greg_j),
344                    "Gregorian directionality inconsistent with directionality for i: {i}, j: {j}"
345                );
346            }
347        }
348    }
349}