// This file is part of ICU4X. For terms of use, please see the file // called LICENSE at the top level of the ICU4X source tree // (online at: https://github.com/unicode-org/icu4x/blob/main/LICENSE ). use crate::error::UnknownEraError; use crate::preferences::CalendarAlgorithm; use crate::{ cal::abstract_gregorian::{impl_with_abstract_gregorian, GregorianYears}, calendar_arithmetic::ArithmeticDate, types, Date, DateError, RangeError, }; use tinystr::tinystr; #[derive(Copy, Clone, Debug, Default)] /// The [Thai Solar Buddhist Calendar](https://en.wikipedia.org/wiki/Thai_solar_calendar) /// /// The Thai Solar Buddhist Calendar is a variant of the [`Gregorian`](crate::cal::Gregorian) calendar /// created by the Thai government. It is identical to the Gregorian calendar except that is uses /// the Buddhist Era (-543 CE) instead of the Common Era. /// /// This implementation extends proleptically for dates before the calendar's creation /// in 2484 BE (1941 CE). /// /// This corresponds to the `"buddhist"` [CLDR calendar](https://unicode.org/reports/tr35/#UnicodeCalendarIdentifier). /// /// # Era codes /// /// This calendar uses a single era code `be`, with 1 Buddhist Era being 543 BCE. Dates before this era use negative years. #[allow(clippy::exhaustive_structs)] // this type is stable pub struct Buddhist; impl_with_abstract_gregorian!( crate::cal::Buddhist, BuddhistDateInner, BuddhistEra, _x, BuddhistEra ); #[derive(Copy, Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)] pub(crate) struct BuddhistEra; impl GregorianYears for BuddhistEra { const EXTENDED_YEAR_OFFSET: i32 = -543; fn extended_from_era_year( &self, era: Option<&[u8]>, year: i32, ) -> Result { match era { Some(b"be") | None => Ok(year), _ => Err(UnknownEraError), } } fn era_year_from_extended(&self, extended_year: i32, _month: u8, _day: u8) -> types::EraYear { types::EraYear { era: tinystr!(16, "be"), era_index: Some(0), year: extended_year, extended_year, ambiguity: types::YearAmbiguity::CenturyRequired, } } fn debug_name(&self) -> &'static str { "Buddhist" } fn calendar_algorithm(&self) -> Option { Some(CalendarAlgorithm::Buddhist) } } impl Date { /// Construct a new Buddhist Date. /// /// Years are specified as BE years. /// /// ```rust /// use icu::calendar::Date; /// /// let date_buddhist = Date::try_new_buddhist(1970, 1, 2) /// .expect("Failed to initialize Buddhist Date instance."); /// /// assert_eq!(date_buddhist.era_year().year, 1970); /// assert_eq!(date_buddhist.month().ordinal, 1); /// assert_eq!(date_buddhist.day_of_month().0, 2); /// ``` pub fn try_new_buddhist(year: i32, month: u8, day: u8) -> Result, RangeError> { ArithmeticDate::new_gregorian::(year, month, day) .map(BuddhistDateInner) .map(|i| Date::from_raw(i, Buddhist)) } } #[cfg(test)] mod test { use crate::cal::Iso; use calendrical_calculations::rata_die::RataDie; use super::*; #[test] fn test_buddhist_roundtrip_near_rd_zero() { for i in -10000..=10000 { let rd = RataDie::new(i); let iso1 = Date::from_rata_die(rd, Iso); let buddhist = iso1.to_calendar(Buddhist); let iso2 = buddhist.to_calendar(Iso); let result = iso2.to_rata_die(); assert_eq!(rd, result); } } #[test] fn test_buddhist_roundtrip_near_epoch() { // Buddhist epoch start RD: -198326 for i in -208326..=-188326 { let rd = RataDie::new(i); let iso1 = Date::from_rata_die(rd, Iso); let buddhist = iso1.to_calendar(Buddhist); let iso2 = buddhist.to_calendar(Iso); let result = iso2.to_rata_die(); assert_eq!(rd, result); } } #[test] fn test_buddhist_directionality_near_rd_zero() { for i in -100..=100 { for j in -100..=100 { let iso_i = Date::from_rata_die(RataDie::new(i), Iso); let iso_j = Date::from_rata_die(RataDie::new(j), Iso); let buddhist_i = Date::new_from_iso(iso_i, Buddhist); let buddhist_j = Date::new_from_iso(iso_j, Buddhist); assert_eq!( i.cmp(&j), iso_i.cmp(&iso_j), "ISO directionality inconsistent with directionality for i: {i}, j: {j}" ); assert_eq!( i.cmp(&j), buddhist_i.cmp(&buddhist_j), "Buddhist directionality inconsistent with directionality for i: {i}, j: {j}" ); } } } #[test] fn test_buddhist_directionality_near_epoch() { // Buddhist epoch start RD: -198326 for i in -198426..=-198226 { for j in -198426..=-198226 { let iso_i = Date::from_rata_die(RataDie::new(i), Iso); let iso_j = Date::from_rata_die(RataDie::new(j), Iso); let buddhist_i = Date::new_from_iso(iso_i, Buddhist); let buddhist_j = Date::new_from_iso(iso_j, Buddhist); assert_eq!( i.cmp(&j), iso_i.cmp(&iso_j), "ISO directionality inconsistent with directionality for i: {i}, j: {j}" ); assert_eq!( i.cmp(&j), buddhist_i.cmp(&buddhist_j), "Buddhist directionality inconsistent with directionality for i: {i}, j: {j}" ); } } } #[derive(Debug)] struct TestCase { iso_year: i32, iso_month: u8, iso_day: u8, buddhist_year: i32, buddhist_month: u8, buddhist_day: u8, } fn check_test_case(case: TestCase) { let iso_year = case.iso_year; let iso_month = case.iso_month; let iso_day = case.iso_day; let buddhist_year = case.buddhist_year; let buddhist_month = case.buddhist_month; let buddhist_day = case.buddhist_day; let iso1 = Date::try_new_iso(iso_year, iso_month, iso_day).unwrap(); let buddhist1 = iso1.to_calendar(Buddhist); assert_eq!( buddhist1.era_year().year, buddhist_year, "Iso -> Buddhist year check failed for case: {case:?}" ); assert_eq!( buddhist1.month().ordinal, buddhist_month, "Iso -> Buddhist month check failed for case: {case:?}" ); assert_eq!( buddhist1.day_of_month().0, buddhist_day, "Iso -> Buddhist day check failed for case: {case:?}" ); let buddhist2 = Date::try_new_buddhist(buddhist_year, buddhist_month, buddhist_day).unwrap(); let iso2 = buddhist2.to_calendar(Iso); assert_eq!( iso2.era_year().year, iso_year, "Buddhist -> Iso year check failed for case: {case:?}" ); assert_eq!( iso2.month().ordinal, iso_month, "Buddhist -> Iso month check failed for case: {case:?}" ); assert_eq!( iso2.day_of_month().0, iso_day, "Buddhist -> Iso day check failed for case: {case:?}" ); } #[test] fn test_buddhist_cases_near_rd_zero() { let cases = [ TestCase { iso_year: -100, iso_month: 2, iso_day: 15, buddhist_year: 443, buddhist_month: 2, buddhist_day: 15, }, TestCase { iso_year: -3, iso_month: 10, iso_day: 29, buddhist_year: 540, buddhist_month: 10, buddhist_day: 29, }, TestCase { iso_year: 0, iso_month: 12, iso_day: 31, buddhist_year: 543, buddhist_month: 12, buddhist_day: 31, }, TestCase { iso_year: 1, iso_month: 1, iso_day: 1, buddhist_year: 544, buddhist_month: 1, buddhist_day: 1, }, TestCase { iso_year: 4, iso_month: 2, iso_day: 29, buddhist_year: 547, buddhist_month: 2, buddhist_day: 29, }, ]; for case in cases { check_test_case(case); } } #[test] fn test_buddhist_cases_near_epoch() { // 1 BE = 543 BCE = -542 ISO let cases = [ TestCase { iso_year: -554, iso_month: 12, iso_day: 31, buddhist_year: -11, buddhist_month: 12, buddhist_day: 31, }, TestCase { iso_year: -553, iso_month: 1, iso_day: 1, buddhist_year: -10, buddhist_month: 1, buddhist_day: 1, }, TestCase { iso_year: -544, iso_month: 8, iso_day: 31, buddhist_year: -1, buddhist_month: 8, buddhist_day: 31, }, TestCase { iso_year: -543, iso_month: 5, iso_day: 12, buddhist_year: 0, buddhist_month: 5, buddhist_day: 12, }, TestCase { iso_year: -543, iso_month: 12, iso_day: 31, buddhist_year: 0, buddhist_month: 12, buddhist_day: 31, }, TestCase { iso_year: -542, iso_month: 1, iso_day: 1, buddhist_year: 1, buddhist_month: 1, buddhist_day: 1, }, TestCase { iso_year: -541, iso_month: 7, iso_day: 9, buddhist_year: 2, buddhist_month: 7, buddhist_day: 9, }, ]; for case in cases { check_test_case(case); } } }