/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 | | } |