icu_calendar/
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
5//! This module contains types and implementations for the Buddhist calendar.
6//!
7//! ```rust
8//! use icu::calendar::{buddhist::Buddhist, Date, DateTime};
9//!
10//! // `Date` type
11//! let date_iso = Date::try_new_iso_date(1970, 1, 2)
12//!     .expect("Failed to initialize ISO Date instance.");
13//! let date_buddhist = Date::new_from_iso(date_iso, Buddhist);
14//!
15//! // `DateTime` type
16//! let datetime_iso = DateTime::try_new_iso_datetime(1970, 1, 2, 13, 1, 0)
17//!     .expect("Failed to initialize ISO DateTime instance.");
18//! let datetime_buddhist = DateTime::new_from_iso(datetime_iso, Buddhist);
19//!
20//! // `Date` checks
21//! assert_eq!(date_buddhist.year().number, 2513);
22//! assert_eq!(date_buddhist.month().ordinal, 1);
23//! assert_eq!(date_buddhist.day_of_month().0, 2);
24//!
25//! // `DateTime` type
26//! assert_eq!(datetime_buddhist.date.year().number, 2513);
27//! assert_eq!(datetime_buddhist.date.month().ordinal, 1);
28//! assert_eq!(datetime_buddhist.date.day_of_month().0, 2);
29//! assert_eq!(datetime_buddhist.time.hour.number(), 13);
30//! assert_eq!(datetime_buddhist.time.minute.number(), 1);
31//! assert_eq!(datetime_buddhist.time.second.number(), 0);
32//! ```
33
34use crate::any_calendar::AnyCalendarKind;
35use crate::calendar_arithmetic::ArithmeticDate;
36use crate::iso::{Iso, IsoDateInner};
37use crate::{types, Calendar, CalendarError, Date, DateDuration, DateDurationUnit, DateTime, Time};
38use tinystr::tinystr;
39
40/// The number of years the Buddhist Era is ahead of C.E. by
41///
42/// (1 AD = 544 BE)
43const BUDDHIST_ERA_OFFSET: i32 = 543;
44
45#[derive(Copy, Clone, Debug, Default)]
46/// The [Thai Solar Buddhist Calendar][cal]
47///
48/// The [Thai Solar Buddhist Calendar][cal] is a solar calendar used in Thailand, with twelve months.
49/// The months and days are identical to that of the Gregorian calendar, however the years are counted
50/// differently using the Buddhist Era.
51///
52/// This type can be used with [`Date`] or [`DateTime`] to represent dates in this calendar.
53///
54/// [cal]: https://en.wikipedia.org/wiki/Thai_solar_calendar
55///
56/// # Era codes
57///
58/// This calendar supports one era, `"be"`, with 1 B.E. being 543 BCE.
59///
60/// # Month codes
61///
62/// This calendar supports 12 solar month codes (`"M01" - "M12"`)
63#[allow(clippy::exhaustive_structs)] // this type is stable
64pub struct Buddhist;
65
66impl Calendar for Buddhist {
67    type DateInner = IsoDateInner;
68
69    fn date_from_codes(
70        &self,
71        era: types::Era,
72        year: i32,
73        month_code: types::MonthCode,
74        day: u8,
75    ) -> Result<Self::DateInner, CalendarError> {
76        if era.0 != tinystr!(16, "be") {
77            return Err(CalendarError::UnknownEra(era.0, self.debug_name()));
78        }
79        let year = year - BUDDHIST_ERA_OFFSET;
80
81        ArithmeticDate::new_from_codes(self, year, month_code, day).map(IsoDateInner)
82    }
83    fn date_from_iso(&self, iso: Date<Iso>) -> IsoDateInner {
84        *iso.inner()
85    }
86
87    fn date_to_iso(&self, date: &Self::DateInner) -> Date<Iso> {
88        Date::from_raw(*date, Iso)
89    }
90
91    fn months_in_year(&self, date: &Self::DateInner) -> u8 {
92        Iso.months_in_year(date)
93    }
94
95    fn days_in_year(&self, date: &Self::DateInner) -> u16 {
96        Iso.days_in_year(date)
97    }
98
99    fn days_in_month(&self, date: &Self::DateInner) -> u8 {
100        Iso.days_in_month(date)
101    }
102
103    fn offset_date(&self, date: &mut Self::DateInner, offset: DateDuration<Self>) {
104        Iso.offset_date(date, offset.cast_unit())
105    }
106
107    #[allow(clippy::field_reassign_with_default)] // it's more clear this way
108    fn until(
109        &self,
110        date1: &Self::DateInner,
111        date2: &Self::DateInner,
112        _calendar2: &Self,
113        largest_unit: DateDurationUnit,
114        smallest_unit: DateDurationUnit,
115    ) -> DateDuration<Self> {
116        Iso.until(date1, date2, &Iso, largest_unit, smallest_unit)
117            .cast_unit()
118    }
119
120    /// The calendar-specific year represented by `date`
121    fn year(&self, date: &Self::DateInner) -> types::FormattableYear {
122        iso_year_as_buddhist(date.0.year)
123    }
124
125    fn is_in_leap_year(&self, date: &Self::DateInner) -> bool {
126        Iso.is_in_leap_year(date)
127    }
128
129    /// The calendar-specific month represented by `date`
130    fn month(&self, date: &Self::DateInner) -> types::FormattableMonth {
131        Iso.month(date)
132    }
133
134    /// The calendar-specific day-of-month represented by `date`
135    fn day_of_month(&self, date: &Self::DateInner) -> types::DayOfMonth {
136        Iso.day_of_month(date)
137    }
138
139    /// Information of the day of the year
140    fn day_of_year_info(&self, date: &Self::DateInner) -> types::DayOfYearInfo {
141        let prev_year = date.0.year - 1;
142        let next_year = date.0.year + 1;
143        types::DayOfYearInfo {
144            day_of_year: Iso::day_of_year(*date),
145            days_in_year: Iso::days_in_year_direct(date.0.year),
146            prev_year: iso_year_as_buddhist(prev_year),
147            days_in_prev_year: Iso::days_in_year_direct(prev_year),
148            next_year: iso_year_as_buddhist(next_year),
149        }
150    }
151
152    fn debug_name(&self) -> &'static str {
153        "Buddhist"
154    }
155
156    fn any_calendar_kind(&self) -> Option<AnyCalendarKind> {
157        Some(AnyCalendarKind::Buddhist)
158    }
159}
160
161impl Date<Buddhist> {
162    /// Construct a new Buddhist Date.
163    ///
164    /// Years are specified as BE years.
165    ///
166    /// ```rust
167    /// use icu::calendar::Date;
168    ///
169    /// let date_buddhist = Date::try_new_buddhist_date(1970, 1, 2)
170    ///     .expect("Failed to initialize Buddhist Date instance.");
171    ///
172    /// assert_eq!(date_buddhist.year().number, 1970);
173    /// assert_eq!(date_buddhist.month().ordinal, 1);
174    /// assert_eq!(date_buddhist.day_of_month().0, 2);
175    /// ```
176    pub fn try_new_buddhist_date(
177        year: i32,
178        month: u8,
179        day: u8,
180    ) -> Result<Date<Buddhist>, CalendarError> {
181        Date::try_new_iso_date(year - BUDDHIST_ERA_OFFSET, month, day)
182            .map(|d| Date::new_from_iso(d, Buddhist))
183    }
184}
185
186impl DateTime<Buddhist> {
187    /// Construct a new Buddhist datetime from integers.
188    ///
189    /// Years are specified as BE years.
190    ///
191    /// ```rust
192    /// use icu::calendar::DateTime;
193    ///
194    /// let datetime_buddhist =
195    ///     DateTime::try_new_buddhist_datetime(1970, 1, 2, 13, 1, 0)
196    ///         .expect("Failed to initialize Buddhist DateTime instance.");
197    ///
198    /// assert_eq!(datetime_buddhist.date.year().number, 1970);
199    /// assert_eq!(datetime_buddhist.date.month().ordinal, 1);
200    /// assert_eq!(datetime_buddhist.date.day_of_month().0, 2);
201    /// assert_eq!(datetime_buddhist.time.hour.number(), 13);
202    /// assert_eq!(datetime_buddhist.time.minute.number(), 1);
203    /// assert_eq!(datetime_buddhist.time.second.number(), 0);
204    /// ```
205    pub fn try_new_buddhist_datetime(
206        year: i32,
207        month: u8,
208        day: u8,
209        hour: u8,
210        minute: u8,
211        second: u8,
212    ) -> Result<DateTime<Buddhist>, CalendarError> {
213        Ok(DateTime {
214            date: Date::try_new_buddhist_date(year, month, day)?,
215            time: Time::try_new(hour, minute, second, 0)?,
216        })
217    }
218}
219
220fn iso_year_as_buddhist(year: i32) -> types::FormattableYear {
221    let buddhist_year = year + BUDDHIST_ERA_OFFSET;
222    types::FormattableYear {
223        era: types::Era(tinystr!(16, "be")),
224        number: buddhist_year,
225        cyclic: None,
226        related_iso: None,
227    }
228}
229
230#[cfg(test)]
231mod test {
232    use calendrical_calculations::rata_die::RataDie;
233
234    use super::*;
235
236    #[test]
237    fn test_buddhist_roundtrip_near_rd_zero() {
238        for i in -10000..=10000 {
239            let rd = RataDie::new(i);
240            let iso1 = Iso::iso_from_fixed(rd);
241            let buddhist = iso1.to_calendar(Buddhist);
242            let iso2 = buddhist.to_calendar(Iso);
243            let result = Iso::fixed_from_iso(iso2.inner);
244            assert_eq!(rd, result);
245        }
246    }
247
248    #[test]
249    fn test_buddhist_roundtrip_near_epoch() {
250        // Buddhist epoch start RD: -198326
251        for i in -208326..=-188326 {
252            let rd = RataDie::new(i);
253            let iso1 = Iso::iso_from_fixed(rd);
254            let buddhist = iso1.to_calendar(Buddhist);
255            let iso2 = buddhist.to_calendar(Iso);
256            let result = Iso::fixed_from_iso(iso2.inner);
257            assert_eq!(rd, result);
258        }
259    }
260
261    #[test]
262    fn test_buddhist_directionality_near_rd_zero() {
263        for i in -100..=100 {
264            for j in -100..=100 {
265                let iso_i = Iso::iso_from_fixed(RataDie::new(i));
266                let iso_j = Iso::iso_from_fixed(RataDie::new(j));
267
268                let buddhist_i = Date::new_from_iso(iso_i, Buddhist);
269                let buddhist_j = Date::new_from_iso(iso_j, Buddhist);
270
271                assert_eq!(
272                    i.cmp(&j),
273                    iso_i.cmp(&iso_j),
274                    "ISO directionality inconsistent with directionality for i: {i}, j: {j}"
275                );
276
277                assert_eq!(
278                    i.cmp(&j),
279                    buddhist_i.cmp(&buddhist_j),
280                    "Buddhist directionality inconsistent with directionality for i: {i}, j: {j}"
281                );
282            }
283        }
284    }
285
286    #[test]
287    fn test_buddhist_directionality_near_epoch() {
288        // Buddhist epoch start RD: -198326
289        for i in -198426..=-198226 {
290            for j in -198426..=-198226 {
291                let iso_i = Iso::iso_from_fixed(RataDie::new(i));
292                let iso_j = Iso::iso_from_fixed(RataDie::new(j));
293
294                let buddhist_i = Date::new_from_iso(iso_i, Buddhist);
295                let buddhist_j = Date::new_from_iso(iso_j, Buddhist);
296
297                assert_eq!(
298                    i.cmp(&j),
299                    iso_i.cmp(&iso_j),
300                    "ISO directionality inconsistent with directionality for i: {i}, j: {j}"
301                );
302
303                assert_eq!(
304                    i.cmp(&j),
305                    buddhist_i.cmp(&buddhist_j),
306                    "Buddhist directionality inconsistent with directionality for i: {i}, j: {j}"
307                );
308            }
309        }
310    }
311
312    #[derive(Debug)]
313    struct TestCase {
314        iso_year: i32,
315        iso_month: u8,
316        iso_day: u8,
317        buddhist_year: i32,
318        buddhist_month: u8,
319        buddhist_day: u8,
320    }
321
322    fn check_test_case(case: TestCase) {
323        let iso_year = case.iso_year;
324        let iso_month = case.iso_month;
325        let iso_day = case.iso_day;
326        let buddhist_year = case.buddhist_year;
327        let buddhist_month = case.buddhist_month;
328        let buddhist_day = case.buddhist_day;
329
330        let iso1 = Date::try_new_iso_date(iso_year, iso_month, iso_day).unwrap();
331        let buddhist1 = iso1.to_calendar(Buddhist);
332        assert_eq!(
333            buddhist1.year().number,
334            buddhist_year,
335            "Iso -> Buddhist year check failed for case: {case:?}"
336        );
337        assert_eq!(
338            buddhist1.month().ordinal,
339            buddhist_month as u32,
340            "Iso -> Buddhist month check failed for case: {case:?}"
341        );
342        assert_eq!(
343            buddhist1.day_of_month().0,
344            buddhist_day as u32,
345            "Iso -> Buddhist day check failed for case: {case:?}"
346        );
347
348        let buddhist2 =
349            Date::try_new_buddhist_date(buddhist_year, buddhist_month, buddhist_day).unwrap();
350        let iso2 = buddhist2.to_calendar(Iso);
351        assert_eq!(
352            iso2.year().number,
353            iso_year,
354            "Buddhist -> Iso year check failed for case: {case:?}"
355        );
356        assert_eq!(
357            iso2.month().ordinal,
358            iso_month as u32,
359            "Buddhist -> Iso month check failed for case: {case:?}"
360        );
361        assert_eq!(
362            iso2.day_of_month().0,
363            iso_day as u32,
364            "Buddhist -> Iso day check failed for case: {case:?}"
365        );
366    }
367
368    #[test]
369    fn test_buddhist_cases_near_rd_zero() {
370        let cases = [
371            TestCase {
372                iso_year: -100,
373                iso_month: 2,
374                iso_day: 15,
375                buddhist_year: 443,
376                buddhist_month: 2,
377                buddhist_day: 15,
378            },
379            TestCase {
380                iso_year: -3,
381                iso_month: 10,
382                iso_day: 29,
383                buddhist_year: 540,
384                buddhist_month: 10,
385                buddhist_day: 29,
386            },
387            TestCase {
388                iso_year: 0,
389                iso_month: 12,
390                iso_day: 31,
391                buddhist_year: 543,
392                buddhist_month: 12,
393                buddhist_day: 31,
394            },
395            TestCase {
396                iso_year: 1,
397                iso_month: 1,
398                iso_day: 1,
399                buddhist_year: 544,
400                buddhist_month: 1,
401                buddhist_day: 1,
402            },
403            TestCase {
404                iso_year: 4,
405                iso_month: 2,
406                iso_day: 29,
407                buddhist_year: 547,
408                buddhist_month: 2,
409                buddhist_day: 29,
410            },
411        ];
412
413        for case in cases {
414            check_test_case(case);
415        }
416    }
417
418    #[test]
419    fn test_buddhist_cases_near_epoch() {
420        // 1 BE = 543 BCE = -542 ISO
421        let cases = [
422            TestCase {
423                iso_year: -554,
424                iso_month: 12,
425                iso_day: 31,
426                buddhist_year: -11,
427                buddhist_month: 12,
428                buddhist_day: 31,
429            },
430            TestCase {
431                iso_year: -553,
432                iso_month: 1,
433                iso_day: 1,
434                buddhist_year: -10,
435                buddhist_month: 1,
436                buddhist_day: 1,
437            },
438            TestCase {
439                iso_year: -544,
440                iso_month: 8,
441                iso_day: 31,
442                buddhist_year: -1,
443                buddhist_month: 8,
444                buddhist_day: 31,
445            },
446            TestCase {
447                iso_year: -543,
448                iso_month: 5,
449                iso_day: 12,
450                buddhist_year: 0,
451                buddhist_month: 5,
452                buddhist_day: 12,
453            },
454            TestCase {
455                iso_year: -543,
456                iso_month: 12,
457                iso_day: 31,
458                buddhist_year: 0,
459                buddhist_month: 12,
460                buddhist_day: 31,
461            },
462            TestCase {
463                iso_year: -542,
464                iso_month: 1,
465                iso_day: 1,
466                buddhist_year: 1,
467                buddhist_month: 1,
468                buddhist_day: 1,
469            },
470            TestCase {
471                iso_year: -541,
472                iso_month: 7,
473                iso_day: 9,
474                buddhist_year: 2,
475                buddhist_month: 7,
476                buddhist_day: 9,
477            },
478        ];
479
480        for case in cases {
481            check_test_case(case);
482        }
483    }
484}