icu_calendar/cal/
buddhist.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::error::UnknownEraError;
6use crate::preferences::CalendarAlgorithm;
7use crate::{
8    cal::abstract_gregorian::{impl_with_abstract_gregorian, GregorianYears},
9    calendar_arithmetic::ArithmeticDate,
10    types, Date, DateError, RangeError,
11};
12use tinystr::tinystr;
13
14#[derive(Copy, Clone, Debug, Default)]
15/// The [Thai Solar Buddhist Calendar](https://en.wikipedia.org/wiki/Thai_solar_calendar)
16///
17/// The Thai Solar Buddhist Calendar is a variant of the [`Gregorian`](crate::cal::Gregorian) calendar
18/// created by the Thai government. It is identical to the Gregorian calendar except that is uses
19/// the Buddhist Era (-543 CE) instead of the Common Era.
20///
21/// This implementation extends proleptically for dates before the calendar's creation
22/// in 2484 BE (1941 CE).
23///
24/// This corresponds to the `"buddhist"` [CLDR calendar](https://unicode.org/reports/tr35/#UnicodeCalendarIdentifier).
25///
26/// # Era codes
27///
28/// This calendar uses a single era code `be`, with 1 Buddhist Era being 543 BCE. Dates before this era use negative years.
29#[allow(clippy::exhaustive_structs)] // this type is stable
30pub struct Buddhist;
31
32impl_with_abstract_gregorian!(
33    crate::cal::Buddhist,
34    BuddhistDateInner,
35    BuddhistEra,
36    _x,
37    BuddhistEra
38);
39
40#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
41pub(crate) struct BuddhistEra;
42
43impl GregorianYears for BuddhistEra {
44    const EXTENDED_YEAR_OFFSET: i32 = -543;
45
46    fn extended_from_era_year(
47        &self,
48        era: Option<&[u8]>,
49        year: i32,
50    ) -> Result<i32, UnknownEraError> {
51        match era {
52            Some(b"be") | None => Ok(year),
53            _ => Err(UnknownEraError),
54        }
55    }
56
57    fn era_year_from_extended(&self, extended_year: i32, _month: u8, _day: u8) -> types::EraYear {
58        types::EraYear {
59            era: tinystr!(16, "be"),
60            era_index: Some(0),
61            year: extended_year,
62            extended_year,
63            ambiguity: types::YearAmbiguity::CenturyRequired,
64        }
65    }
66
67    fn debug_name(&self) -> &'static str {
68        "Buddhist"
69    }
70
71    fn calendar_algorithm(&self) -> Option<CalendarAlgorithm> {
72        Some(CalendarAlgorithm::Buddhist)
73    }
74}
75
76impl Date<Buddhist> {
77    /// Construct a new Buddhist Date.
78    ///
79    /// Years are specified as BE years.
80    ///
81    /// ```rust
82    /// use icu::calendar::Date;
83    ///
84    /// let date_buddhist = Date::try_new_buddhist(1970, 1, 2)
85    ///     .expect("Failed to initialize Buddhist Date instance.");
86    ///
87    /// assert_eq!(date_buddhist.era_year().year, 1970);
88    /// assert_eq!(date_buddhist.month().ordinal, 1);
89    /// assert_eq!(date_buddhist.day_of_month().0, 2);
90    /// ```
91    pub fn try_new_buddhist(year: i32, month: u8, day: u8) -> Result<Date<Buddhist>, RangeError> {
92        ArithmeticDate::new_gregorian::<BuddhistEra>(year, month, day)
93            .map(BuddhistDateInner)
94            .map(|i| Date::from_raw(i, Buddhist))
95    }
96}
97
98#[cfg(test)]
99mod test {
100    use crate::cal::Iso;
101    use calendrical_calculations::rata_die::RataDie;
102
103    use super::*;
104
105    #[test]
106    fn test_buddhist_roundtrip_near_rd_zero() {
107        for i in -10000..=10000 {
108            let rd = RataDie::new(i);
109            let iso1 = Date::from_rata_die(rd, Iso);
110            let buddhist = iso1.to_calendar(Buddhist);
111            let iso2 = buddhist.to_calendar(Iso);
112            let result = iso2.to_rata_die();
113            assert_eq!(rd, result);
114        }
115    }
116
117    #[test]
118    fn test_buddhist_roundtrip_near_epoch() {
119        // Buddhist epoch start RD: -198326
120        for i in -208326..=-188326 {
121            let rd = RataDie::new(i);
122            let iso1 = Date::from_rata_die(rd, Iso);
123            let buddhist = iso1.to_calendar(Buddhist);
124            let iso2 = buddhist.to_calendar(Iso);
125            let result = iso2.to_rata_die();
126            assert_eq!(rd, result);
127        }
128    }
129
130    #[test]
131    fn test_buddhist_directionality_near_rd_zero() {
132        for i in -100..=100 {
133            for j in -100..=100 {
134                let iso_i = Date::from_rata_die(RataDie::new(i), Iso);
135                let iso_j = Date::from_rata_die(RataDie::new(j), Iso);
136
137                let buddhist_i = Date::new_from_iso(iso_i, Buddhist);
138                let buddhist_j = Date::new_from_iso(iso_j, Buddhist);
139
140                assert_eq!(
141                    i.cmp(&j),
142                    iso_i.cmp(&iso_j),
143                    "ISO directionality inconsistent with directionality for i: {i}, j: {j}"
144                );
145
146                assert_eq!(
147                    i.cmp(&j),
148                    buddhist_i.cmp(&buddhist_j),
149                    "Buddhist directionality inconsistent with directionality for i: {i}, j: {j}"
150                );
151            }
152        }
153    }
154
155    #[test]
156    fn test_buddhist_directionality_near_epoch() {
157        // Buddhist epoch start RD: -198326
158        for i in -198426..=-198226 {
159            for j in -198426..=-198226 {
160                let iso_i = Date::from_rata_die(RataDie::new(i), Iso);
161                let iso_j = Date::from_rata_die(RataDie::new(j), Iso);
162
163                let buddhist_i = Date::new_from_iso(iso_i, Buddhist);
164                let buddhist_j = Date::new_from_iso(iso_j, Buddhist);
165
166                assert_eq!(
167                    i.cmp(&j),
168                    iso_i.cmp(&iso_j),
169                    "ISO directionality inconsistent with directionality for i: {i}, j: {j}"
170                );
171
172                assert_eq!(
173                    i.cmp(&j),
174                    buddhist_i.cmp(&buddhist_j),
175                    "Buddhist directionality inconsistent with directionality for i: {i}, j: {j}"
176                );
177            }
178        }
179    }
180
181    #[derive(Debug)]
182    struct TestCase {
183        iso_year: i32,
184        iso_month: u8,
185        iso_day: u8,
186        buddhist_year: i32,
187        buddhist_month: u8,
188        buddhist_day: u8,
189    }
190
191    fn check_test_case(case: TestCase) {
192        let iso_year = case.iso_year;
193        let iso_month = case.iso_month;
194        let iso_day = case.iso_day;
195        let buddhist_year = case.buddhist_year;
196        let buddhist_month = case.buddhist_month;
197        let buddhist_day = case.buddhist_day;
198
199        let iso1 = Date::try_new_iso(iso_year, iso_month, iso_day).unwrap();
200        let buddhist1 = iso1.to_calendar(Buddhist);
201        assert_eq!(
202            buddhist1.era_year().year,
203            buddhist_year,
204            "Iso -> Buddhist year check failed for case: {case:?}"
205        );
206        assert_eq!(
207            buddhist1.month().ordinal,
208            buddhist_month,
209            "Iso -> Buddhist month check failed for case: {case:?}"
210        );
211        assert_eq!(
212            buddhist1.day_of_month().0,
213            buddhist_day,
214            "Iso -> Buddhist day check failed for case: {case:?}"
215        );
216
217        let buddhist2 =
218            Date::try_new_buddhist(buddhist_year, buddhist_month, buddhist_day).unwrap();
219        let iso2 = buddhist2.to_calendar(Iso);
220        assert_eq!(
221            iso2.era_year().year,
222            iso_year,
223            "Buddhist -> Iso year check failed for case: {case:?}"
224        );
225        assert_eq!(
226            iso2.month().ordinal,
227            iso_month,
228            "Buddhist -> Iso month check failed for case: {case:?}"
229        );
230        assert_eq!(
231            iso2.day_of_month().0,
232            iso_day,
233            "Buddhist -> Iso day check failed for case: {case:?}"
234        );
235    }
236
237    #[test]
238    fn test_buddhist_cases_near_rd_zero() {
239        let cases = [
240            TestCase {
241                iso_year: -100,
242                iso_month: 2,
243                iso_day: 15,
244                buddhist_year: 443,
245                buddhist_month: 2,
246                buddhist_day: 15,
247            },
248            TestCase {
249                iso_year: -3,
250                iso_month: 10,
251                iso_day: 29,
252                buddhist_year: 540,
253                buddhist_month: 10,
254                buddhist_day: 29,
255            },
256            TestCase {
257                iso_year: 0,
258                iso_month: 12,
259                iso_day: 31,
260                buddhist_year: 543,
261                buddhist_month: 12,
262                buddhist_day: 31,
263            },
264            TestCase {
265                iso_year: 1,
266                iso_month: 1,
267                iso_day: 1,
268                buddhist_year: 544,
269                buddhist_month: 1,
270                buddhist_day: 1,
271            },
272            TestCase {
273                iso_year: 4,
274                iso_month: 2,
275                iso_day: 29,
276                buddhist_year: 547,
277                buddhist_month: 2,
278                buddhist_day: 29,
279            },
280        ];
281
282        for case in cases {
283            check_test_case(case);
284        }
285    }
286
287    #[test]
288    fn test_buddhist_cases_near_epoch() {
289        // 1 BE = 543 BCE = -542 ISO
290        let cases = [
291            TestCase {
292                iso_year: -554,
293                iso_month: 12,
294                iso_day: 31,
295                buddhist_year: -11,
296                buddhist_month: 12,
297                buddhist_day: 31,
298            },
299            TestCase {
300                iso_year: -553,
301                iso_month: 1,
302                iso_day: 1,
303                buddhist_year: -10,
304                buddhist_month: 1,
305                buddhist_day: 1,
306            },
307            TestCase {
308                iso_year: -544,
309                iso_month: 8,
310                iso_day: 31,
311                buddhist_year: -1,
312                buddhist_month: 8,
313                buddhist_day: 31,
314            },
315            TestCase {
316                iso_year: -543,
317                iso_month: 5,
318                iso_day: 12,
319                buddhist_year: 0,
320                buddhist_month: 5,
321                buddhist_day: 12,
322            },
323            TestCase {
324                iso_year: -543,
325                iso_month: 12,
326                iso_day: 31,
327                buddhist_year: 0,
328                buddhist_month: 12,
329                buddhist_day: 31,
330            },
331            TestCase {
332                iso_year: -542,
333                iso_month: 1,
334                iso_day: 1,
335                buddhist_year: 1,
336                buddhist_month: 1,
337                buddhist_day: 1,
338            },
339            TestCase {
340                iso_year: -541,
341                iso_month: 7,
342                iso_day: 9,
343                buddhist_year: 2,
344                buddhist_month: 7,
345                buddhist_day: 9,
346            },
347        ];
348
349        for case in cases {
350            check_test_case(case);
351        }
352    }
353}