1use crate::cal::iso::Iso;
6use crate::calendar_arithmetic::DateFieldsResolver;
7use crate::calendar_arithmetic::{ArithmeticDate, ToExtendedYear};
8use crate::error::{
9 DateError, DateFromFieldsError, EcmaReferenceYearError, MonthCodeError, UnknownEraError,
10};
11use crate::options::{DateAddOptions, DateDifferenceOptions};
12use crate::options::{DateFromFieldsOptions, Overflow};
13use crate::types::ValidMonthCode;
14use crate::AsCalendar;
15use crate::{types, Calendar, Date};
16use calendrical_calculations::chinese_based::{
17 self, ChineseBased, YearBounds, WELL_BEHAVED_ASTRONOMICAL_RANGE,
18};
19use calendrical_calculations::rata_die::RataDie;
20use icu_locale_core::preferences::extensions::unicode::keywords::CalendarAlgorithm;
21use icu_provider::prelude::*;
22
23#[path = "east_asian_traditional/china_data.rs"]
24mod china_data;
25#[path = "east_asian_traditional/korea_data.rs"]
26mod korea_data;
27#[path = "east_asian_traditional/qing_data.rs"]
28mod qing_data;
29#[path = "east_asian_traditional/simple.rs"]
30mod simple;
31
32#[derive(Clone, Debug, Default, Copy, PartialEq, Eq, PartialOrd, Ord)]
80#[allow(clippy::exhaustive_structs)] pub struct EastAsianTraditional<R>(pub R);
82
83pub trait Rules: Clone + core::fmt::Debug + crate::cal::scaffold::UnstableSealed {
99 fn year_data(&self, related_iso: i32) -> EastAsianTraditionalYearData;
101
102 fn ecma_reference_year(
116 &self,
117 _month_code: (u8, bool),
119 _day: u8,
120 ) -> Result<i32, EcmaReferenceYearError> {
121 Err(EcmaReferenceYearError::Unimplemented)
122 }
123
124 fn debug_name(&self) -> &'static str {
126 "Chinese (custom)"
127 }
128
129 fn calendar_algorithm(&self) -> Option<CalendarAlgorithm> {
131 None
132 }
133}
134
135pub type ChineseTraditional = EastAsianTraditional<China>;
164
165#[derive(Copy, Clone, Debug, Default)]
169#[non_exhaustive]
170pub struct China;
171
172impl China {
173 pub fn gb_t_33661_2017(related_iso: i32) -> EastAsianTraditionalYearData {
191 EastAsianTraditionalYearData::calendrical_calculations::<chinese_based::Chinese>(
192 related_iso,
193 )
194 }
195}
196
197impl crate::cal::scaffold::UnstableSealed for China {}
198impl Rules for China {
199 fn year_data(&self, related_iso: i32) -> EastAsianTraditionalYearData {
200 if let Some(year) = EastAsianTraditionalYearData::lookup(
201 related_iso,
202 china_data::STARTING_YEAR,
203 china_data::DATA,
204 ) {
205 year
206 } else if related_iso > china_data::STARTING_YEAR {
207 EastAsianTraditionalYearData::simple(simple::UTC_PLUS_8, related_iso)
208 } else if let Some(year) = EastAsianTraditionalYearData::lookup(
209 related_iso,
210 qing_data::STARTING_YEAR,
211 qing_data::DATA,
212 ) {
213 year
214 } else {
215 EastAsianTraditionalYearData::simple(simple::BEIJING_UTC_OFFSET, related_iso)
216 }
217 }
218
219 fn ecma_reference_year(
220 &self,
221 month_code: (u8, bool),
222 day: u8,
223 ) -> Result<i32, EcmaReferenceYearError> {
224 let (number, is_leap) = month_code;
225 let extended_year = match (number, is_leap, day > 29) {
227 (1, false, false) => 1972,
228 (1, false, true) => 1970,
229 (1, true, false) => 1898,
230 (1, true, true) => 1898,
231 (2, false, false) => 1972,
232 (2, false, true) => 1972,
233 (2, true, false) => 1947,
234 (2, true, true) => 1830,
235 (3, false, false) => 1972,
236 (3, false, true) => 1966,
237 (3, true, false) => 1966,
238 (3, true, true) => 1955,
239 (4, false, false) => 1972,
240 (4, false, true) => 1970,
241 (4, true, false) => 1963,
242 (4, true, true) => 1944,
243 (5, false, false) => 1972,
244 (5, false, true) => 1972,
245 (5, true, false) => 1971,
246 (5, true, true) => 1952,
247 (6, false, false) => 1972,
248 (6, false, true) => 1971,
249 (6, true, false) => 1960,
250 (6, true, true) => 1941,
251 (7, false, false) => 1972,
252 (7, false, true) => 1972,
253 (7, true, false) => 1968,
254 (7, true, true) => 1938,
255 (8, false, false) => 1972,
256 (8, false, true) => 1971,
257 (8, true, false) => 1957,
258 (8, true, true) => 1691,
259 (9, false, false) => 1972,
260 (9, false, true) => 1972,
261 (9, true, false) => 2014,
262 (9, true, true) => 1843,
263 (10, false, false) => 1972,
264 (10, false, true) => 1972,
265 (10, true, false) => 1984,
266 (10, true, true) => 1737,
267 (11, false, false) if day > 26 => 1971,
270 (11, false, false) => 1972,
271 (11, false, true) => 1969,
272 (11, true, false) => 2033,
273 (11, true, true) => 1889,
274 (12, false, false) => 1971,
275 (12, false, true) => 1971,
276 (12, true, false) => 1878,
277 (12, true, true) => 1783,
278 _ => return Err(EcmaReferenceYearError::MonthCodeNotInCalendar),
279 };
280 Ok(extended_year)
281 }
282
283 fn calendar_algorithm(&self) -> Option<CalendarAlgorithm> {
284 Some(CalendarAlgorithm::Chinese)
285 }
286
287 fn debug_name(&self) -> &'static str {
288 "Chinese"
289 }
290}
291
292pub type KoreanTraditional = EastAsianTraditional<Korea>;
341
342#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord)]
346#[non_exhaustive]
347pub struct Korea;
348
349impl Korea {
350 pub fn adapted_gb_t_33661_2017(related_iso: i32) -> EastAsianTraditionalYearData {
354 EastAsianTraditionalYearData::calendrical_calculations::<chinese_based::Dangi>(related_iso)
355 }
356}
357
358impl KoreanTraditional {
359 pub const fn new() -> Self {
361 Self(Korea)
362 }
363
364 #[cfg(feature = "serde")]
366 #[doc = icu_provider::gen_buffer_unstable_docs!(BUFFER,Self::new)]
367 #[deprecated(since = "2.1.0", note = "use `Self::new()")]
368 pub fn try_new_with_buffer_provider(
369 _provider: &(impl icu_provider::buf::BufferProvider + ?Sized),
370 ) -> Result<Self, DataError> {
371 Ok(Self::new())
372 }
373
374 #[doc = icu_provider::gen_buffer_unstable_docs!(UNSTABLE, Self::new)]
376 #[deprecated(since = "2.1.0", note = "use `Self::new()")]
377 pub fn try_new_unstable<D: ?Sized>(_provider: &D) -> Result<Self, DataError> {
378 Ok(Self::new())
379 }
380
381 #[deprecated(since = "2.1.0", note = "use `Self::new()")]
383 pub fn new_always_calculating() -> Self {
384 Self::new()
385 }
386}
387
388impl crate::cal::scaffold::UnstableSealed for Korea {}
389impl Rules for Korea {
390 fn year_data(&self, related_iso: i32) -> EastAsianTraditionalYearData {
391 if let Some(year) = EastAsianTraditionalYearData::lookup(
392 related_iso,
393 korea_data::STARTING_YEAR,
394 korea_data::DATA,
395 ) {
396 year
397 } else if related_iso > korea_data::STARTING_YEAR {
398 EastAsianTraditionalYearData::simple(simple::UTC_PLUS_9, related_iso)
399 } else if let Some(year) = EastAsianTraditionalYearData::lookup(
400 related_iso,
401 qing_data::STARTING_YEAR,
402 qing_data::DATA,
403 ) {
404 year
407 } else {
408 EastAsianTraditionalYearData::simple(simple::BEIJING_UTC_OFFSET, related_iso)
409 }
410 }
411
412 fn ecma_reference_year(
413 &self,
414 month_code: (u8, bool),
415 day: u8,
416 ) -> Result<i32, EcmaReferenceYearError> {
417 let (number, is_leap) = month_code;
418 let extended_year = match (number, is_leap, day > 29) {
420 (1, false, false) => 1972,
421 (1, false, true) => 1970,
422 (1, true, false) => 1898,
423 (1, true, true) => 1898,
424 (2, false, false) => 1972,
425 (2, false, true) => 1972,
426 (2, true, false) => 1947,
427 (2, true, true) => 1830,
428 (3, false, false) => 1972,
429 (3, false, true) => 1968,
430 (3, true, false) => 1966,
431 (3, true, true) => 1955,
432 (4, false, false) => 1972,
433 (4, false, true) => 1970,
434 (4, true, false) => 1963,
435 (4, true, true) => 1944,
436 (5, false, false) => 1972,
437 (5, false, true) => 1972,
438 (5, true, false) => 1971,
439 (5, true, true) => 1952,
440 (6, false, false) => 1972,
441 (6, false, true) => 1971,
442 (6, true, false) => 1960,
443 (6, true, true) => 1941,
444 (7, false, false) => 1972,
445 (7, false, true) => 1972,
446 (7, true, false) => 1968,
447 (7, true, true) => 1938,
448 (8, false, false) => 1972,
449 (8, false, true) => 1971,
450 (8, true, false) => 1957,
451 (8, true, true) => 1691,
452 (9, false, false) => 1972,
453 (9, false, true) => 1972,
454 (9, true, false) => 2014,
455 (9, true, true) => 1843,
456 (10, false, false) => 1972,
457 (10, false, true) => 1972,
458 (10, true, false) => 1984,
459 (10, true, true) => 1737,
460 (11, false, false) if day > 26 => 1971,
463 (11, false, false) => 1972,
464 (11, false, true) => 1969,
465 (11, true, false) => 2033,
466 (11, true, true) => 1889,
467 (12, false, false) => 1971,
468 (12, false, true) => 1971,
469 (12, true, false) => 1878,
470 (12, true, true) => 1783,
471 _ => return Err(EcmaReferenceYearError::MonthCodeNotInCalendar),
472 };
473 Ok(extended_year)
474 }
475
476 fn calendar_algorithm(&self) -> Option<CalendarAlgorithm> {
477 Some(CalendarAlgorithm::Dangi)
478 }
479 fn debug_name(&self) -> &'static str {
480 "Korean"
481 }
482}
483
484impl<A: AsCalendar<Calendar = KoreanTraditional>> Date<A> {
485 #[deprecated(since = "2.1.0", note = "use `Date::try_new_from_codes`")]
489 pub fn try_new_dangi_with_calendar(
490 related_iso_year: i32,
491 ordinal_month: u8,
492 day: u8,
493 calendar: A,
494 ) -> Result<Date<A>, DateError> {
495 ArithmeticDate::try_from_ymd(
496 calendar.as_calendar().0.year_data(related_iso_year),
497 ordinal_month,
498 day,
499 )
500 .map(ChineseDateInner)
501 .map(|inner| Date::from_raw(inner, calendar))
502 .map_err(Into::into)
503 }
504}
505
506#[derive(Debug, Clone)]
508pub struct ChineseDateInner<R: Rules>(ArithmeticDate<EastAsianTraditional<R>>);
509
510impl<R: Rules> Copy for ChineseDateInner<R> {}
511impl<R: Rules> PartialEq for ChineseDateInner<R> {
512 fn eq(&self, other: &Self) -> bool {
513 self.0 == other.0
514 }
515}
516impl<R: Rules> Eq for ChineseDateInner<R> {}
517impl<R: Rules> PartialOrd for ChineseDateInner<R> {
518 fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
519 Some(self.cmp(other))
520 }
521}
522impl<R: Rules> Ord for ChineseDateInner<R> {
523 fn cmp(&self, other: &Self) -> core::cmp::Ordering {
524 self.0.cmp(&other.0)
525 }
526}
527
528impl ChineseTraditional {
529 pub const fn new() -> Self {
531 EastAsianTraditional(China)
532 }
533
534 #[cfg(feature = "serde")]
535 #[doc = icu_provider::gen_buffer_unstable_docs!(BUFFER,Self::new)]
536 #[deprecated(since = "2.1.0", note = "use `Self::new()")]
537 pub fn try_new_with_buffer_provider(
538 _provider: &(impl icu_provider::buf::BufferProvider + ?Sized),
539 ) -> Result<Self, DataError> {
540 Ok(Self::new())
541 }
542
543 #[doc = icu_provider::gen_buffer_unstable_docs!(UNSTABLE, Self::new)]
544 #[deprecated(since = "2.1.0", note = "use `Self::new()")]
545 pub fn try_new_unstable<D: ?Sized>(_provider: &D) -> Result<Self, DataError> {
546 Ok(Self::new())
547 }
548
549 #[deprecated(since = "2.1.0", note = "use `Self::new()")]
551 pub fn new_always_calculating() -> Self {
552 Self::new()
553 }
554}
555
556impl<R: Rules> DateFieldsResolver for EastAsianTraditional<R> {
557 type YearInfo = EastAsianTraditionalYearData;
558
559 fn days_in_provided_month(year: EastAsianTraditionalYearData, month: u8) -> u8 {
560 29 + year.packed.month_has_30_days(month) as u8
561 }
562
563 fn months_in_provided_year(year: EastAsianTraditionalYearData) -> u8 {
565 12 + year.packed.leap_month().is_some() as u8
566 }
567
568 #[inline]
569 fn year_info_from_era(
570 &self,
571 _era: &[u8],
572 _era_year: i32,
573 ) -> Result<Self::YearInfo, UnknownEraError> {
574 Err(UnknownEraError)
576 }
577
578 #[inline]
579 fn year_info_from_extended(&self, extended_year: i32) -> Self::YearInfo {
580 self.0.year_data(extended_year)
581 }
582
583 #[inline]
584 fn reference_year_from_month_day(
585 &self,
586 month_code: types::ValidMonthCode,
587 day: u8,
588 ) -> Result<Self::YearInfo, EcmaReferenceYearError> {
589 self.0
590 .ecma_reference_year(month_code.to_tuple(), day)
591 .map(|y| self.0.year_data(y))
592 }
593
594 fn ordinal_month_from_code(
595 &self,
596 year: &Self::YearInfo,
597 month_code: types::ValidMonthCode,
598 options: DateFromFieldsOptions,
599 ) -> Result<u8, MonthCodeError> {
600 let leap_month = year.packed.leap_month().unwrap_or(14);
603
604 if month_code == ValidMonthCode::new_unchecked(leap_month - 1, true) {
607 return Ok(leap_month);
608 }
609
610 let (number @ 1..13, leap) = month_code.to_tuple() else {
611 return Err(MonthCodeError::NotInCalendar);
612 };
613
614 if leap && options.overflow != Some(Overflow::Constrain) {
615 return Err(MonthCodeError::NotInYear);
617 }
618
619 Ok(number + (number >= leap_month) as u8)
621 }
622
623 fn month_code_from_ordinal(&self, year: &Self::YearInfo, ordinal_month: u8) -> ValidMonthCode {
624 let leap_month = year.packed.leap_month().unwrap_or(14);
627 ValidMonthCode::new_unchecked(
628 ordinal_month - (ordinal_month >= leap_month) as u8,
630 ordinal_month == leap_month,
631 )
632 }
633}
634
635impl<R: Rules> crate::cal::scaffold::UnstableSealed for EastAsianTraditional<R> {}
636impl<R: Rules> Calendar for EastAsianTraditional<R> {
637 type DateInner = ChineseDateInner<R>;
638 type Year = types::CyclicYear;
639 type DifferenceError = core::convert::Infallible;
640
641 fn from_codes(
642 &self,
643 era: Option<&str>,
644 year: i32,
645 month_code: types::MonthCode,
646 day: u8,
647 ) -> Result<Self::DateInner, DateError> {
648 ArithmeticDate::from_codes(era, year, month_code, day, self).map(ChineseDateInner)
649 }
650
651 #[cfg(feature = "unstable")]
652 fn from_fields(
653 &self,
654 fields: types::DateFields,
655 options: DateFromFieldsOptions,
656 ) -> Result<Self::DateInner, DateFromFieldsError> {
657 ArithmeticDate::from_fields(fields, options, self).map(ChineseDateInner)
658 }
659
660 fn from_rata_die(&self, rd: RataDie) -> Self::DateInner {
661 let iso = Iso.from_rata_die(rd);
662 let year = {
663 let candidate = self.0.year_data(iso.0.year);
664
665 if rd >= candidate.new_year() {
666 candidate
667 } else {
668 self.0.year_data(iso.0.year - 1)
669 }
670 };
671
672 let rd = rd.clamp(
674 year.new_year(),
675 year.new_year() + year.packed.days_in_year() as i64,
676 );
677
678 let day_of_year = (rd - year.new_year()) as u16;
679
680 let mut month = (day_of_year / 30) as u8 + 1;
683 let mut last_day_of_month = year.packed.last_day_of_month(month);
684 let mut last_day_of_prev_month = year.packed.last_day_of_month(month - 1);
685
686 while day_of_year >= last_day_of_month {
687 month += 1;
688 last_day_of_prev_month = last_day_of_month;
689 last_day_of_month = year.packed.last_day_of_month(month);
690 }
691
692 let day = (day_of_year + 1 - last_day_of_prev_month) as u8;
693
694 ChineseDateInner(ArithmeticDate::new_unchecked(year, month, day))
695 }
696
697 fn to_rata_die(&self, date: &Self::DateInner) -> RataDie {
698 date.0.year.new_year()
699 + date.0.year.packed.last_day_of_month(date.0.month - 1) as i64
700 + (date.0.day - 1) as i64
701 }
702
703 fn has_cheap_iso_conversion(&self) -> bool {
704 false
705 }
706
707 fn days_in_year(&self, date: &Self::DateInner) -> u16 {
710 date.0.year.packed.days_in_year()
711 }
712
713 fn days_in_month(&self, date: &Self::DateInner) -> u8 {
714 Self::days_in_provided_month(date.0.year, date.0.month)
715 }
716
717 #[cfg(feature = "unstable")]
718 fn add(
719 &self,
720 date: &Self::DateInner,
721 duration: types::DateDuration,
722 options: DateAddOptions,
723 ) -> Result<Self::DateInner, DateError> {
724 date.0.added(duration, self, options).map(ChineseDateInner)
725 }
726
727 #[cfg(feature = "unstable")]
728 fn until(
729 &self,
730 date1: &Self::DateInner,
731 date2: &Self::DateInner,
732 options: DateDifferenceOptions,
733 ) -> Result<types::DateDuration, Self::DifferenceError> {
734 Ok(date1.0.until(&date2.0, self, options))
735 }
736
737 fn debug_name(&self) -> &'static str {
739 self.0.debug_name()
740 }
741
742 fn year_info(&self, date: &Self::DateInner) -> Self::Year {
743 let year = date.0.year;
744 types::CyclicYear {
745 year: (year.related_iso - 4).rem_euclid(60) as u8 + 1,
746 related_iso: year.related_iso,
747 }
748 }
749
750 fn is_in_leap_year(&self, date: &Self::DateInner) -> bool {
751 date.0.year.packed.leap_month().is_some()
752 }
753
754 fn month(&self, date: &Self::DateInner) -> types::MonthInfo {
759 types::MonthInfo::for_code_and_ordinal(
760 self.month_code_from_ordinal(&date.0.year, date.0.month),
761 date.0.month,
762 )
763 }
764
765 fn day_of_month(&self, date: &Self::DateInner) -> types::DayOfMonth {
767 types::DayOfMonth(date.0.day)
768 }
769
770 fn day_of_year(&self, date: &Self::DateInner) -> types::DayOfYear {
772 types::DayOfYear(date.0.year.packed.last_day_of_month(date.0.month - 1) + date.0.day as u16)
773 }
774
775 fn calendar_algorithm(&self) -> Option<CalendarAlgorithm> {
776 self.0.calendar_algorithm()
777 }
778
779 fn months_in_year(&self, date: &Self::DateInner) -> u8 {
780 Self::months_in_provided_year(date.0.year)
781 }
782}
783
784impl<A: AsCalendar<Calendar = ChineseTraditional>> Date<A> {
785 #[deprecated(since = "2.1.0", note = "use `Date::try_new_from_codes`")]
789 pub fn try_new_chinese_with_calendar(
790 related_iso_year: i32,
791 ordinal_month: u8,
792 day: u8,
793 calendar: A,
794 ) -> Result<Date<A>, DateError> {
795 ArithmeticDate::try_from_ymd(
796 calendar.as_calendar().0.year_data(related_iso_year),
797 ordinal_month,
798 day,
799 )
800 .map(ChineseDateInner)
801 .map(|inner| Date::from_raw(inner, calendar))
802 .map_err(Into::into)
803 }
804}
805
806#[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Ord)]
808pub struct EastAsianTraditionalYearData {
810 packed: PackedEastAsianTraditionalYearData,
815 related_iso: i32,
816}
817
818impl ToExtendedYear for EastAsianTraditionalYearData {
819 fn to_extended_year(&self) -> i32 {
820 self.related_iso
821 }
822}
823
824impl EastAsianTraditionalYearData {
825 pub fn new(
838 related_iso: i32,
839 start_day: RataDie,
840 month_lengths: [bool; 13],
841 leap_month: Option<u8>,
842 ) -> Self {
843 Self {
844 packed: PackedEastAsianTraditionalYearData::new(
845 related_iso,
846 month_lengths,
847 leap_month,
848 start_day,
849 ),
850 related_iso,
851 }
852 }
853
854 fn lookup(
855 related_iso: i32,
856 starting_year: i32,
857 data: &[PackedEastAsianTraditionalYearData],
858 ) -> Option<Self> {
859 Some(related_iso)
860 .and_then(|e| usize::try_from(e.checked_sub(starting_year)?).ok())
861 .and_then(|i| data.get(i))
862 .map(|&packed| Self {
863 related_iso,
864 packed,
865 })
866 }
867
868 fn calendrical_calculations<CB: ChineseBased>(
869 related_iso: i32,
870 ) -> EastAsianTraditionalYearData {
871 let mid_year = calendrical_calculations::gregorian::fixed_from_gregorian(related_iso, 7, 1);
872 let year_bounds = YearBounds::compute::<CB>(mid_year);
873
874 let YearBounds {
875 new_year,
876 next_new_year,
877 ..
878 } = year_bounds;
879 let (month_lengths, leap_month) =
880 chinese_based::month_structure_for_year::<CB>(new_year, next_new_year);
881
882 EastAsianTraditionalYearData {
883 packed: PackedEastAsianTraditionalYearData::new(
884 related_iso,
885 month_lengths,
886 leap_month,
887 new_year,
888 ),
889 related_iso,
890 }
891 }
892
893 fn new_year(self) -> RataDie {
895 self.packed.new_year(self.related_iso)
896 }
897}
898
899#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
920struct PackedEastAsianTraditionalYearData(u8, u8, u8);
921
922impl PackedEastAsianTraditionalYearData {
923 const fn earliest_ny(related_iso: i32) -> RataDie {
932 calendrical_calculations::gregorian::fixed_from_gregorian(related_iso, 1, 19)
933 }
934
935 const fn new(
937 related_iso: i32,
938 month_lengths: [bool; 13],
939 leap_month: Option<u8>,
940 new_year: RataDie,
941 ) -> Self {
942 if let Some(l) = leap_month {
945 debug_assert!(2 <= l && l <= 13, "Leap month indices must be 2 <= i <= 13");
946 } else {
947 debug_assert!(
948 !month_lengths[12],
949 "Last month length should not be set for non-leap years"
950 )
951 }
952
953 let ny_offset = new_year.since(Self::earliest_ny(related_iso));
954
955 #[cfg(debug_assertions)]
956 let out_of_valid_astronomical_range = WELL_BEHAVED_ASTRONOMICAL_RANGE.start.to_i64_date()
957 > new_year.to_i64_date()
958 || new_year.to_i64_date() > WELL_BEHAVED_ASTRONOMICAL_RANGE.end.to_i64_date();
959
960 #[cfg(debug_assertions)]
963 debug_assert!(
964 ny_offset >= 0 || out_of_valid_astronomical_range,
965 "Year offset too small to store"
966 );
967 #[cfg(debug_assertions)]
969 debug_assert!(
970 ny_offset < 35 || out_of_valid_astronomical_range,
971 "Year offset too big to store"
972 );
973
974 let ny_offset = ny_offset & (0x40 - 1);
982
983 let mut all = 0u32; let mut month = 0;
986 while month < month_lengths.len() {
987 #[allow(clippy::indexing_slicing)] if month_lengths[month] {
989 all |= 1 << month as u32;
990 }
991 month += 1;
992 }
993 let leap_month_idx = if let Some(leap_month_idx) = leap_month {
994 leap_month_idx
995 } else {
996 0
997 };
998 all |= (leap_month_idx as u32) << (8 + 5);
999 all |= (ny_offset as u32) << (16 + 1);
1000 let le = all.to_le_bytes();
1001 Self(le[0], le[1], le[2])
1002 }
1003
1004 fn new_year(self, related_iso: i32) -> RataDie {
1005 Self::earliest_ny(related_iso) + (self.2 as i64 >> 1)
1006 }
1007
1008 fn leap_month(self) -> Option<u8> {
1009 let bits = (self.1 >> 5) + ((self.2 & 0b1) << 3);
1010
1011 (bits != 0).then_some(bits)
1012 }
1013
1014 fn month_has_30_days(self, month: u8) -> bool {
1016 let months = u16::from_le_bytes([self.0, self.1]);
1017 months & (1 << (month - 1) as u16) != 0
1018 }
1019
1020 fn last_day_of_month(self, month: u8) -> u16 {
1022 let months = u16::from_le_bytes([self.0, self.1]);
1023 let mut prev_month_lengths = 29 * month as u16;
1025 let long_month_bits = months & ((1 << month as u16) - 1);
1029 prev_month_lengths += long_month_bits.count_ones().try_into().unwrap_or(0);
1030 prev_month_lengths
1031 }
1032
1033 fn days_in_year(self) -> u16 {
1034 self.last_day_of_month(12 + self.leap_month().is_some() as u8)
1035 }
1036}
1037
1038#[cfg(test)]
1039mod test {
1040 use super::*;
1041 use crate::options::{DateFromFieldsOptions, Overflow};
1042 use crate::types::DateFields;
1043 use calendrical_calculations::{gregorian::fixed_from_gregorian, rata_die::RataDie};
1044 use std::collections::BTreeMap;
1045
1046 #[test]
1047 fn test_chinese_from_rd() {
1048 #[derive(Debug)]
1049 struct TestCase {
1050 rd: i64,
1051 expected_year: i32,
1052 expected_month: u8,
1053 expected_day: u8,
1054 }
1055
1056 let cases = [
1057 TestCase {
1058 rd: -964192,
1059 expected_year: -2639,
1060 expected_month: 1,
1061 expected_day: 1,
1062 },
1063 TestCase {
1064 rd: -963838,
1065 expected_year: -2638,
1066 expected_month: 1,
1067 expected_day: 1,
1068 },
1069 TestCase {
1070 rd: -963129,
1071 expected_year: -2637,
1072 expected_month: 13,
1073 expected_day: 1,
1074 },
1075 TestCase {
1076 rd: -963100,
1077 expected_year: -2637,
1078 expected_month: 13,
1079 expected_day: 30,
1080 },
1081 TestCase {
1082 rd: -963099,
1083 expected_year: -2636,
1084 expected_month: 1,
1085 expected_day: 1,
1086 },
1087 TestCase {
1088 rd: 738700,
1089 expected_year: 2023,
1090 expected_month: 6,
1091 expected_day: 12,
1092 },
1093 TestCase {
1094 rd: fixed_from_gregorian(2319, 2, 20).to_i64_date(),
1095 expected_year: 2318,
1096 expected_month: 13,
1097 expected_day: 30,
1098 },
1099 TestCase {
1100 rd: fixed_from_gregorian(2319, 2, 21).to_i64_date(),
1101 expected_year: 2319,
1102 expected_month: 1,
1103 expected_day: 1,
1104 },
1105 TestCase {
1106 rd: 738718,
1107 expected_year: 2023,
1108 expected_month: 6,
1109 expected_day: 30,
1110 },
1111 TestCase {
1112 rd: 738747,
1113 expected_year: 2023,
1114 expected_month: 7,
1115 expected_day: 29,
1116 },
1117 TestCase {
1118 rd: 738748,
1119 expected_year: 2023,
1120 expected_month: 8,
1121 expected_day: 1,
1122 },
1123 TestCase {
1124 rd: 738865,
1125 expected_year: 2023,
1126 expected_month: 11,
1127 expected_day: 29,
1128 },
1129 TestCase {
1130 rd: 738895,
1131 expected_year: 2023,
1132 expected_month: 12,
1133 expected_day: 29,
1134 },
1135 TestCase {
1136 rd: 738925,
1137 expected_year: 2023,
1138 expected_month: 13,
1139 expected_day: 30,
1140 },
1141 TestCase {
1142 rd: 0,
1143 expected_year: 0,
1144 expected_month: 11,
1145 expected_day: 19,
1146 },
1147 TestCase {
1148 rd: -1,
1149 expected_year: 0,
1150 expected_month: 11,
1151 expected_day: 18,
1152 },
1153 TestCase {
1154 rd: -365,
1155 expected_year: -1,
1156 expected_month: 12,
1157 expected_day: 9,
1158 },
1159 TestCase {
1160 rd: 100,
1161 expected_year: 1,
1162 expected_month: 3,
1163 expected_day: 1,
1164 },
1165 ];
1166
1167 for case in cases {
1168 let rata_die = RataDie::new(case.rd);
1169
1170 let chinese = Date::from_rata_die(rata_die, ChineseTraditional::new());
1171 assert_eq!(
1172 case.expected_year,
1173 chinese.extended_year(),
1174 "Chinese from RD failed, case: {case:?}"
1175 );
1176 assert_eq!(
1177 case.expected_month,
1178 chinese.month().ordinal,
1179 "Chinese from RD failed, case: {case:?}"
1180 );
1181 assert_eq!(
1182 case.expected_day,
1183 chinese.day_of_month().0,
1184 "Chinese from RD failed, case: {case:?}"
1185 );
1186 }
1187 }
1188
1189 #[test]
1190 fn test_rd_from_chinese() {
1191 #[derive(Debug)]
1192 struct TestCase {
1193 year: i32,
1194 ordinal_month: u8,
1195 month_code: types::MonthCode,
1196 day: u8,
1197 expected: i64,
1198 }
1199
1200 let cases = [
1201 TestCase {
1202 year: 2023,
1203 ordinal_month: 6,
1204 month_code: types::MonthCode::new_normal(5).unwrap(),
1205 day: 6,
1206 expected: 738694,
1208 },
1209 TestCase {
1210 year: -2636,
1211 ordinal_month: 1,
1212 month_code: types::MonthCode::new_normal(1).unwrap(),
1213 day: 1,
1214 expected: -963099,
1215 },
1216 ];
1217
1218 for case in cases {
1219 let date = Date::try_new_from_codes(
1220 None,
1221 case.year,
1222 case.month_code,
1223 case.day,
1224 ChineseTraditional::new(),
1225 )
1226 .unwrap();
1227 #[allow(deprecated)] {
1229 assert_eq!(
1230 Date::try_new_chinese_with_calendar(
1231 case.year,
1232 case.ordinal_month,
1233 case.day,
1234 ChineseTraditional::new()
1235 ),
1236 Ok(date)
1237 );
1238 }
1239 let rd = date.to_rata_die().to_i64_date();
1240 let expected = case.expected;
1241 assert_eq!(rd, expected, "RD from Chinese failed, with expected: {expected} and calculated: {rd}, for test case: {case:?}");
1242 }
1243 }
1244
1245 #[test]
1246 fn test_rd_chinese_roundtrip() {
1247 let mut rd = -1963020;
1248 let max_rd = 1963020;
1249 let mut iters = 0;
1250 let max_iters = 560;
1251 while rd < max_rd && iters < max_iters {
1252 let rata_die = RataDie::new(rd);
1253
1254 let chinese = Date::from_rata_die(rata_die, ChineseTraditional::new());
1255 let result = chinese.to_rata_die();
1256 assert_eq!(result, rata_die, "Failed roundtrip RD -> Chinese -> RD for RD: {rata_die:?}, with calculated: {result:?} from Chinese date:\n{chinese:?}");
1257
1258 rd += 7043;
1259 iters += 1;
1260 }
1261 }
1262
1263 #[test]
1264 fn test_chinese_epoch() {
1265 let iso = Date::try_new_iso(-2636, 2, 15).unwrap();
1266
1267 let chinese = iso.to_calendar(ChineseTraditional::new());
1268
1269 assert_eq!(chinese.cyclic_year().related_iso, -2636);
1270 assert_eq!(chinese.month().ordinal, 1);
1271 assert_eq!(chinese.month().standard_code.0, "M01");
1272 assert_eq!(chinese.day_of_month().0, 1);
1273 assert_eq!(chinese.cyclic_year().year, 1);
1274 assert_eq!(chinese.cyclic_year().related_iso, -2636);
1275 }
1276
1277 #[test]
1278 fn test_iso_to_chinese_negative_years() {
1279 #[derive(Debug)]
1280 struct TestCase {
1281 iso_year: i32,
1282 iso_month: u8,
1283 iso_day: u8,
1284 expected_year: i32,
1285 expected_month: u8,
1286 expected_day: u8,
1287 }
1288
1289 let cases = [
1290 TestCase {
1291 iso_year: -2636,
1292 iso_month: 2,
1293 iso_day: 14,
1294 expected_year: -2637,
1295 expected_month: 13,
1296 expected_day: 30,
1297 },
1298 TestCase {
1299 iso_year: -2636,
1300 iso_month: 1,
1301 iso_day: 15,
1302 expected_year: -2637,
1303 expected_month: 12,
1304 expected_day: 29,
1305 },
1306 ];
1307
1308 for case in cases {
1309 let iso = Date::try_new_iso(case.iso_year, case.iso_month, case.iso_day).unwrap();
1310
1311 let chinese = iso.to_calendar(ChineseTraditional::new());
1312 assert_eq!(
1313 case.expected_year,
1314 chinese.cyclic_year().related_iso,
1315 "ISO to Chinese failed for case: {case:?}"
1316 );
1317 assert_eq!(
1318 case.expected_month,
1319 chinese.month().ordinal,
1320 "ISO to Chinese failed for case: {case:?}"
1321 );
1322 assert_eq!(
1323 case.expected_day,
1324 chinese.day_of_month().0,
1325 "ISO to Chinese failed for case: {case:?}"
1326 );
1327 }
1328 }
1329
1330 #[test]
1331 fn test_chinese_leap_months() {
1332 let expected = [
1333 (1933, 6),
1334 (1938, 8),
1335 (1984, 11),
1336 (2009, 6),
1337 (2017, 7),
1338 (2028, 6),
1339 ];
1340
1341 for case in expected {
1342 let year = case.0;
1343 let expected_month = case.1;
1344 let iso = Date::try_new_iso(year, 6, 1).unwrap();
1345
1346 let chinese_date = iso.to_calendar(ChineseTraditional::new());
1347 assert!(
1348 chinese_date.is_in_leap_year(),
1349 "{year} should be a leap year"
1350 );
1351 let new_year = chinese_date.inner.0.year.new_year();
1352 assert_eq!(
1353 expected_month,
1354 chinese_based::get_leap_month_from_new_year::<chinese_based::Chinese>(new_year),
1355 "{year} have leap month {expected_month}"
1356 );
1357 }
1358 }
1359
1360 #[test]
1361 fn test_month_days() {
1362 let year = ChineseTraditional::new().0.year_data(2023);
1363 let cases = [
1364 (1, 29),
1365 (2, 30),
1366 (3, 29),
1367 (4, 29),
1368 (5, 30),
1369 (6, 30),
1370 (7, 29),
1371 (8, 30),
1372 (9, 30),
1373 (10, 29),
1374 (11, 30),
1375 (12, 29),
1376 (13, 30),
1377 ];
1378 for case in cases {
1379 let days_in_month = EastAsianTraditional::<China>::days_in_provided_month(year, case.0);
1380 assert_eq!(
1381 case.1, days_in_month,
1382 "month_days test failed for case: {case:?}"
1383 );
1384 }
1385 }
1386
1387 #[test]
1388 fn test_ordinal_to_month_code() {
1389 #[derive(Debug)]
1390 struct TestCase {
1391 year: i32,
1392 month: u8,
1393 day: u8,
1394 expected_code: &'static str,
1395 }
1396
1397 let cases = [
1398 TestCase {
1399 year: 2023,
1400 month: 1,
1401 day: 9,
1402 expected_code: "M12",
1403 },
1404 TestCase {
1405 year: 2023,
1406 month: 2,
1407 day: 9,
1408 expected_code: "M01",
1409 },
1410 TestCase {
1411 year: 2023,
1412 month: 3,
1413 day: 9,
1414 expected_code: "M02",
1415 },
1416 TestCase {
1417 year: 2023,
1418 month: 4,
1419 day: 9,
1420 expected_code: "M02L",
1421 },
1422 TestCase {
1423 year: 2023,
1424 month: 5,
1425 day: 9,
1426 expected_code: "M03",
1427 },
1428 TestCase {
1429 year: 2023,
1430 month: 6,
1431 day: 9,
1432 expected_code: "M04",
1433 },
1434 TestCase {
1435 year: 2023,
1436 month: 7,
1437 day: 9,
1438 expected_code: "M05",
1439 },
1440 TestCase {
1441 year: 2023,
1442 month: 8,
1443 day: 9,
1444 expected_code: "M06",
1445 },
1446 TestCase {
1447 year: 2023,
1448 month: 9,
1449 day: 9,
1450 expected_code: "M07",
1451 },
1452 TestCase {
1453 year: 2023,
1454 month: 10,
1455 day: 9,
1456 expected_code: "M08",
1457 },
1458 TestCase {
1459 year: 2023,
1460 month: 11,
1461 day: 9,
1462 expected_code: "M09",
1463 },
1464 TestCase {
1465 year: 2023,
1466 month: 12,
1467 day: 9,
1468 expected_code: "M10",
1469 },
1470 TestCase {
1471 year: 2024,
1472 month: 1,
1473 day: 9,
1474 expected_code: "M11",
1475 },
1476 TestCase {
1477 year: 2024,
1478 month: 2,
1479 day: 9,
1480 expected_code: "M12",
1481 },
1482 TestCase {
1483 year: 2024,
1484 month: 2,
1485 day: 10,
1486 expected_code: "M01",
1487 },
1488 ];
1489
1490 for case in cases {
1491 let iso = Date::try_new_iso(case.year, case.month, case.day).unwrap();
1492 let chinese = iso.to_calendar(ChineseTraditional::new());
1493 let result_code = chinese.month().standard_code.0;
1494 let expected_code = case.expected_code.to_string();
1495 assert_eq!(
1496 expected_code, result_code,
1497 "Month codes did not match for test case: {case:?}"
1498 );
1499 }
1500 }
1501
1502 #[test]
1503 fn test_month_code_to_ordinal() {
1504 let cal = ChineseTraditional::new();
1505 let reject = DateFromFieldsOptions {
1506 overflow: Some(Overflow::Reject),
1507 ..Default::default()
1508 };
1509 let year = cal.year_info_from_extended(2023);
1510 let leap_month = year.packed.leap_month().unwrap();
1511 for ordinal in 1..=13 {
1512 let code = ValidMonthCode::new_unchecked(
1513 ordinal - (ordinal >= leap_month) as u8,
1514 ordinal == leap_month,
1515 );
1516 assert_eq!(
1517 cal.ordinal_month_from_code(&year, code, reject),
1518 Ok(ordinal),
1519 "Code to ordinal failed for year: {}, code: {ordinal}",
1520 year.related_iso
1521 );
1522 }
1523 }
1524
1525 #[test]
1526 fn check_invalid_month_code_to_ordinal() {
1527 let cal = ChineseTraditional::new();
1528 let reject = DateFromFieldsOptions {
1529 overflow: Some(Overflow::Reject),
1530 ..Default::default()
1531 };
1532 for year in [4659, 4660] {
1533 let year = cal.year_info_from_extended(year);
1534 for (code, error) in [
1535 (
1536 ValidMonthCode::new_unchecked(4, true),
1537 MonthCodeError::NotInYear,
1538 ),
1539 (
1540 ValidMonthCode::new_unchecked(13, false),
1541 MonthCodeError::NotInCalendar,
1542 ),
1543 ] {
1544 assert_eq!(
1545 cal.ordinal_month_from_code(&year, code, reject),
1546 Err(error),
1547 "Invalid month code failed for year: {}, code: {code:?}",
1548 year.related_iso,
1549 );
1550 }
1551 }
1552 }
1553
1554 #[test]
1555 fn test_iso_chinese_roundtrip() {
1556 for i in -1000..=1000 {
1557 let year = i;
1558 let month = i as u8 % 12 + 1;
1559 let day = i as u8 % 28 + 1;
1560 let iso = Date::try_new_iso(year, month, day).unwrap();
1561 let chinese = iso.to_calendar(ChineseTraditional::new());
1562 let result = chinese.to_calendar(Iso);
1563 assert_eq!(iso, result, "ISO to Chinese roundtrip failed!\nIso: {iso:?}\nChinese: {chinese:?}\nResult: {result:?}");
1564 }
1565 }
1566
1567 fn check_cyclic_and_rel_iso(year: i32) {
1568 let iso = Date::try_new_iso(year, 6, 6).unwrap();
1569 let chinese = iso.to_calendar(ChineseTraditional::new());
1570 let korean = iso.to_calendar(KoreanTraditional::new());
1571 let chinese_year = chinese.cyclic_year();
1572 let korean_year = korean.cyclic_year();
1573 assert_eq!(
1574 chinese_year, korean_year,
1575 "Cyclic year failed for year: {year}"
1576 );
1577 let chinese_rel_iso = chinese_year.related_iso;
1578 let korean_rel_iso = korean_year.related_iso;
1579 assert_eq!(
1580 chinese_rel_iso, korean_rel_iso,
1581 "Rel. ISO year equality failed for year: {year}"
1582 );
1583 assert_eq!(korean_rel_iso, year, "Korean Rel. ISO failed!");
1584 }
1585
1586 #[test]
1587 fn test_cyclic_same_as_chinese_near_present_day() {
1588 for year in 1923..=2123 {
1589 check_cyclic_and_rel_iso(year);
1590 }
1591 }
1592
1593 #[test]
1594 fn test_cyclic_same_as_chinese_near_rd_zero() {
1595 for year in -100..=100 {
1596 check_cyclic_and_rel_iso(year);
1597 }
1598 }
1599
1600 #[test]
1601 fn test_iso_to_korean_roundtrip() {
1602 let mut rd = -1963020;
1603 let max_rd = 1963020;
1604 let mut iters = 0;
1605 let max_iters = 560;
1606 while rd < max_rd && iters < max_iters {
1607 let rata_die = RataDie::new(rd);
1608 let iso = Date::from_rata_die(rata_die, Iso);
1609 let korean = iso.to_calendar(KoreanTraditional::new());
1610 let result = korean.to_calendar(Iso);
1611 assert_eq!(
1612 iso, result,
1613 "Failed roundtrip ISO -> Korean -> ISO for RD: {rd}"
1614 );
1615
1616 rd += 7043;
1617 iters += 1;
1618 }
1619 }
1620
1621 #[test]
1622 fn test_from_fields_constrain() {
1623 let fields = DateFields {
1624 day: Some(31),
1625 month_code: Some(b"M01"),
1626 extended_year: Some(1972),
1627 ..Default::default()
1628 };
1629 let options = DateFromFieldsOptions {
1630 overflow: Some(Overflow::Constrain),
1631 ..Default::default()
1632 };
1633
1634 let cal = ChineseTraditional::new();
1635 let date = Date::try_from_fields(fields, options, cal).unwrap();
1636 assert_eq!(
1637 date.day_of_month().0,
1638 29,
1639 "Day was successfully constrained"
1640 );
1641
1642 let fields = DateFields {
1644 day: Some(1),
1645 month_code: Some(b"M01L"),
1646 extended_year: Some(2022),
1647 ..Default::default()
1648 };
1649 let date = Date::try_from_fields(fields, options, cal).unwrap();
1650 assert_eq!(
1651 date.month().standard_code.0,
1652 "M01",
1653 "Month was successfully constrained"
1654 );
1655 }
1656
1657 #[test]
1658 fn test_from_fields_regress_7049() {
1659 let fields = DateFields {
1662 extended_year: Some(889192448),
1663 ordinal_month: Some(1),
1664 day: Some(1),
1665 ..Default::default()
1666 };
1667 let options = DateFromFieldsOptions {
1668 overflow: Some(Overflow::Reject),
1669 ..Default::default()
1670 };
1671
1672 let cal = ChineseTraditional::new();
1673 assert!(matches!(
1674 Date::try_from_fields(fields, options, cal).unwrap_err(),
1675 DateFromFieldsError::Range { .. }
1676 ));
1677 }
1678
1679 #[test]
1680 #[ignore] fn test_against_hong_kong_observatory_data() {
1682 use crate::{cal::Gregorian, Date};
1683
1684 let mut related_iso = 1900;
1685 let mut lunar_month = ValidMonthCode::new_unchecked(11, false);
1686
1687 for year in 1901..=2100 {
1688 println!("Validating year {year}...");
1689
1690 for line in ureq::get(&format!(
1691 "https://www.hko.gov.hk/en/gts/time/calendar/text/files/T{year}e.txt"
1692 ))
1693 .call()
1694 .unwrap()
1695 .body_mut()
1696 .read_to_string()
1697 .unwrap()
1698 .split('\n')
1699 {
1700 if !line.starts_with(['1', '2']) {
1701 continue;
1703 }
1704
1705 let mut fields = line.split_ascii_whitespace();
1706
1707 let mut gregorian = fields.next().unwrap().split('/');
1708 let gregorian = Date::try_new_gregorian(
1709 gregorian.next().unwrap().parse().unwrap(),
1710 gregorian.next().unwrap().parse().unwrap(),
1711 gregorian.next().unwrap().parse().unwrap(),
1712 )
1713 .unwrap();
1714
1715 let day_or_lunar_month = fields.next().unwrap();
1716
1717 let lunar_day = if fields.next().is_some_and(|s| s.contains("Lunar")) {
1718 let new_lunar_month = day_or_lunar_month
1719 .split_once(['s', 'n', 'r', 't'])
1721 .unwrap()
1722 .0
1723 .parse()
1724 .unwrap();
1725 lunar_month = ValidMonthCode::new_unchecked(
1726 new_lunar_month,
1727 new_lunar_month == lunar_month.number(),
1728 );
1729 if new_lunar_month == 1 {
1730 related_iso += 1;
1731 }
1732 1
1733 } else {
1734 day_or_lunar_month.parse().unwrap()
1735 };
1736
1737 let chinese = Date::try_new_from_codes(
1738 None,
1739 related_iso,
1740 lunar_month.to_month_code(),
1741 lunar_day,
1742 ChineseTraditional::new(),
1743 )
1744 .unwrap();
1745
1746 assert_eq!(
1747 gregorian,
1748 chinese.to_calendar(Gregorian),
1749 "{line}, {chinese:?}"
1750 );
1751 }
1752 }
1753 }
1754
1755 #[test]
1756 #[ignore] fn test_against_kasi_data() {
1758 use crate::{cal::Gregorian, types::MonthCode, Date};
1759
1760 let uri = "https://gist.githubusercontent.com/Manishearth/d8c94a7df22a9eacefc4472a5805322e/raw/e1ea3b0aa52428686bb3a9cd0f262878515e16c1/resolved.json";
1762
1763 #[derive(serde::Deserialize)]
1764 struct Golden(BTreeMap<i32, BTreeMap<MonthCode, MonthData>>);
1765
1766 #[derive(serde::Deserialize)]
1767 struct MonthData {
1768 start_date: String,
1769 }
1770
1771 let json = ureq::get(uri)
1772 .call()
1773 .unwrap()
1774 .body_mut()
1775 .read_to_string()
1776 .unwrap();
1777
1778 let golden = serde_json::from_str::<Golden>(&json).unwrap();
1779
1780 for (&year, months) in &golden.0 {
1781 if year == 1899 || year == 2050 {
1782 continue;
1783 }
1784 for (&month, month_data) in months {
1785 let mut gregorian = month_data.start_date.split('-');
1786 let gregorian = Date::try_new_gregorian(
1787 gregorian.next().unwrap().parse().unwrap(),
1788 gregorian.next().unwrap().parse().unwrap(),
1789 gregorian.next().unwrap().parse().unwrap(),
1790 )
1791 .unwrap();
1792
1793 assert_eq!(
1794 Date::try_new_from_codes(None, year, month, 1, KoreanTraditional::new())
1795 .unwrap()
1796 .to_calendar(Gregorian),
1797 gregorian
1798 );
1799 }
1800 }
1801 }
1802
1803 #[test]
1804 #[ignore]
1805 fn generate_reference_years() {
1806 generate_reference_years_for(ChineseTraditional::new());
1807 generate_reference_years_for(KoreanTraditional::new());
1808 fn generate_reference_years_for<R: Rules + Copy>(calendar: EastAsianTraditional<R>) {
1809 use crate::Date;
1810
1811 println!("Reference years for {calendar:?}:");
1812 let reference_year_end = Date::from_rata_die(
1813 crate::cal::abstract_gregorian::LAST_DAY_OF_REFERENCE_YEAR,
1814 calendar,
1815 );
1816 let year_1900_start = Date::try_new_gregorian(1900, 1, 1)
1817 .unwrap()
1818 .to_calendar(calendar);
1819 let year_2035_end = Date::try_new_gregorian(2035, 12, 31)
1820 .unwrap()
1821 .to_calendar(calendar);
1822 for month in 1..=12 {
1823 for leap in [false, true] {
1824 'outer: for long in [false, true] {
1825 for (start_year, start_month, end_year, end_month, by) in [
1826 (
1827 reference_year_end.extended_year(),
1828 reference_year_end.month().month_number(),
1829 year_1900_start.extended_year(),
1830 year_1900_start.month().month_number(),
1831 -1,
1832 ),
1833 (
1834 reference_year_end.extended_year(),
1835 reference_year_end.month().month_number(),
1836 year_2035_end.extended_year(),
1837 year_2035_end.month().month_number(),
1838 1,
1839 ),
1840 (
1841 year_1900_start.extended_year(),
1842 year_1900_start.month().month_number(),
1843 -10000,
1844 1,
1845 -1,
1846 ),
1847 ] {
1848 let mut year = start_year;
1849 while year * by < end_year * by {
1850 if year == start_year
1851 && month as i32 * by <= start_month as i32 * by
1852 || year == end_year
1853 && month as i32 * by >= end_month as i32 * by
1854 {
1855 year += by;
1856 continue;
1857 }
1858 let data = calendar.0.year_data(year);
1859 let leap_month = data.packed.leap_month().unwrap_or(15);
1860 let ordinal_month = if leap && month + 1 == leap_month {
1861 month + 1
1862 } else {
1863 month + (month + 1 > leap_month) as u8
1864 };
1865 if (!long || data.packed.month_has_30_days(ordinal_month))
1866 && (!leap || month + 1 == leap_month)
1867 {
1868 println!("({month}, {leap:?}, {long:?}) => {year},");
1869 continue 'outer;
1870 }
1871 year += by;
1872 }
1873 }
1874 println!("({month}, {leap:?}, {long:?}) => todo!(),")
1875 }
1876 }
1877 }
1878 }
1879 }
1880
1881 #[test]
1882 fn test_roundtrip_packed() {
1883 fn packed_roundtrip_single(
1884 month_lengths: [bool; 13],
1885 leap_month_idx: Option<u8>,
1886 ny_offset: i64,
1887 ) {
1888 let ny =
1889 calendrical_calculations::gregorian::fixed_from_gregorian(1000, 1, 1) + ny_offset;
1890 let packed =
1891 PackedEastAsianTraditionalYearData::new(1000, month_lengths, leap_month_idx, ny);
1892
1893 assert_eq!(
1894 ny,
1895 packed.new_year(1000),
1896 "Roundtrip with {month_lengths:?}, {leap_month_idx:?}, {ny_offset}"
1897 );
1898 assert_eq!(
1899 leap_month_idx,
1900 packed.leap_month(),
1901 "Roundtrip with {month_lengths:?}, {leap_month_idx:?}, {ny_offset}"
1902 );
1903 assert_eq!(
1904 month_lengths,
1905 core::array::from_fn(|i| packed.month_has_30_days(i as u8 + 1)),
1906 "Roundtrip with {month_lengths:?}, {leap_month_idx:?}, {ny_offset}"
1907 );
1908 }
1909
1910 const SHORT: [bool; 13] = [false; 13];
1911 const LONG: [bool; 13] = [true; 13];
1912 const ALTERNATING1: [bool; 13] = [
1913 false, true, false, true, false, true, false, true, false, true, false, true, false,
1914 ];
1915 const ALTERNATING2: [bool; 13] = [
1916 true, false, true, false, true, false, true, false, true, false, true, false, false,
1917 ];
1918 const RANDOM1: [bool; 13] = [
1919 true, true, false, false, true, true, false, true, true, true, true, false, false,
1920 ];
1921 const RANDOM2: [bool; 13] = [
1922 false, true, true, true, true, false, true, true, true, false, false, true, false,
1923 ];
1924 packed_roundtrip_single(SHORT, None, 18 + 5);
1925 packed_roundtrip_single(SHORT, None, 18 + 10);
1926 packed_roundtrip_single(SHORT, Some(11), 18 + 15);
1927 packed_roundtrip_single(LONG, Some(12), 18 + 15);
1928 packed_roundtrip_single(ALTERNATING1, None, 18 + 2);
1929 packed_roundtrip_single(ALTERNATING1, Some(3), 18 + 5);
1930 packed_roundtrip_single(ALTERNATING2, None, 18 + 9);
1931 packed_roundtrip_single(ALTERNATING2, Some(7), 18 + 26);
1932 packed_roundtrip_single(RANDOM1, None, 18 + 29);
1933 packed_roundtrip_single(RANDOM1, Some(12), 18 + 29);
1934 packed_roundtrip_single(RANDOM1, Some(2), 18 + 21);
1935 packed_roundtrip_single(RANDOM2, None, 18 + 25);
1936 packed_roundtrip_single(RANDOM2, Some(2), 18 + 19);
1937 packed_roundtrip_single(RANDOM2, Some(5), 18 + 2);
1938 packed_roundtrip_single(RANDOM2, Some(12), 18 + 5);
1939 }
1940}