1use crate::cal::chinese_based::{ChineseBasedPrecomputedData, ChineseBasedWithDataLoading};
22use crate::cal::iso::{Iso, IsoDateInner};
23use crate::calendar_arithmetic::PrecomputedDataSource;
24use crate::calendar_arithmetic::{ArithmeticDate, CalendarArithmetic};
25use crate::error::DateError;
26use crate::provider::chinese_based::CalendarDangiV1;
27use crate::types::CyclicYear;
28use crate::AsCalendar;
29use crate::{types, Calendar, Date};
30use calendrical_calculations::chinese_based;
31use calendrical_calculations::rata_die::RataDie;
32use core::cmp::Ordering;
33use icu_provider::prelude::*;
34
35#[derive(Clone, Debug, Default)]
75pub struct Dangi {
76 data: Option<DataPayload<CalendarDangiV1>>,
77}
78
79#[derive(Debug, Eq, PartialEq, PartialOrd, Ord)]
81pub struct DangiDateInner(ArithmeticDate<Dangi>);
82
83impl Copy for DangiDateInner {}
85impl Clone for DangiDateInner {
86 fn clone(&self) -> Self {
87 *self
88 }
89}
90
91impl PartialEq for Dangi {
94 fn eq(&self, _: &Self) -> bool {
95 true
96 }
97}
98impl Eq for Dangi {}
99#[allow(clippy::non_canonical_partial_ord_impl)] impl PartialOrd for Dangi {
101 fn partial_cmp(&self, _: &Self) -> Option<Ordering> {
102 Some(Ordering::Equal)
103 }
104}
105
106impl Ord for Dangi {
107 fn cmp(&self, _: &Self) -> Ordering {
108 Ordering::Equal
109 }
110}
111
112impl Dangi {
113 #[cfg(feature = "compiled_data")]
119 pub const fn new() -> Self {
120 Self {
121 data: Some(DataPayload::from_static_ref(
122 crate::provider::Baked::SINGLETON_CALENDAR_DANGI_V1,
123 )),
124 }
125 }
126
127 icu_provider::gen_buffer_data_constructors!(() -> error: DataError,
128 functions: [
129 new: skip,
130 try_new_with_buffer_provider,
131 try_new_unstable,
132 Self,
133 ]);
134
135 #[doc = icu_provider::gen_buffer_unstable_docs!(UNSTABLE, Self::new)]
136 pub fn try_new_unstable<D: DataProvider<CalendarDangiV1> + ?Sized>(
137 provider: &D,
138 ) -> Result<Self, DataError> {
139 Ok(Self {
140 data: Some(provider.load(Default::default())?.payload),
141 })
142 }
143
144 pub fn new_always_calculating() -> Self {
146 Dangi { data: None }
147 }
148
149 pub(crate) const DEBUG_NAME: &'static str = "Dangi";
150}
151
152impl crate::cal::scaffold::UnstableSealed for Dangi {}
153impl Calendar for Dangi {
154 type DateInner = DangiDateInner;
155 type Year = CyclicYear;
156
157 fn from_codes(
158 &self,
159 era: Option<&str>,
160 year: i32,
161 month_code: crate::types::MonthCode,
162 day: u8,
163 ) -> Result<Self::DateInner, DateError> {
164 match era {
165 None => {}
166 _ => return Err(DateError::UnknownEra),
167 }
168
169 let year = self.get_precomputed_data().load_or_compute_info(year);
170
171 let Some(month) = year.parse_month_code(month_code) else {
172 return Err(DateError::UnknownMonthCode(month_code));
173 };
174
175 year.validate_md(month, day)?;
176
177 Ok(DangiDateInner(ArithmeticDate::new_unchecked(
178 year, month, day,
179 )))
180 }
181
182 fn from_rata_die(&self, rd: RataDie) -> Self::DateInner {
183 let iso = Iso.from_rata_die(rd);
184 let y = self
185 .get_precomputed_data()
186 .load_or_compute_info_for_rd(rd, iso.0);
187 let (m, d) = y.md_from_rd(rd);
188 DangiDateInner(ArithmeticDate::new_unchecked(y, m, d))
189 }
190
191 fn to_rata_die(&self, date: &Self::DateInner) -> RataDie {
192 date.0.year.rd_from_md(date.0.month, date.0.day)
193 }
194
195 fn from_iso(&self, iso: IsoDateInner) -> Self::DateInner {
196 let rd = Iso.to_rata_die(&iso);
197 let y = self
198 .get_precomputed_data()
199 .load_or_compute_info_for_rd(rd, iso.0);
200 let (m, d) = y.md_from_rd(rd);
201 DangiDateInner(ArithmeticDate::new_unchecked(y, m, d))
202 }
203
204 fn to_iso(&self, date: &Self::DateInner) -> IsoDateInner {
205 Iso.from_rata_die(self.to_rata_die(date))
206 }
207
208 fn months_in_year(&self, date: &Self::DateInner) -> u8 {
209 date.0.months_in_year()
210 }
211
212 fn days_in_year(&self, date: &Self::DateInner) -> u16 {
213 date.0.days_in_year()
214 }
215
216 fn days_in_month(&self, date: &Self::DateInner) -> u8 {
217 date.0.days_in_month()
218 }
219
220 fn offset_date(&self, date: &mut Self::DateInner, offset: crate::DateDuration<Self>) {
221 date.0.offset_date(offset, &self.get_precomputed_data());
222 }
223
224 fn until(
225 &self,
226 date1: &Self::DateInner,
227 date2: &Self::DateInner,
228 _calendar2: &Self,
229 largest_unit: crate::DateDurationUnit,
230 smallest_unit: crate::DateDurationUnit,
231 ) -> crate::DateDuration<Self> {
232 date1.0.until(date2.0, largest_unit, smallest_unit)
233 }
234
235 fn debug_name(&self) -> &'static str {
236 Self::DEBUG_NAME
237 }
238
239 fn year_info(&self, date: &Self::DateInner) -> Self::Year {
240 let year = date.0.year;
241 CyclicYear {
242 year: (year.related_iso as i64 - 4).rem_euclid(60) as u8 + 1,
243 related_iso: year.related_iso,
244 }
245 }
246
247 fn extended_year(&self, date: &Self::DateInner) -> i32 {
248 chinese_based::extended_from_iso::<chinese_based::Dangi>(date.0.year.related_iso)
249 }
250
251 fn is_in_leap_year(&self, date: &Self::DateInner) -> bool {
252 Self::provided_year_is_leap(date.0.year)
253 }
254
255 fn month(&self, date: &Self::DateInner) -> crate::types::MonthInfo {
256 date.0.year.month(date.0.month)
257 }
258
259 fn day_of_month(&self, date: &Self::DateInner) -> crate::types::DayOfMonth {
260 date.0.day_of_month()
261 }
262
263 fn day_of_year(&self, date: &Self::DateInner) -> crate::types::DayOfYear {
264 types::DayOfYear(date.0.year.day_of_year(date.0.month, date.0.day))
265 }
266
267 fn calendar_algorithm(&self) -> Option<crate::preferences::CalendarAlgorithm> {
268 Some(crate::preferences::CalendarAlgorithm::Dangi)
269 }
270}
271
272impl<A: AsCalendar<Calendar = Dangi>> Date<A> {
273 pub fn try_new_dangi_with_calendar(
296 related_iso_year: i32,
297 month: u8,
298 day: u8,
299 calendar: A,
300 ) -> Result<Date<A>, DateError> {
301 let year = calendar
302 .as_calendar()
303 .get_precomputed_data()
304 .load_or_compute_info(related_iso_year);
305 year.validate_md(month, day)?;
306 Ok(Date::from_raw(
307 DangiDateInner(ArithmeticDate::new_unchecked(year, month, day)),
308 calendar,
309 ))
310 }
311}
312
313impl ChineseBasedWithDataLoading for Dangi {
314 type CB = calendrical_calculations::chinese_based::Dangi;
315 fn get_precomputed_data(&self) -> ChineseBasedPrecomputedData<Self::CB> {
316 ChineseBasedPrecomputedData::new(self.data.as_ref().map(|d| d.get()))
317 }
318}
319
320#[cfg(test)]
321mod test {
322
323 use super::*;
324 use crate::cal::Chinese;
325 use calendrical_calculations::rata_die::RataDie;
326
327 fn do_twice(
329 dangi_calculating: &Dangi,
330 dangi_cached: &Dangi,
331 test: impl Fn(crate::Ref<Dangi>, &'static str),
332 ) {
333 test(crate::Ref(dangi_calculating), "calculating");
334 test(crate::Ref(dangi_cached), "cached");
335 }
336
337 fn check_cyclic_and_rel_iso(year: i32) {
338 let iso = Date::try_new_iso(year, 6, 6).unwrap();
339 let chinese = iso.to_calendar(Chinese::new_always_calculating());
340 let dangi = iso.to_calendar(Dangi::new_always_calculating());
341 let chinese_year = chinese.cyclic_year();
342 let korean_year = dangi.cyclic_year();
343 assert_eq!(
344 chinese_year, korean_year,
345 "Cyclic year failed for year: {year}"
346 );
347 let chinese_rel_iso = chinese_year.related_iso;
348 let korean_rel_iso = korean_year.related_iso;
349 assert_eq!(
350 chinese_rel_iso, korean_rel_iso,
351 "Rel. ISO year equality failed for year: {year}"
352 );
353 assert_eq!(korean_rel_iso, year, "Dangi Rel. ISO failed!");
354 }
355
356 #[test]
357 fn test_cyclic_same_as_chinese_near_present_day() {
358 for year in 1923..=2123 {
359 check_cyclic_and_rel_iso(year);
360 }
361 }
362
363 #[test]
364 fn test_cyclic_same_as_chinese_near_rd_zero() {
365 for year in -100..=100 {
366 check_cyclic_and_rel_iso(year);
367 }
368 }
369
370 #[test]
371 fn test_iso_to_dangi_roundtrip() {
372 let mut rd = -1963020;
373 let max_rd = 1963020;
374 let mut iters = 0;
375 let max_iters = 560;
376 let dangi_calculating = Dangi::new_always_calculating();
377 let dangi_cached = Dangi::new();
378 while rd < max_rd && iters < max_iters {
379 let rata_die = RataDie::new(rd);
380 let iso = Date::from_rata_die(rata_die, Iso);
381 do_twice(&dangi_calculating, &dangi_cached, |dangi, calendar_type| {
382 let korean = iso.to_calendar(dangi);
383 let result = korean.to_calendar(Iso);
384 assert_eq!(
385 iso, result,
386 "[{calendar_type}] Failed roundtrip ISO -> Dangi -> ISO for RD: {rd}"
387 );
388 });
389
390 rd += 7043;
391 iters += 1;
392 }
393 }
394
395 #[test]
396 fn test_dangi_consistent_with_icu() {
397 #[derive(Debug)]
404 struct TestCase {
405 iso_year: i32,
406 iso_month: u8,
407 iso_day: u8,
408 expected_rel_iso: i32,
409 expected_cyclic: u8,
410 expected_month: u8,
411 expected_day: u8,
412 }
413
414 let cases = [
415 TestCase {
416 iso_year: 4321,
418 iso_month: 1,
419 iso_day: 23,
420 expected_rel_iso: 4320,
421 expected_cyclic: 57,
422 expected_month: 13,
423 expected_day: 12,
424 },
425 TestCase {
426 iso_year: 3649,
427 iso_month: 9,
428 iso_day: 20,
429 expected_rel_iso: 3649,
430 expected_cyclic: 46,
431 expected_month: 9,
432 expected_day: 1,
433 },
434 TestCase {
435 iso_year: 3333,
436 iso_month: 3,
437 iso_day: 3,
438 expected_rel_iso: 3333,
439 expected_cyclic: 30,
440 expected_month: 1,
441 expected_day: 25,
442 },
443 TestCase {
444 iso_year: 3000,
445 iso_month: 3,
446 iso_day: 30,
447 expected_rel_iso: 3000,
448 expected_cyclic: 57,
449 expected_month: 3,
450 expected_day: 3,
451 },
452 TestCase {
453 iso_year: 2772,
454 iso_month: 7,
455 iso_day: 27,
456 expected_rel_iso: 2772,
457 expected_cyclic: 9,
458 expected_month: 7,
459 expected_day: 5,
460 },
461 TestCase {
462 iso_year: 2525,
463 iso_month: 2,
464 iso_day: 25,
465 expected_rel_iso: 2525,
466 expected_cyclic: 2,
467 expected_month: 2,
468 expected_day: 3,
469 },
470 TestCase {
471 iso_year: 2345,
472 iso_month: 3,
473 iso_day: 21,
474 expected_rel_iso: 2345,
475 expected_cyclic: 2,
476 expected_month: 2,
477 expected_day: 17,
478 },
479 TestCase {
480 iso_year: 2222,
481 iso_month: 2,
482 iso_day: 22,
483 expected_rel_iso: 2222,
484 expected_cyclic: 59,
485 expected_month: 1,
486 expected_day: 11,
487 },
488 TestCase {
489 iso_year: 2167,
490 iso_month: 6,
491 iso_day: 22,
492 expected_rel_iso: 2167,
493 expected_cyclic: 4,
494 expected_month: 5,
495 expected_day: 6,
496 },
497 TestCase {
498 iso_year: 2121,
499 iso_month: 2,
500 iso_day: 12,
501 expected_rel_iso: 2120,
502 expected_cyclic: 17,
503 expected_month: 13,
504 expected_day: 25,
505 },
506 TestCase {
507 iso_year: 2080,
508 iso_month: 12,
509 iso_day: 31,
510 expected_rel_iso: 2080,
511 expected_cyclic: 37,
512 expected_month: 12,
513 expected_day: 21,
514 },
515 TestCase {
516 iso_year: 2030,
517 iso_month: 3,
518 iso_day: 20,
519 expected_rel_iso: 2030,
520 expected_cyclic: 47,
521 expected_month: 2,
522 expected_day: 17,
523 },
524 TestCase {
525 iso_year: 2027,
526 iso_month: 2,
527 iso_day: 7,
528 expected_rel_iso: 2027,
529 expected_cyclic: 44,
530 expected_month: 1,
531 expected_day: 1,
532 },
533 TestCase {
534 iso_year: 2023,
535 iso_month: 7,
536 iso_day: 1,
537 expected_rel_iso: 2023,
538 expected_cyclic: 40,
539 expected_month: 6,
540 expected_day: 14,
541 },
542 TestCase {
543 iso_year: 2022,
544 iso_month: 3,
545 iso_day: 1,
546 expected_rel_iso: 2022,
547 expected_cyclic: 39,
548 expected_month: 1,
549 expected_day: 29,
550 },
551 TestCase {
552 iso_year: 2021,
553 iso_month: 2,
554 iso_day: 1,
555 expected_rel_iso: 2020,
556 expected_cyclic: 37,
557 expected_month: 13,
558 expected_day: 20,
559 },
560 TestCase {
561 iso_year: 2016,
562 iso_month: 3,
563 iso_day: 30,
564 expected_rel_iso: 2016,
565 expected_cyclic: 33,
566 expected_month: 2,
567 expected_day: 22,
568 },
569 TestCase {
570 iso_year: 2016,
571 iso_month: 7,
572 iso_day: 30,
573 expected_rel_iso: 2016,
574 expected_cyclic: 33,
575 expected_month: 6,
576 expected_day: 27,
577 },
578 TestCase {
579 iso_year: 2015,
580 iso_month: 9,
581 iso_day: 22,
582 expected_rel_iso: 2015,
583 expected_cyclic: 32,
584 expected_month: 8,
585 expected_day: 10,
586 },
587 TestCase {
588 iso_year: 2013,
589 iso_month: 10,
590 iso_day: 1,
591 expected_rel_iso: 2013,
592 expected_cyclic: 30,
593 expected_month: 8,
594 expected_day: 27,
595 },
596 TestCase {
597 iso_year: 2010,
598 iso_month: 2,
599 iso_day: 1,
600 expected_rel_iso: 2009,
601 expected_cyclic: 26,
602 expected_month: 13,
603 expected_day: 18,
604 },
605 TestCase {
606 iso_year: 2000,
607 iso_month: 8,
608 iso_day: 30,
609 expected_rel_iso: 2000,
610 expected_cyclic: 17,
611 expected_month: 8,
612 expected_day: 2,
613 },
614 TestCase {
615 iso_year: 1990,
616 iso_month: 11,
617 iso_day: 11,
618 expected_rel_iso: 1990,
619 expected_cyclic: 7,
620 expected_month: 10,
621 expected_day: 24,
622 },
623 TestCase {
624 iso_year: 1970,
625 iso_month: 6,
626 iso_day: 10,
627 expected_rel_iso: 1970,
628 expected_cyclic: 47,
629 expected_month: 5,
630 expected_day: 7,
631 },
632 TestCase {
633 iso_year: 1970,
634 iso_month: 1,
635 iso_day: 1,
636 expected_rel_iso: 1969,
637 expected_cyclic: 46,
638 expected_month: 11,
639 expected_day: 24,
640 },
641 TestCase {
642 iso_year: 1941,
643 iso_month: 12,
644 iso_day: 7,
645 expected_rel_iso: 1941,
646 expected_cyclic: 18,
647 expected_month: 11,
648 expected_day: 19,
649 },
650 TestCase {
651 iso_year: 1812,
652 iso_month: 5,
653 iso_day: 4,
654 expected_rel_iso: 1812,
655 expected_cyclic: 9,
656 expected_month: 3,
657 expected_day: 24,
658 },
659 TestCase {
660 iso_year: 1655,
661 iso_month: 6,
662 iso_day: 15,
663 expected_rel_iso: 1655,
664 expected_cyclic: 32,
665 expected_month: 5,
666 expected_day: 12,
667 },
668 TestCase {
669 iso_year: 1333,
670 iso_month: 3,
671 iso_day: 10,
672 expected_rel_iso: 1333,
673 expected_cyclic: 10,
674 expected_month: 2,
675 expected_day: 16,
676 },
677 TestCase {
678 iso_year: 1000,
679 iso_month: 10,
680 iso_day: 10,
681 expected_rel_iso: 1000,
682 expected_cyclic: 37,
683 expected_month: 9,
684 expected_day: 5,
685 },
686 TestCase {
687 iso_year: 842,
688 iso_month: 2,
689 iso_day: 15,
690 expected_rel_iso: 841,
691 expected_cyclic: 58,
692 expected_month: 13,
693 expected_day: 28,
694 },
695 TestCase {
696 iso_year: 101,
697 iso_month: 1,
698 iso_day: 10,
699 expected_rel_iso: 100,
700 expected_cyclic: 37,
701 expected_month: 12,
702 expected_day: 24,
703 },
704 TestCase {
705 iso_year: -1,
706 iso_month: 3,
707 iso_day: 28,
708 expected_rel_iso: -1,
709 expected_cyclic: 56,
710 expected_month: 2,
711 expected_day: 25,
712 },
713 TestCase {
714 iso_year: -3,
715 iso_month: 2,
716 iso_day: 28,
717 expected_rel_iso: -3,
718 expected_cyclic: 54,
719 expected_month: 2,
720 expected_day: 5,
721 },
722 TestCase {
723 iso_year: -365,
724 iso_month: 7,
725 iso_day: 24,
726 expected_rel_iso: -365,
727 expected_cyclic: 52,
728 expected_month: 6,
729 expected_day: 24,
730 },
731 TestCase {
732 iso_year: -999,
733 iso_month: 9,
734 iso_day: 9,
735 expected_rel_iso: -999,
736 expected_cyclic: 18,
737 expected_month: 7,
738 expected_day: 27,
739 },
740 TestCase {
741 iso_year: -1500,
742 iso_month: 1,
743 iso_day: 5,
744 expected_rel_iso: -1501,
745 expected_cyclic: 56,
746 expected_month: 12,
747 expected_day: 2,
748 },
749 TestCase {
750 iso_year: -2332,
751 iso_month: 3,
752 iso_day: 1,
753 expected_rel_iso: -2332,
754 expected_cyclic: 5,
755 expected_month: 1,
756 expected_day: 16,
757 },
758 TestCase {
759 iso_year: -2332,
760 iso_month: 2,
761 iso_day: 15,
762 expected_rel_iso: -2332,
763 expected_cyclic: 5,
764 expected_month: 1,
765 expected_day: 1,
766 },
767 TestCase {
768 iso_year: -2332,
770 iso_month: 2,
771 iso_day: 14,
772 expected_rel_iso: -2333,
773 expected_cyclic: 4,
774 expected_month: 13,
775 expected_day: 30,
776 },
777 TestCase {
778 iso_year: -2332,
780 iso_month: 1,
781 iso_day: 17,
782 expected_rel_iso: -2333,
783 expected_cyclic: 4,
784 expected_month: 13,
785 expected_day: 2,
786 },
787 TestCase {
788 iso_year: -2332,
790 iso_month: 1,
791 iso_day: 16,
792 expected_rel_iso: -2333,
793 expected_cyclic: 4,
794 expected_month: 13,
795 expected_day: 1,
796 },
797 TestCase {
798 iso_year: -2332,
799 iso_month: 1,
800 iso_day: 15,
801 expected_rel_iso: -2333,
802 expected_cyclic: 4,
803 expected_month: 12,
804 expected_day: 29,
805 },
806 TestCase {
807 iso_year: -2332,
808 iso_month: 1,
809 iso_day: 1,
810 expected_rel_iso: -2333,
811 expected_cyclic: 4,
812 expected_month: 12,
813 expected_day: 15,
814 },
815 TestCase {
816 iso_year: -2333,
817 iso_month: 1,
818 iso_day: 16,
819 expected_rel_iso: -2334,
820 expected_cyclic: 3,
821 expected_month: 12,
822 expected_day: 19,
823 },
824 TestCase {
825 iso_year: -2333,
826 iso_month: 1,
827 iso_day: 27,
828 expected_rel_iso: -2333,
829 expected_cyclic: 4,
830 expected_month: 1,
831 expected_day: 1,
832 },
833 TestCase {
834 iso_year: -2333,
835 iso_month: 1,
836 iso_day: 26,
837 expected_rel_iso: -2334,
838 expected_cyclic: 3,
839 expected_month: 12,
840 expected_day: 29,
841 },
842 TestCase {
843 iso_year: -2600,
844 iso_month: 9,
845 iso_day: 16,
846 expected_rel_iso: -2600,
847 expected_cyclic: 37,
848 expected_month: 8,
849 expected_day: 16,
850 },
851 TestCase {
852 iso_year: -2855,
853 iso_month: 2,
854 iso_day: 3,
855 expected_rel_iso: -2856,
856 expected_cyclic: 21,
857 expected_month: 12,
858 expected_day: 30,
859 },
860 TestCase {
861 iso_year: -3000,
863 iso_month: 5,
864 iso_day: 15,
865 expected_rel_iso: -3000,
866 expected_cyclic: 57,
867 expected_month: 4,
868 expected_day: 1,
869 },
870 TestCase {
871 iso_year: -3649,
873 iso_month: 9,
874 iso_day: 20,
875 expected_rel_iso: -3649,
876 expected_cyclic: 8,
877 expected_month: 8,
878 expected_day: 10,
879 },
880 TestCase {
881 iso_year: -3649,
883 iso_month: 3,
884 iso_day: 30,
885 expected_rel_iso: -3649,
886 expected_cyclic: 8,
887 expected_month: 2,
888 expected_day: 14,
889 },
890 TestCase {
891 iso_year: -3650,
893 iso_month: 3,
894 iso_day: 30,
895 expected_rel_iso: -3650,
896 expected_cyclic: 7,
897 expected_month: 3,
898 expected_day: 3,
899 },
900 ];
901
902 let dangi_calculating = Dangi::new_always_calculating();
903 let dangi_cached = Dangi::new();
904
905 for case in cases {
906 let iso = Date::try_new_iso(case.iso_year, case.iso_month, case.iso_day).unwrap();
907 do_twice(&dangi_calculating, &dangi_cached, |dangi, calendar_type| {
908 let dangi = iso.to_calendar(dangi);
909 let dangi_cyclic = dangi.cyclic_year();
910 let dangi_month = dangi.month().ordinal;
911 let dangi_day = dangi.day_of_month().0;
912
913 assert_eq!(
914 dangi_cyclic.related_iso, case.expected_rel_iso,
915 "[{calendar_type}] Related ISO failed for test case: {case:?}"
916 );
917 assert_eq!(
918 dangi_cyclic.year, case.expected_cyclic,
919 "[{calendar_type}] Cyclic year failed for test case: {case:?}"
920 );
921 assert_eq!(
922 dangi_month, case.expected_month,
923 "[{calendar_type}] Month failed for test case: {case:?}"
924 );
925 assert_eq!(
926 dangi_day, case.expected_day,
927 "[{calendar_type}] Day failed for test case: {case:?}"
928 );
929 });
930 }
931 }
932}