icu_calendar/
dangi.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 Korean Dangi calendar.
6//!
7//! ```rust
8//! use icu::calendar::dangi::Dangi;
9//! use icu::calendar::{Date, DateTime, Ref};
10//!
11//! let dangi = Dangi::new();
12//! let dangi = Ref(&dangi); // to avoid cloning
13//!
14//! // `Date` type
15//! let dangi_date = Date::try_new_dangi_date_with_calendar(4356, 6, 6, dangi)
16//!     .expect("Failed to initialize Dangi Date instance.");
17//!
18//! // `DateTime` type
19//! let dangi_datetime = DateTime::try_new_dangi_datetime_with_calendar(
20//!     4356, 6, 6, 13, 1, 0, dangi,
21//! )
22//! .expect("Failed to initialize Dangi DateTime instance.");
23//!
24//! // `Date` checks
25//! assert_eq!(dangi_date.year().number, 4356);
26//! assert_eq!(dangi_date.year().related_iso, Some(2023));
27//! assert_eq!(dangi_date.year().cyclic.unwrap().get(), 40);
28//! assert_eq!(dangi_date.month().ordinal, 6);
29//! assert_eq!(dangi_date.day_of_month().0, 6);
30//!
31//! // `DateTime` checks
32//! assert_eq!(dangi_datetime.date.year().number, 4356);
33//! assert_eq!(dangi_datetime.date.year().related_iso, Some(2023));
34//! assert_eq!(dangi_datetime.date.year().cyclic.unwrap().get(), 40);
35//! assert_eq!(dangi_datetime.date.month().ordinal, 6);
36//! assert_eq!(dangi_datetime.date.day_of_month().0, 6);
37//! assert_eq!(dangi_datetime.time.hour.number(), 13);
38//! assert_eq!(dangi_datetime.time.minute.number(), 1);
39//! assert_eq!(dangi_datetime.time.second.number(), 0);
40//! ```
41
42use crate::calendar_arithmetic::CalendarArithmetic;
43use crate::calendar_arithmetic::PrecomputedDataSource;
44use crate::chinese_based::{
45    chinese_based_ordinal_lunar_month_from_code, ChineseBasedPrecomputedData,
46    ChineseBasedWithDataLoading, ChineseBasedYearInfo,
47};
48use crate::provider::chinese_based::DangiCacheV1Marker;
49use crate::AsCalendar;
50use crate::{
51    chinese_based::ChineseBasedDateInner,
52    types::{self, Era, FormattableYear},
53    AnyCalendarKind, Calendar, CalendarError, Date, DateTime, Iso, Time,
54};
55use core::cmp::Ordering;
56use core::num::NonZeroU8;
57use icu_provider::prelude::*;
58use tinystr::tinystr;
59
60/// The Dangi Calendar
61///
62/// The Dangi Calendar is a lunisolar calendar used traditionally in North and South Korea.
63/// It is often used today to track important cultural events and holidays like Seollal
64/// (Korean lunar new year). It is similar to the Chinese lunar calendar (see `Chinese`),
65/// except that observations are based in Korea (currently UTC+9) rather than China (UTC+8).
66/// This can cause some differences; for example, 2012 was a leap year, but in the Dangi
67/// calendar the leap month was 3, while in the Chinese calendar the leap month was 4.
68///
69/// This calendar is currently in a preview state: formatting for this calendar is not
70/// going to be perfect.
71///
72/// ```rust
73/// use icu::calendar::{chinese::Chinese, dangi::Dangi, Date};
74/// use tinystr::tinystr;
75///
76/// let iso_a = Date::try_new_iso_date(2012, 4, 23).unwrap();
77/// let dangi_a = iso_a.to_calendar(Dangi::new());
78/// let chinese_a = iso_a.to_calendar(Chinese::new());
79///
80/// assert_eq!(dangi_a.month().code.0, tinystr!(4, "M03L"));
81/// assert_eq!(chinese_a.month().code.0, tinystr!(4, "M04"));
82///
83/// let iso_b = Date::try_new_iso_date(2012, 5, 23).unwrap();
84/// let dangi_b = iso_b.to_calendar(Dangi::new());
85/// let chinese_b = iso_b.to_calendar(Chinese::new());
86///
87/// assert_eq!(dangi_b.month().code.0, tinystr!(4, "M04"));
88/// assert_eq!(chinese_b.month().code.0, tinystr!(4, "M04L"));
89/// ```
90/// # Era codes
91///
92/// This Calendar supports a single era code "dangi" based on the year -2332 ISO (2333 BCE) as year 1. Typically
93/// years will be formatted using cyclic years and the related ISO year.
94///
95/// # Month codes
96///
97/// This calendar is a lunisolar calendar. It supports regular month codes `"M01" - "M12"` as well
98/// as leap month codes `"M01L" - "M12L"`.
99#[derive(Clone, Debug, Default)]
100pub struct Dangi {
101    data: Option<DataPayload<DangiCacheV1Marker>>,
102}
103
104/// The inner date type used for representing [`Date`]s of [`Dangi`]. See [`Date`] and [`Dangi`] for more detail.
105#[derive(Debug, Eq, PartialEq, PartialOrd, Ord)]
106pub struct DangiDateInner(ChineseBasedDateInner<Dangi>);
107
108type Inner = ChineseBasedDateInner<Dangi>;
109
110// we want these impls without the `C: Copy/Clone` bounds
111impl Copy for DangiDateInner {}
112impl Clone for DangiDateInner {
113    fn clone(&self) -> Self {
114        *self
115    }
116}
117
118// These impls just make custom derives on types containing C
119// work. They're basically no-ops
120impl PartialEq for Dangi {
121    fn eq(&self, _: &Self) -> bool {
122        true
123    }
124}
125impl Eq for Dangi {}
126#[allow(clippy::non_canonical_partial_ord_impl)] // this is intentional
127impl PartialOrd for Dangi {
128    fn partial_cmp(&self, _: &Self) -> Option<Ordering> {
129        Some(Ordering::Equal)
130    }
131}
132
133impl Ord for Dangi {
134    fn cmp(&self, _: &Self) -> Ordering {
135        Ordering::Equal
136    }
137}
138
139impl Dangi {
140    /// Creates a new [`Dangi`] with some precomputed calendrical calculations.
141    ///
142    /// ✨ *Enabled with the `compiled_data` Cargo feature.*
143    ///
144    /// [📚 Help choosing a constructor](icu_provider::constructors)
145    #[cfg(feature = "compiled_data")]
146    pub const fn new() -> Self {
147        Self {
148            data: Some(DataPayload::from_static_ref(
149                crate::provider::Baked::SINGLETON_CALENDAR_DANGICACHE_V1,
150            )),
151        }
152    }
153
154    icu_provider::gen_any_buffer_data_constructors!(locale: skip, options: skip, error: CalendarError,
155        #[cfg(skip)]
156        functions: [
157            new,
158            try_new_with_any_provider,
159            try_new_with_buffer_provider,
160            try_new_unstable,
161            Self,
162    ]);
163
164    #[doc = icu_provider::gen_any_buffer_unstable_docs!(UNSTABLE, Self::new)]
165    pub fn try_new_unstable<D: DataProvider<DangiCacheV1Marker> + ?Sized>(
166        provider: &D,
167    ) -> Result<Self, CalendarError> {
168        Ok(Self {
169            data: Some(provider.load(Default::default())?.take_payload()?),
170        })
171    }
172
173    /// Construct a new [`Dangi`] without any precomputed calendrical calculations.
174    pub fn new_always_calculating() -> Self {
175        Dangi { data: None }
176    }
177
178    pub(crate) const DEBUG_NAME: &'static str = "Dangi";
179}
180
181impl Calendar for Dangi {
182    type DateInner = DangiDateInner;
183
184    fn date_from_codes(
185        &self,
186        era: crate::types::Era,
187        year: i32,
188        month_code: crate::types::MonthCode,
189        day: u8,
190    ) -> Result<Self::DateInner, crate::Error> {
191        let year_info = self.get_precomputed_data().load_or_compute_info(year);
192
193        let month = if let Some(ordinal) =
194            chinese_based_ordinal_lunar_month_from_code(month_code, year_info)
195        {
196            ordinal
197        } else {
198            return Err(CalendarError::UnknownMonthCode(
199                month_code.0,
200                self.debug_name(),
201            ));
202        };
203
204        if era.0 != tinystr!(16, "dangi") {
205            return Err(CalendarError::UnknownEra(era.0, self.debug_name()));
206        }
207
208        let arithmetic = Inner::new_from_ordinals(year, month, day, year_info);
209        Ok(DangiDateInner(ChineseBasedDateInner(arithmetic?)))
210    }
211
212    fn date_from_iso(&self, iso: Date<crate::Iso>) -> Self::DateInner {
213        let fixed = Iso::fixed_from_iso(iso.inner);
214        DangiDateInner(Inner::chinese_based_date_from_fixed(
215            self,
216            fixed,
217            iso.inner.0,
218        ))
219    }
220
221    fn date_to_iso(&self, date: &Self::DateInner) -> Date<crate::Iso> {
222        let fixed = Inner::fixed_from_chinese_based_date_inner(date.0);
223        Iso::iso_from_fixed(fixed)
224    }
225
226    fn months_in_year(&self, date: &Self::DateInner) -> u8 {
227        date.0.months_in_year_inner()
228    }
229
230    fn days_in_year(&self, date: &Self::DateInner) -> u16 {
231        date.0.days_in_year_inner()
232    }
233
234    fn days_in_month(&self, date: &Self::DateInner) -> u8 {
235        date.0.days_in_month_inner()
236    }
237
238    fn offset_date(&self, date: &mut Self::DateInner, offset: crate::DateDuration<Self>) {
239        date.0 .0.offset_date(offset, &self.get_precomputed_data());
240    }
241
242    fn until(
243        &self,
244        date1: &Self::DateInner,
245        date2: &Self::DateInner,
246        _calendar2: &Self,
247        largest_unit: crate::DateDurationUnit,
248        smallest_unit: crate::DateDurationUnit,
249    ) -> crate::DateDuration<Self> {
250        date1.0 .0.until(date2.0 .0, largest_unit, smallest_unit)
251    }
252
253    fn debug_name(&self) -> &'static str {
254        Self::DEBUG_NAME
255    }
256
257    fn year(&self, date: &Self::DateInner) -> crate::types::FormattableYear {
258        Self::format_dangi_year(date.0 .0.year, Some(date.0 .0.year_info))
259    }
260
261    fn is_in_leap_year(&self, date: &Self::DateInner) -> bool {
262        Self::is_leap_year(date.0 .0.year, date.0 .0.year_info)
263    }
264
265    fn month(&self, date: &Self::DateInner) -> crate::types::FormattableMonth {
266        date.0.month()
267    }
268
269    fn day_of_month(&self, date: &Self::DateInner) -> crate::types::DayOfMonth {
270        types::DayOfMonth(date.0 .0.day as u32)
271    }
272
273    fn day_of_year_info(&self, date: &Self::DateInner) -> crate::types::DayOfYearInfo {
274        let prev_year = date.0 .0.year.saturating_sub(1);
275        let next_year = date.0 .0.year.saturating_add(1);
276        types::DayOfYearInfo {
277            day_of_year: date.0 .0.day_of_year(),
278            days_in_year: date.0.days_in_year_inner(),
279            prev_year: Self::format_dangi_year(prev_year, None),
280            days_in_prev_year: date.0.days_in_prev_year(),
281            next_year: Self::format_dangi_year(next_year, None),
282        }
283    }
284
285    fn day_of_week(&self, date: &Self::DateInner) -> crate::types::IsoWeekday {
286        self.date_to_iso(date).day_of_week()
287    }
288
289    fn any_calendar_kind(&self) -> Option<crate::AnyCalendarKind> {
290        Some(AnyCalendarKind::Dangi)
291    }
292}
293
294impl<A: AsCalendar<Calendar = Dangi>> Date<A> {
295    /// Construct a new Dangi date from a `year`, `month`, and `day`.
296    /// `year` represents the Chinese year counted infinitely with -2332 (2333 BCE) as year 1;
297    /// `month` represents the month of the year ordinally (ex. if it is a leap year, the last month will be 13, not 12);
298    /// `day` indicates day of month.
299    ///
300    /// This date will not use any precomputed calendrical calculations,
301    /// one that loads such data from a provider will be added in the future (#3933)
302    ///
303    /// ```rust
304    /// use icu::calendar::dangi::Dangi;
305    /// use icu::calendar::Date;
306    ///
307    /// let dangi = Dangi::new();
308    ///
309    /// let date_dangi = Date::try_new_dangi_date_with_calendar(4356, 6, 18, dangi)
310    ///     .expect("Failed to initialize Dangi Date instance.");
311    ///
312    /// assert_eq!(date_dangi.year().number, 4356);
313    /// assert_eq!(date_dangi.year().cyclic.unwrap().get(), 40);
314    /// assert_eq!(date_dangi.year().related_iso, Some(2023));
315    /// assert_eq!(date_dangi.month().ordinal, 6);
316    /// assert_eq!(date_dangi.day_of_month().0, 18);
317    /// ```
318    pub fn try_new_dangi_date_with_calendar(
319        year: i32,
320        month: u8,
321        day: u8,
322        calendar: A,
323    ) -> Result<Date<A>, CalendarError> {
324        let year_info = calendar
325            .as_calendar()
326            .get_precomputed_data()
327            .load_or_compute_info(year);
328        let arithmetic = Inner::new_from_ordinals(year, month, day, year_info);
329        Ok(Date::from_raw(
330            DangiDateInner(ChineseBasedDateInner(arithmetic?)),
331            calendar,
332        ))
333    }
334}
335
336impl<A: AsCalendar<Calendar = Dangi>> DateTime<A> {
337    /// Construct a new Dangi DateTime from integers. See `try_new_dangi_date_with_calendar`.
338    ///
339    /// This datetime will not use any precomputed calendrical calculations,
340    /// one that loads such data from a provider will be added in the future (#3933)
341    ///
342    /// ```rust
343    /// use icu::calendar::dangi::Dangi;
344    /// use icu::calendar::DateTime;
345    ///
346    /// let dangi = Dangi::new();
347    ///
348    /// let dangi_datetime = DateTime::try_new_dangi_datetime_with_calendar(
349    ///     4356, 6, 6, 13, 1, 0, dangi,
350    /// )
351    /// .expect("Failed to initialize Dangi DateTime instance.");
352    ///
353    /// assert_eq!(dangi_datetime.date.year().number, 4356);
354    /// assert_eq!(dangi_datetime.date.year().related_iso, Some(2023));
355    /// assert_eq!(dangi_datetime.date.year().cyclic.unwrap().get(), 40);
356    /// assert_eq!(dangi_datetime.date.month().ordinal, 6);
357    /// assert_eq!(dangi_datetime.date.day_of_month().0, 6);
358    /// assert_eq!(dangi_datetime.time.hour.number(), 13);
359    /// assert_eq!(dangi_datetime.time.minute.number(), 1);
360    /// assert_eq!(dangi_datetime.time.second.number(), 0);
361    /// ```
362    pub fn try_new_dangi_datetime_with_calendar(
363        year: i32,
364        month: u8,
365        day: u8,
366        hour: u8,
367        minute: u8,
368        second: u8,
369        calendar: A,
370    ) -> Result<DateTime<A>, CalendarError> {
371        Ok(DateTime {
372            date: Date::try_new_dangi_date_with_calendar(year, month, day, calendar)?,
373            time: Time::try_new(hour, minute, second, 0)?,
374        })
375    }
376}
377
378type DangiCB = calendrical_calculations::chinese_based::Dangi;
379impl ChineseBasedWithDataLoading for Dangi {
380    type CB = DangiCB;
381    fn get_precomputed_data(&self) -> ChineseBasedPrecomputedData<Self::CB> {
382        ChineseBasedPrecomputedData::new(self.data.as_ref().map(|d| d.get()))
383    }
384}
385
386impl Dangi {
387    /// Get a `FormattableYear` from an integer Dangi year; optionally, a `ChineseBasedYearInfo`
388    /// can be passed in for faster results.
389    fn format_dangi_year(
390        year: i32,
391        year_info_option: Option<ChineseBasedYearInfo>,
392    ) -> FormattableYear {
393        let era = Era(tinystr!(16, "dangi"));
394        let number = year;
395        // constant 364 from https://github.com/EdReingold/calendar-code2/blob/main/calendar.l#L5704
396        let cyclic = (number as i64 - 1 + 364).rem_euclid(60) as u8;
397        let cyclic = NonZeroU8::new(cyclic + 1); // 1-indexed
398        let rata_die_in_year = if let Some(info) = year_info_option {
399            info.new_year::<DangiCB>(year)
400        } else {
401            Inner::fixed_mid_year_from_year(number)
402        };
403        let iso_formattable_year = Iso::iso_from_fixed(rata_die_in_year).year();
404        let related_iso = Some(iso_formattable_year.number);
405        types::FormattableYear {
406            era,
407            number,
408            cyclic,
409            related_iso,
410        }
411    }
412}
413
414#[cfg(test)]
415mod test {
416
417    use super::*;
418    use crate::chinese::Chinese;
419    use calendrical_calculations::rata_die::RataDie;
420
421    /// Run a test twice, with two calendars
422    fn do_twice(
423        dangi_calculating: &Dangi,
424        dangi_cached: &Dangi,
425        test: impl Fn(crate::Ref<Dangi>, &'static str),
426    ) {
427        test(crate::Ref(dangi_calculating), "calculating");
428        test(crate::Ref(dangi_cached), "cached");
429    }
430
431    fn check_cyclic_and_rel_iso(year: i32) {
432        let iso = Date::try_new_iso_date(year, 6, 6).unwrap();
433        let chinese = iso.to_calendar(Chinese::new_always_calculating());
434        let dangi = iso.to_calendar(Dangi::new_always_calculating());
435        let chinese_year = chinese.year().cyclic;
436        let korean_year = dangi.year().cyclic;
437        assert_eq!(
438            chinese_year, korean_year,
439            "Cyclic year failed for year: {year}"
440        );
441        let chinese_rel_iso = chinese.year().related_iso;
442        let korean_rel_iso = dangi.year().related_iso;
443        assert_eq!(
444            chinese_rel_iso, korean_rel_iso,
445            "Rel. ISO year equality failed for year: {year}"
446        );
447        assert_eq!(korean_rel_iso, Some(year), "Dangi Rel. ISO failed!");
448    }
449
450    #[test]
451    fn test_cyclic_same_as_chinese_near_present_day() {
452        for year in 1923..=2123 {
453            check_cyclic_and_rel_iso(year);
454        }
455    }
456
457    #[test]
458    fn test_cyclic_same_as_chinese_near_rd_zero() {
459        for year in -100..=100 {
460            check_cyclic_and_rel_iso(year);
461        }
462    }
463
464    #[test]
465    fn test_iso_to_dangi_roundtrip() {
466        let mut fixed = -1963020;
467        let max_fixed = 1963020;
468        let mut iters = 0;
469        let max_iters = 560;
470        let dangi_calculating = Dangi::new_always_calculating();
471        let dangi_cached = Dangi::new();
472        while fixed < max_fixed && iters < max_iters {
473            let rata_die = RataDie::new(fixed);
474            let iso = Iso::iso_from_fixed(rata_die);
475            do_twice(&dangi_calculating, &dangi_cached, |dangi, calendar_type| {
476                let korean = iso.to_calendar(dangi);
477                let result = korean.to_calendar(Iso);
478                assert_eq!(
479                    iso, result,
480                    "[{calendar_type}] Failed roundtrip ISO -> Dangi -> ISO for fixed: {fixed}"
481                );
482            });
483
484            fixed += 7043;
485            iters += 1;
486        }
487    }
488
489    #[test]
490    fn test_dangi_consistent_with_icu() {
491        // Test cases for this test are derived from existing ICU Intl.DateTimeFormat. If there is a bug in ICU,
492        // these test cases may be affected, and this calendar's output may not be entirely valid.
493
494        // There are a number of test cases which do not match ICU for dates very far in the past or future,
495        // see #3709.
496
497        #[derive(Debug)]
498        struct TestCase {
499            iso_year: i32,
500            iso_month: u8,
501            iso_day: u8,
502            expected_rel_iso: i32,
503            expected_cyclic: u8,
504            expected_month: u32,
505            expected_day: u32,
506        }
507
508        let cases = [
509            TestCase {
510                // #3709: This test case fails to match ICU
511                iso_year: 4321,
512                iso_month: 1,
513                iso_day: 23,
514                expected_rel_iso: 4320,
515                expected_cyclic: 57,
516                expected_month: 13,
517                expected_day: 12,
518            },
519            TestCase {
520                iso_year: 3649,
521                iso_month: 9,
522                iso_day: 20,
523                expected_rel_iso: 3649,
524                expected_cyclic: 46,
525                expected_month: 9,
526                expected_day: 1,
527            },
528            TestCase {
529                iso_year: 3333,
530                iso_month: 3,
531                iso_day: 3,
532                expected_rel_iso: 3333,
533                expected_cyclic: 30,
534                expected_month: 1,
535                expected_day: 25,
536            },
537            TestCase {
538                iso_year: 3000,
539                iso_month: 3,
540                iso_day: 30,
541                expected_rel_iso: 3000,
542                expected_cyclic: 57,
543                expected_month: 3,
544                expected_day: 3,
545            },
546            TestCase {
547                iso_year: 2772,
548                iso_month: 7,
549                iso_day: 27,
550                expected_rel_iso: 2772,
551                expected_cyclic: 9,
552                expected_month: 7,
553                expected_day: 5,
554            },
555            TestCase {
556                iso_year: 2525,
557                iso_month: 2,
558                iso_day: 25,
559                expected_rel_iso: 2525,
560                expected_cyclic: 2,
561                expected_month: 2,
562                expected_day: 3,
563            },
564            TestCase {
565                iso_year: 2345,
566                iso_month: 3,
567                iso_day: 21,
568                expected_rel_iso: 2345,
569                expected_cyclic: 2,
570                expected_month: 2,
571                expected_day: 17,
572            },
573            TestCase {
574                iso_year: 2222,
575                iso_month: 2,
576                iso_day: 22,
577                expected_rel_iso: 2222,
578                expected_cyclic: 59,
579                expected_month: 1,
580                expected_day: 11,
581            },
582            TestCase {
583                iso_year: 2167,
584                iso_month: 6,
585                iso_day: 22,
586                expected_rel_iso: 2167,
587                expected_cyclic: 4,
588                expected_month: 5,
589                expected_day: 6,
590            },
591            TestCase {
592                iso_year: 2121,
593                iso_month: 2,
594                iso_day: 12,
595                expected_rel_iso: 2120,
596                expected_cyclic: 17,
597                expected_month: 13,
598                expected_day: 25,
599            },
600            TestCase {
601                iso_year: 2080,
602                iso_month: 12,
603                iso_day: 31,
604                expected_rel_iso: 2080,
605                expected_cyclic: 37,
606                expected_month: 12,
607                expected_day: 21,
608            },
609            TestCase {
610                iso_year: 2030,
611                iso_month: 3,
612                iso_day: 20,
613                expected_rel_iso: 2030,
614                expected_cyclic: 47,
615                expected_month: 2,
616                expected_day: 17,
617            },
618            TestCase {
619                iso_year: 2027,
620                iso_month: 2,
621                iso_day: 7,
622                expected_rel_iso: 2027,
623                expected_cyclic: 44,
624                expected_month: 1,
625                expected_day: 1,
626            },
627            TestCase {
628                iso_year: 2023,
629                iso_month: 7,
630                iso_day: 1,
631                expected_rel_iso: 2023,
632                expected_cyclic: 40,
633                expected_month: 6,
634                expected_day: 14,
635            },
636            TestCase {
637                iso_year: 2022,
638                iso_month: 3,
639                iso_day: 1,
640                expected_rel_iso: 2022,
641                expected_cyclic: 39,
642                expected_month: 1,
643                expected_day: 29,
644            },
645            TestCase {
646                iso_year: 2021,
647                iso_month: 2,
648                iso_day: 1,
649                expected_rel_iso: 2020,
650                expected_cyclic: 37,
651                expected_month: 13,
652                expected_day: 20,
653            },
654            TestCase {
655                iso_year: 2016,
656                iso_month: 3,
657                iso_day: 30,
658                expected_rel_iso: 2016,
659                expected_cyclic: 33,
660                expected_month: 2,
661                expected_day: 22,
662            },
663            TestCase {
664                iso_year: 2016,
665                iso_month: 7,
666                iso_day: 30,
667                expected_rel_iso: 2016,
668                expected_cyclic: 33,
669                expected_month: 6,
670                expected_day: 27,
671            },
672            TestCase {
673                iso_year: 2015,
674                iso_month: 9,
675                iso_day: 22,
676                expected_rel_iso: 2015,
677                expected_cyclic: 32,
678                expected_month: 8,
679                expected_day: 10,
680            },
681            TestCase {
682                iso_year: 2013,
683                iso_month: 10,
684                iso_day: 1,
685                expected_rel_iso: 2013,
686                expected_cyclic: 30,
687                expected_month: 8,
688                expected_day: 27,
689            },
690            TestCase {
691                iso_year: 2010,
692                iso_month: 2,
693                iso_day: 1,
694                expected_rel_iso: 2009,
695                expected_cyclic: 26,
696                expected_month: 13,
697                expected_day: 18,
698            },
699            TestCase {
700                iso_year: 2000,
701                iso_month: 8,
702                iso_day: 30,
703                expected_rel_iso: 2000,
704                expected_cyclic: 17,
705                expected_month: 8,
706                expected_day: 2,
707            },
708            TestCase {
709                iso_year: 1990,
710                iso_month: 11,
711                iso_day: 11,
712                expected_rel_iso: 1990,
713                expected_cyclic: 7,
714                expected_month: 10,
715                expected_day: 24,
716            },
717            TestCase {
718                iso_year: 1970,
719                iso_month: 6,
720                iso_day: 10,
721                expected_rel_iso: 1970,
722                expected_cyclic: 47,
723                expected_month: 5,
724                expected_day: 7,
725            },
726            TestCase {
727                iso_year: 1970,
728                iso_month: 1,
729                iso_day: 1,
730                expected_rel_iso: 1969,
731                expected_cyclic: 46,
732                expected_month: 11,
733                expected_day: 24,
734            },
735            TestCase {
736                iso_year: 1941,
737                iso_month: 12,
738                iso_day: 7,
739                expected_rel_iso: 1941,
740                expected_cyclic: 18,
741                expected_month: 11,
742                expected_day: 19,
743            },
744            TestCase {
745                iso_year: 1812,
746                iso_month: 5,
747                iso_day: 4,
748                expected_rel_iso: 1812,
749                expected_cyclic: 9,
750                expected_month: 3,
751                expected_day: 24,
752            },
753            TestCase {
754                iso_year: 1655,
755                iso_month: 6,
756                iso_day: 15,
757                expected_rel_iso: 1655,
758                expected_cyclic: 32,
759                expected_month: 5,
760                expected_day: 12,
761            },
762            TestCase {
763                iso_year: 1333,
764                iso_month: 3,
765                iso_day: 10,
766                expected_rel_iso: 1333,
767                expected_cyclic: 10,
768                expected_month: 2,
769                expected_day: 16,
770            },
771            TestCase {
772                iso_year: 1000,
773                iso_month: 10,
774                iso_day: 10,
775                expected_rel_iso: 1000,
776                expected_cyclic: 37,
777                expected_month: 9,
778                expected_day: 5,
779            },
780            TestCase {
781                iso_year: 842,
782                iso_month: 2,
783                iso_day: 15,
784                expected_rel_iso: 841,
785                expected_cyclic: 58,
786                expected_month: 13,
787                expected_day: 28,
788            },
789            TestCase {
790                iso_year: 101,
791                iso_month: 1,
792                iso_day: 10,
793                expected_rel_iso: 100,
794                expected_cyclic: 37,
795                expected_month: 12,
796                expected_day: 24,
797            },
798            TestCase {
799                iso_year: -1,
800                iso_month: 3,
801                iso_day: 28,
802                expected_rel_iso: -1,
803                expected_cyclic: 56,
804                expected_month: 2,
805                expected_day: 25,
806            },
807            TestCase {
808                iso_year: -3,
809                iso_month: 2,
810                iso_day: 28,
811                expected_rel_iso: -3,
812                expected_cyclic: 54,
813                expected_month: 2,
814                expected_day: 5,
815            },
816            TestCase {
817                iso_year: -365,
818                iso_month: 7,
819                iso_day: 24,
820                expected_rel_iso: -365,
821                expected_cyclic: 52,
822                expected_month: 6,
823                expected_day: 24,
824            },
825            TestCase {
826                iso_year: -999,
827                iso_month: 9,
828                iso_day: 9,
829                expected_rel_iso: -999,
830                expected_cyclic: 18,
831                expected_month: 7,
832                expected_day: 27,
833            },
834            TestCase {
835                iso_year: -1500,
836                iso_month: 1,
837                iso_day: 5,
838                expected_rel_iso: -1501,
839                expected_cyclic: 56,
840                expected_month: 12,
841                expected_day: 2,
842            },
843            TestCase {
844                iso_year: -2332,
845                iso_month: 3,
846                iso_day: 1,
847                expected_rel_iso: -2332,
848                expected_cyclic: 5,
849                expected_month: 1,
850                expected_day: 16,
851            },
852            TestCase {
853                iso_year: -2332,
854                iso_month: 2,
855                iso_day: 15,
856                expected_rel_iso: -2332,
857                expected_cyclic: 5,
858                expected_month: 1,
859                expected_day: 1,
860            },
861            TestCase {
862                // #3709: This test case fails to match ICU
863                iso_year: -2332,
864                iso_month: 2,
865                iso_day: 14,
866                expected_rel_iso: -2333,
867                expected_cyclic: 4,
868                expected_month: 13,
869                expected_day: 30,
870            },
871            TestCase {
872                // #3709: This test case fails to match ICU
873                iso_year: -2332,
874                iso_month: 1,
875                iso_day: 17,
876                expected_rel_iso: -2333,
877                expected_cyclic: 4,
878                expected_month: 13,
879                expected_day: 2,
880            },
881            TestCase {
882                // #3709: This test case fails to match ICU
883                iso_year: -2332,
884                iso_month: 1,
885                iso_day: 16,
886                expected_rel_iso: -2333,
887                expected_cyclic: 4,
888                expected_month: 13,
889                expected_day: 1,
890            },
891            TestCase {
892                iso_year: -2332,
893                iso_month: 1,
894                iso_day: 15,
895                expected_rel_iso: -2333,
896                expected_cyclic: 4,
897                expected_month: 12,
898                expected_day: 29,
899            },
900            TestCase {
901                iso_year: -2332,
902                iso_month: 1,
903                iso_day: 1,
904                expected_rel_iso: -2333,
905                expected_cyclic: 4,
906                expected_month: 12,
907                expected_day: 15,
908            },
909            TestCase {
910                iso_year: -2333,
911                iso_month: 1,
912                iso_day: 16,
913                expected_rel_iso: -2334,
914                expected_cyclic: 3,
915                expected_month: 12,
916                expected_day: 19,
917            },
918            TestCase {
919                iso_year: -2333,
920                iso_month: 1,
921                iso_day: 27,
922                expected_rel_iso: -2333,
923                expected_cyclic: 4,
924                expected_month: 1,
925                expected_day: 1,
926            },
927            TestCase {
928                iso_year: -2333,
929                iso_month: 1,
930                iso_day: 26,
931                expected_rel_iso: -2334,
932                expected_cyclic: 3,
933                expected_month: 12,
934                expected_day: 29,
935            },
936            TestCase {
937                iso_year: -2600,
938                iso_month: 9,
939                iso_day: 16,
940                expected_rel_iso: -2600,
941                expected_cyclic: 37,
942                expected_month: 8,
943                expected_day: 16,
944            },
945            TestCase {
946                iso_year: -2855,
947                iso_month: 2,
948                iso_day: 3,
949                expected_rel_iso: -2856,
950                expected_cyclic: 21,
951                expected_month: 12,
952                expected_day: 30,
953            },
954            TestCase {
955                // #3709: This test case fails to match ICU
956                iso_year: -3000,
957                iso_month: 5,
958                iso_day: 15,
959                expected_rel_iso: -3000,
960                expected_cyclic: 57,
961                expected_month: 4,
962                expected_day: 1,
963            },
964            TestCase {
965                // #3709: This test case fails to match ICU
966                iso_year: -3649,
967                iso_month: 9,
968                iso_day: 20,
969                expected_rel_iso: -3649,
970                expected_cyclic: 8,
971                expected_month: 8,
972                expected_day: 10,
973            },
974            TestCase {
975                // #3709: This test case fails to match ICU
976                iso_year: -3649,
977                iso_month: 3,
978                iso_day: 30,
979                expected_rel_iso: -3649,
980                expected_cyclic: 8,
981                expected_month: 2,
982                expected_day: 14,
983            },
984            TestCase {
985                // #3709: This test case fails to match ICU
986                iso_year: -3650,
987                iso_month: 3,
988                iso_day: 30,
989                expected_rel_iso: -3650,
990                expected_cyclic: 7,
991                expected_month: 3,
992                expected_day: 3,
993            },
994        ];
995
996        let dangi_calculating = Dangi::new_always_calculating();
997        let dangi_cached = Dangi::new();
998
999        for case in cases {
1000            let iso = Date::try_new_iso_date(case.iso_year, case.iso_month, case.iso_day).unwrap();
1001            do_twice(&dangi_calculating, &dangi_cached, |dangi, calendar_type| {
1002                let dangi = iso.to_calendar(dangi);
1003                let dangi_rel_iso = dangi.year().related_iso;
1004                let dangi_cyclic = dangi.year().cyclic;
1005                let dangi_month = dangi.month().ordinal;
1006                let dangi_day = dangi.day_of_month().0;
1007
1008                assert_eq!(
1009                    dangi_rel_iso,
1010                    Some(case.expected_rel_iso),
1011                    "[{calendar_type}] Related ISO failed for test case: {case:?}"
1012                );
1013                assert_eq!(
1014                    dangi_cyclic.unwrap().get(),
1015                    case.expected_cyclic,
1016                    "[{calendar_type}] Cyclic year failed for test case: {case:?}"
1017                );
1018                assert_eq!(
1019                    dangi_month, case.expected_month,
1020                    "[{calendar_type}] Month failed for test case: {case:?}"
1021                );
1022                assert_eq!(
1023                    dangi_day, case.expected_day,
1024                    "[{calendar_type}] Day failed for test case: {case:?}"
1025                );
1026            });
1027        }
1028    }
1029}