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}