Coverage Report

Created: 2025-08-29 06:18

/rust/registry/src/index.crates.io-6f17d22bba15001f/icu_calendar-1.5.2/src/dangi.rs
Line
Count
Source (jump to first uncovered line)
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
42
use crate::calendar_arithmetic::CalendarArithmetic;
43
use crate::calendar_arithmetic::PrecomputedDataSource;
44
use crate::chinese_based::{
45
    chinese_based_ordinal_lunar_month_from_code, ChineseBasedPrecomputedData,
46
    ChineseBasedWithDataLoading, ChineseBasedYearInfo,
47
};
48
use crate::provider::chinese_based::DangiCacheV1Marker;
49
use crate::AsCalendar;
50
use crate::{
51
    chinese_based::ChineseBasedDateInner,
52
    types::{self, Era, FormattableYear},
53
    AnyCalendarKind, Calendar, CalendarError, Date, DateTime, Iso, Time,
54
};
55
use core::cmp::Ordering;
56
use core::num::NonZeroU8;
57
use icu_provider::prelude::*;
58
use 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)]
100
pub 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)]
106
pub struct DangiDateInner(ChineseBasedDateInner<Dangi>);
107
108
type Inner = ChineseBasedDateInner<Dangi>;
109
110
// we want these impls without the `C: Copy/Clone` bounds
111
impl Copy for DangiDateInner {}
112
impl Clone for DangiDateInner {
113
0
    fn clone(&self) -> Self {
114
0
        *self
115
0
    }
116
}
117
118
// These impls just make custom derives on types containing C
119
// work. They're basically no-ops
120
impl PartialEq for Dangi {
121
0
    fn eq(&self, _: &Self) -> bool {
122
0
        true
123
0
    }
124
}
125
impl Eq for Dangi {}
126
#[allow(clippy::non_canonical_partial_ord_impl)] // this is intentional
127
impl PartialOrd for Dangi {
128
0
    fn partial_cmp(&self, _: &Self) -> Option<Ordering> {
129
0
        Some(Ordering::Equal)
130
0
    }
131
}
132
133
impl Ord for Dangi {
134
0
    fn cmp(&self, _: &Self) -> Ordering {
135
0
        Ordering::Equal
136
0
    }
137
}
138
139
impl 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
0
    pub const fn new() -> Self {
147
0
        Self {
148
0
            data: Some(DataPayload::from_static_ref(
149
0
                crate::provider::Baked::SINGLETON_CALENDAR_DANGICACHE_V1,
150
0
            )),
151
0
        }
152
0
    }
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
0
    pub fn try_new_unstable<D: DataProvider<DangiCacheV1Marker> + ?Sized>(
166
0
        provider: &D,
167
0
    ) -> Result<Self, CalendarError> {
168
0
        Ok(Self {
169
0
            data: Some(provider.load(Default::default())?.take_payload()?),
170
        })
171
0
    }
Unexecuted instantiation: <icu_calendar::dangi::Dangi>::try_new_unstable::<icu_provider::any::DowncastingAnyProvider<icu_provider_adapters::empty::EmptyDataProvider>>
Unexecuted instantiation: <icu_calendar::dangi::Dangi>::try_new_unstable::<_>
172
173
    /// Construct a new [`Dangi`] without any precomputed calendrical calculations.
174
0
    pub fn new_always_calculating() -> Self {
175
0
        Dangi { data: None }
176
0
    }
177
178
    pub(crate) const DEBUG_NAME: &'static str = "Dangi";
179
}
180
181
impl Calendar for Dangi {
182
    type DateInner = DangiDateInner;
183
184
0
    fn date_from_codes(
185
0
        &self,
186
0
        era: crate::types::Era,
187
0
        year: i32,
188
0
        month_code: crate::types::MonthCode,
189
0
        day: u8,
190
0
    ) -> Result<Self::DateInner, crate::Error> {
191
0
        let year_info = self.get_precomputed_data().load_or_compute_info(year);
192
193
0
        let month = if let Some(ordinal) =
194
0
            chinese_based_ordinal_lunar_month_from_code(month_code, year_info)
195
        {
196
0
            ordinal
197
        } else {
198
0
            return Err(CalendarError::UnknownMonthCode(
199
0
                month_code.0,
200
0
                self.debug_name(),
201
0
            ));
202
        };
203
204
0
        if era.0 != tinystr!(16, "dangi") {
205
0
            return Err(CalendarError::UnknownEra(era.0, self.debug_name()));
206
0
        }
207
0
208
0
        let arithmetic = Inner::new_from_ordinals(year, month, day, year_info);
209
0
        Ok(DangiDateInner(ChineseBasedDateInner(arithmetic?)))
210
0
    }
211
212
0
    fn date_from_iso(&self, iso: Date<crate::Iso>) -> Self::DateInner {
213
0
        let fixed = Iso::fixed_from_iso(iso.inner);
214
0
        DangiDateInner(Inner::chinese_based_date_from_fixed(
215
0
            self,
216
0
            fixed,
217
0
            iso.inner.0,
218
0
        ))
219
0
    }
220
221
0
    fn date_to_iso(&self, date: &Self::DateInner) -> Date<crate::Iso> {
222
0
        let fixed = Inner::fixed_from_chinese_based_date_inner(date.0);
223
0
        Iso::iso_from_fixed(fixed)
224
0
    }
225
226
0
    fn months_in_year(&self, date: &Self::DateInner) -> u8 {
227
0
        date.0.months_in_year_inner()
228
0
    }
229
230
0
    fn days_in_year(&self, date: &Self::DateInner) -> u16 {
231
0
        date.0.days_in_year_inner()
232
0
    }
233
234
0
    fn days_in_month(&self, date: &Self::DateInner) -> u8 {
235
0
        date.0.days_in_month_inner()
236
0
    }
237
238
0
    fn offset_date(&self, date: &mut Self::DateInner, offset: crate::DateDuration<Self>) {
239
0
        date.0 .0.offset_date(offset, &self.get_precomputed_data());
240
0
    }
241
242
0
    fn until(
243
0
        &self,
244
0
        date1: &Self::DateInner,
245
0
        date2: &Self::DateInner,
246
0
        _calendar2: &Self,
247
0
        largest_unit: crate::DateDurationUnit,
248
0
        smallest_unit: crate::DateDurationUnit,
249
0
    ) -> crate::DateDuration<Self> {
250
0
        date1.0 .0.until(date2.0 .0, largest_unit, smallest_unit)
251
0
    }
252
253
0
    fn debug_name(&self) -> &'static str {
254
0
        Self::DEBUG_NAME
255
0
    }
256
257
0
    fn year(&self, date: &Self::DateInner) -> crate::types::FormattableYear {
258
0
        Self::format_dangi_year(date.0 .0.year, Some(date.0 .0.year_info))
259
0
    }
260
261
0
    fn is_in_leap_year(&self, date: &Self::DateInner) -> bool {
262
0
        Self::is_leap_year(date.0 .0.year, date.0 .0.year_info)
263
0
    }
264
265
0
    fn month(&self, date: &Self::DateInner) -> crate::types::FormattableMonth {
266
0
        date.0.month()
267
0
    }
268
269
0
    fn day_of_month(&self, date: &Self::DateInner) -> crate::types::DayOfMonth {
270
0
        types::DayOfMonth(date.0 .0.day as u32)
271
0
    }
272
273
0
    fn day_of_year_info(&self, date: &Self::DateInner) -> crate::types::DayOfYearInfo {
274
0
        let prev_year = date.0 .0.year.saturating_sub(1);
275
0
        let next_year = date.0 .0.year.saturating_add(1);
276
0
        types::DayOfYearInfo {
277
0
            day_of_year: date.0 .0.day_of_year(),
278
0
            days_in_year: date.0.days_in_year_inner(),
279
0
            prev_year: Self::format_dangi_year(prev_year, None),
280
0
            days_in_prev_year: date.0.days_in_prev_year(),
281
0
            next_year: Self::format_dangi_year(next_year, None),
282
0
        }
283
0
    }
284
285
0
    fn day_of_week(&self, date: &Self::DateInner) -> crate::types::IsoWeekday {
286
0
        self.date_to_iso(date).day_of_week()
287
0
    }
288
289
0
    fn any_calendar_kind(&self) -> Option<crate::AnyCalendarKind> {
290
0
        Some(AnyCalendarKind::Dangi)
291
0
    }
292
}
293
294
impl<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
0
    pub fn try_new_dangi_date_with_calendar(
319
0
        year: i32,
320
0
        month: u8,
321
0
        day: u8,
322
0
        calendar: A,
323
0
    ) -> Result<Date<A>, CalendarError> {
324
0
        let year_info = calendar
325
0
            .as_calendar()
326
0
            .get_precomputed_data()
327
0
            .load_or_compute_info(year);
328
0
        let arithmetic = Inner::new_from_ordinals(year, month, day, year_info);
329
0
        Ok(Date::from_raw(
330
0
            DangiDateInner(ChineseBasedDateInner(arithmetic?)),
331
0
            calendar,
332
        ))
333
0
    }
334
}
335
336
impl<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
0
    pub fn try_new_dangi_datetime_with_calendar(
363
0
        year: i32,
364
0
        month: u8,
365
0
        day: u8,
366
0
        hour: u8,
367
0
        minute: u8,
368
0
        second: u8,
369
0
        calendar: A,
370
0
    ) -> Result<DateTime<A>, CalendarError> {
371
0
        Ok(DateTime {
372
0
            date: Date::try_new_dangi_date_with_calendar(year, month, day, calendar)?,
373
0
            time: Time::try_new(hour, minute, second, 0)?,
374
        })
375
0
    }
376
}
377
378
type DangiCB = calendrical_calculations::chinese_based::Dangi;
379
impl ChineseBasedWithDataLoading for Dangi {
380
    type CB = DangiCB;
381
0
    fn get_precomputed_data(&self) -> ChineseBasedPrecomputedData<Self::CB> {
382
0
        ChineseBasedPrecomputedData::new(self.data.as_ref().map(|d| d.get()))
383
0
    }
384
}
385
386
impl Dangi {
387
    /// Get a `FormattableYear` from an integer Dangi year; optionally, a `ChineseBasedYearInfo`
388
    /// can be passed in for faster results.
389
0
    fn format_dangi_year(
390
0
        year: i32,
391
0
        year_info_option: Option<ChineseBasedYearInfo>,
392
0
    ) -> FormattableYear {
393
0
        let era = Era(tinystr!(16, "dangi"));
394
0
        let number = year;
395
0
        // constant 364 from https://github.com/EdReingold/calendar-code2/blob/main/calendar.l#L5704
396
0
        let cyclic = (number as i64 - 1 + 364).rem_euclid(60) as u8;
397
0
        let cyclic = NonZeroU8::new(cyclic + 1); // 1-indexed
398
0
        let rata_die_in_year = if let Some(info) = year_info_option {
399
0
            info.new_year::<DangiCB>(year)
400
        } else {
401
0
            Inner::fixed_mid_year_from_year(number)
402
        };
403
0
        let iso_formattable_year = Iso::iso_from_fixed(rata_die_in_year).year();
404
0
        let related_iso = Some(iso_formattable_year.number);
405
0
        types::FormattableYear {
406
0
            era,
407
0
            number,
408
0
            cyclic,
409
0
            related_iso,
410
0
        }
411
0
    }
412
}
413
414
#[cfg(test)]
415
mod 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
}