1use 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
40const BUDDHIST_ERA_OFFSET: i32 = 543;
44
45#[derive(Copy, Clone, Debug, Default)]
46#[allow(clippy::exhaustive_structs)] pub 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)] 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 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 fn month(&self, date: &Self::DateInner) -> types::FormattableMonth {
131 Iso.month(date)
132 }
133
134 fn day_of_month(&self, date: &Self::DateInner) -> types::DayOfMonth {
136 Iso.day_of_month(date)
137 }
138
139 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 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 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 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 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 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}