1use crate::cal::iso::{Iso, IsoDateInner};
22use crate::calendar_arithmetic::PrecomputedDataSource;
23use crate::calendar_arithmetic::{ArithmeticDate, CalendarArithmetic};
24use crate::error::{year_check, DateError};
25use crate::provider::hijri::PackedHijriYearInfo;
26use crate::provider::hijri::{CalendarHijriSimulatedMeccaV1, HijriData};
27use crate::types::EraYear;
28use crate::{types, Calendar, Date, DateDuration, DateDurationUnit};
29use crate::{AsCalendar, RangeError};
30use calendrical_calculations::islamic::{ISLAMIC_EPOCH_FRIDAY, ISLAMIC_EPOCH_THURSDAY};
31use calendrical_calculations::rata_die::RataDie;
32use icu_provider::marker::ErasedMarker;
33use icu_provider::prelude::*;
34use tinystr::tinystr;
35use ummalqura_data::{UMMALQURA_DATA, UMMALQURA_DATA_STARTING_YEAR};
36
37mod ummalqura_data;
38
39fn era_year(year: i32) -> EraYear {
40 if year > 0 {
41 types::EraYear {
42 era: tinystr!(16, "ah"),
43 era_index: Some(0),
44 year,
45 ambiguity: types::YearAmbiguity::CenturyRequired,
46 }
47 } else {
48 types::EraYear {
49 era: tinystr!(16, "bh"),
50 era_index: Some(1),
51 year: 1 - year,
52 ambiguity: types::YearAmbiguity::CenturyRequired,
53 }
54 }
55}
56
57#[derive(Clone, Debug)]
68pub struct HijriSimulated {
69 pub(crate) location: HijriSimulatedLocation,
70 data: Option<DataPayload<ErasedMarker<HijriData<'static>>>>,
71}
72
73#[derive(Clone, Debug, Copy, PartialEq)]
74pub(crate) enum HijriSimulatedLocation {
75 Mecca,
76}
77
78impl HijriSimulatedLocation {
79 fn location(self) -> calendrical_calculations::islamic::Location {
80 match self {
81 Self::Mecca => calendrical_calculations::islamic::MECCA,
82 }
83 }
84}
85
86#[derive(Clone, Debug, Default)]
99#[non_exhaustive]
100pub struct HijriUmmAlQura;
101
102#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq, PartialOrd, Ord)]
117pub struct HijriTabular {
118 pub(crate) leap_years: HijriTabularLeapYears,
119 pub(crate) epoch: HijriTabularEpoch,
120}
121
122impl HijriSimulated {
123 #[cfg(feature = "compiled_data")]
129 pub const fn new_mecca() -> Self {
130 Self {
131 location: HijriSimulatedLocation::Mecca,
132 data: Some(DataPayload::from_static_ref(
133 crate::provider::Baked::SINGLETON_CALENDAR_HIJRI_SIMULATED_MECCA_V1,
134 )),
135 }
136 }
137
138 icu_provider::gen_buffer_data_constructors!(() -> error: DataError,
139 functions: [
140 new: skip,
141 try_new_mecca_with_buffer_provider,
142 try_new_mecca_unstable,
143 Self,
144 ]);
145
146 #[doc = icu_provider::gen_buffer_unstable_docs!(UNSTABLE, Self::new_mecca)]
147 pub fn try_new_mecca_unstable<D: DataProvider<CalendarHijriSimulatedMeccaV1> + ?Sized>(
148 provider: &D,
149 ) -> Result<Self, DataError> {
150 Ok(Self {
151 location: HijriSimulatedLocation::Mecca,
152 data: Some(provider.load(Default::default())?.payload.cast()),
153 })
154 }
155
156 pub const fn new_mecca_always_calculating() -> Self {
158 Self {
159 location: HijriSimulatedLocation::Mecca,
160 data: None,
161 }
162 }
163
164 #[cfg(feature = "datagen")]
166 pub fn build_cache(&self, extended_years: core::ops::Range<i32>) -> HijriData<'static> {
167 let data = extended_years
168 .clone()
169 .map(|year| self.location.compute_year_info(year).pack())
170 .collect();
171 HijriData {
172 first_extended_year: extended_years.start,
173 data,
174 }
175 }
176}
177
178impl HijriUmmAlQura {
179 pub const fn new() -> Self {
181 Self
182 }
183}
184
185#[non_exhaustive]
187#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq, PartialOrd, Ord)]
188pub enum HijriTabularEpoch {
189 Thursday,
191 Friday,
193}
194
195impl HijriTabularEpoch {
196 fn rata_die(self) -> RataDie {
197 match self {
198 Self::Thursday => ISLAMIC_EPOCH_THURSDAY,
199 Self::Friday => ISLAMIC_EPOCH_FRIDAY,
200 }
201 }
202}
203
204#[non_exhaustive]
209#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq, PartialOrd, Ord)]
210pub enum HijriTabularLeapYears {
211 TypeII,
213}
214
215impl HijriTabular {
216 pub const fn new(leap_years: HijriTabularLeapYears, epoch: HijriTabularEpoch) -> Self {
218 Self { epoch, leap_years }
219 }
220}
221
222#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)]
223pub(crate) struct HijriYearInfo {
224 month_lengths: [bool; 12],
225 start_day: RataDie,
226 value: i32,
227}
228
229impl From<HijriYearInfo> for i32 {
230 fn from(value: HijriYearInfo) -> Self {
231 value.value
232 }
233}
234
235impl HijriData<'_> {
236 fn get(&self, extended_year: i32) -> Option<HijriYearInfo> {
238 Some(HijriYearInfo::unpack(
239 extended_year,
240 self.data
241 .get(usize::try_from(extended_year - self.first_extended_year).ok()?)?,
242 ))
243 }
244}
245
246const LONG_YEAR_LEN: u16 = 355;
247const SHORT_YEAR_LEN: u16 = 354;
248
249impl HijriYearInfo {
250 #[cfg(feature = "datagen")]
251 fn pack(&self) -> PackedHijriYearInfo {
252 PackedHijriYearInfo::new(self.value, self.month_lengths, self.start_day)
253 }
254
255 fn unpack(extended_year: i32, packed: PackedHijriYearInfo) -> Self {
256 let (month_lengths, start_day) = packed.unpack(extended_year);
257
258 HijriYearInfo {
259 month_lengths,
260 start_day,
261 value: extended_year,
262 }
263 }
264
265 fn days_in_month(self, month: u8) -> u8 {
267 let Some(zero_month) = month.checked_sub(1) else {
268 return 29;
269 };
270
271 if self.month_lengths.get(zero_month as usize) == Some(&true) {
272 30
273 } else {
274 29
275 }
276 }
277
278 fn days_in_year(self) -> u16 {
279 self.last_day_of_month(12)
280 }
281
282 fn md_to_rd(self, month: u8, day: u8) -> RataDie {
284 let month_offset = if month == 1 {
285 0
286 } else {
287 self.last_day_of_month(month - 1)
288 };
289 self.start_day + month_offset as i64 + (day - 1) as i64
290 }
291
292 fn md_from_rd(self, rd: RataDie) -> (u8, u8) {
293 let day_of_year = (rd - self.start_day) as u16;
294 debug_assert!(day_of_year < 360);
295 let mut month = (day_of_year / 30) as u8 + 1;
298
299 let day_of_year = day_of_year + 1;
300 let mut last_day_of_month = self.last_day_of_month(month);
301 let mut last_day_of_prev_month = if month == 1 {
302 0
303 } else {
304 self.last_day_of_month(month - 1)
305 };
306
307 while day_of_year > last_day_of_month && month <= 12 {
308 month += 1;
309 last_day_of_prev_month = last_day_of_month;
310 last_day_of_month = self.last_day_of_month(month);
311 }
312 debug_assert!(
313 day_of_year - last_day_of_prev_month <= 30,
314 "Found day {} that doesn't fit in month!",
315 day_of_year - last_day_of_prev_month
316 );
317 let day = (day_of_year - last_day_of_prev_month) as u8;
318 (month, day)
319 }
320
321 fn last_day_of_month(self, month: u8) -> u16 {
323 29 * month as u16
324 + self
325 .month_lengths
326 .get(..month as usize)
327 .unwrap_or_default()
328 .iter()
329 .filter(|&&x| x)
330 .count() as u16
331 }
332}
333
334impl PrecomputedDataSource<HijriYearInfo> for HijriSimulated {
335 fn load_or_compute_info(&self, extended_year: i32) -> HijriYearInfo {
336 self.data
337 .as_ref()
338 .and_then(|d| d.get().get(extended_year))
339 .unwrap_or_else(|| self.location.compute_year_info(extended_year))
340 }
341}
342
343#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq, PartialOrd, Ord)]
346pub struct HijriSimulatedDateInner(ArithmeticDate<HijriSimulated>);
347
348impl CalendarArithmetic for HijriSimulated {
349 type YearInfo = HijriYearInfo;
350
351 fn days_in_provided_month(year: Self::YearInfo, month: u8) -> u8 {
352 year.days_in_month(month)
353 }
354
355 fn months_in_provided_year(_year: Self::YearInfo) -> u8 {
356 12
357 }
358
359 fn days_in_provided_year(year: Self::YearInfo) -> u16 {
360 year.days_in_year()
361 }
362
363 fn provided_year_is_leap(year: Self::YearInfo) -> bool {
365 year.days_in_year() != SHORT_YEAR_LEN
366 }
367
368 fn last_month_day_in_provided_year(year: Self::YearInfo) -> (u8, u8) {
369 let days = Self::days_in_provided_month(year, 12);
370
371 (12, days)
372 }
373}
374
375impl crate::cal::scaffold::UnstableSealed for HijriSimulated {}
376impl Calendar for HijriSimulated {
377 type DateInner = HijriSimulatedDateInner;
378 type Year = types::EraYear;
379 fn from_codes(
380 &self,
381 era: Option<&str>,
382 year: i32,
383 month_code: types::MonthCode,
384 day: u8,
385 ) -> Result<Self::DateInner, DateError> {
386 let year = match era {
387 Some("ah") | None => year_check(year, 1..)?,
388 Some("bh") => 1 - year_check(year, 1..)?,
389 Some(_) => return Err(DateError::UnknownEra),
390 };
391 let Some((month, false)) = month_code.parsed() else {
392 return Err(DateError::UnknownMonthCode(month_code));
393 };
394 Ok(HijriSimulatedDateInner(ArithmeticDate::new_from_ordinals(
395 self.load_or_compute_info(year),
396 month,
397 day,
398 )?))
399 }
400
401 fn from_rata_die(&self, rd: RataDie) -> Self::DateInner {
402 let extended_year = ((rd - calendrical_calculations::islamic::ISLAMIC_EPOCH_FRIDAY) as f64
406 / calendrical_calculations::islamic::MEAN_YEAR_LENGTH)
407 as i32
408 + 1;
409
410 let year = self.load_or_compute_info(extended_year);
411
412 let y = if rd < year.start_day {
413 self.load_or_compute_info(extended_year - 1)
414 } else {
415 let next_year = self.load_or_compute_info(extended_year + 1);
416 if rd < next_year.start_day {
417 year
418 } else {
419 next_year
420 }
421 };
422 let (m, d) = y.md_from_rd(rd);
423 HijriSimulatedDateInner(ArithmeticDate::new_unchecked(y, m, d))
424 }
425
426 fn to_rata_die(&self, date: &Self::DateInner) -> RataDie {
427 date.0.year.md_to_rd(date.0.month, date.0.day)
428 }
429
430 fn from_iso(&self, iso: IsoDateInner) -> Self::DateInner {
431 self.from_rata_die(Iso.to_rata_die(&iso))
432 }
433
434 fn to_iso(&self, date: &Self::DateInner) -> IsoDateInner {
435 Iso.from_rata_die(self.to_rata_die(date))
436 }
437
438 fn months_in_year(&self, date: &Self::DateInner) -> u8 {
439 date.0.months_in_year()
440 }
441
442 fn days_in_year(&self, date: &Self::DateInner) -> u16 {
443 date.0.days_in_year()
444 }
445
446 fn days_in_month(&self, date: &Self::DateInner) -> u8 {
447 date.0.days_in_month()
448 }
449
450 fn offset_date(&self, date: &mut Self::DateInner, offset: DateDuration<Self>) {
451 date.0.offset_date(offset, self)
452 }
453
454 fn until(
455 &self,
456 date1: &Self::DateInner,
457 date2: &Self::DateInner,
458 _calendar2: &Self,
459 _largest_unit: DateDurationUnit,
460 _smallest_unit: DateDurationUnit,
461 ) -> DateDuration<Self> {
462 date1.0.until(date2.0, _largest_unit, _smallest_unit)
463 }
464
465 fn debug_name(&self) -> &'static str {
466 Self::DEBUG_NAME
467 }
468
469 fn year_info(&self, date: &Self::DateInner) -> Self::Year {
470 era_year(self.extended_year(date))
471 }
472
473 fn extended_year(&self, date: &Self::DateInner) -> i32 {
474 date.0.extended_year()
475 }
476
477 fn is_in_leap_year(&self, date: &Self::DateInner) -> bool {
478 Self::provided_year_is_leap(date.0.year)
479 }
480
481 fn month(&self, date: &Self::DateInner) -> types::MonthInfo {
482 date.0.month()
483 }
484
485 fn day_of_month(&self, date: &Self::DateInner) -> types::DayOfMonth {
486 date.0.day_of_month()
487 }
488
489 fn day_of_year(&self, date: &Self::DateInner) -> types::DayOfYear {
490 date.0.day_of_year()
491 }
492
493 fn calendar_algorithm(&self) -> Option<crate::preferences::CalendarAlgorithm> {
494 Some(match self.location {
495 crate::cal::hijri::HijriSimulatedLocation::Mecca => {
496 crate::preferences::CalendarAlgorithm::Hijri(Some(
497 crate::preferences::HijriCalendarAlgorithm::Rgsa,
498 ))
499 }
500 })
501 }
502}
503
504impl HijriSimulatedLocation {
505 fn compute_year_info(self, extended_year: i32) -> HijriYearInfo {
506 let start_day = calendrical_calculations::islamic::fixed_from_observational_islamic(
507 extended_year,
508 1,
509 1,
510 self.location(),
511 );
512 let next_start_day = calendrical_calculations::islamic::fixed_from_observational_islamic(
513 extended_year + 1,
514 1,
515 1,
516 self.location(),
517 );
518 match (next_start_day - start_day) as u16 {
519 LONG_YEAR_LEN | SHORT_YEAR_LEN => (),
520 353 => {
521 icu_provider::log::trace!(
522 "({}) Found year {extended_year} AH with length {}. See <https://github.com/unicode-org/icu4x/issues/4930>",
523 HijriSimulated::DEBUG_NAME,
524 next_start_day - start_day
525 );
526 }
527 other => {
528 debug_assert!(
529 false,
530 "({}) Found year {extended_year} AH with length {}!",
531 HijriSimulated::DEBUG_NAME,
532 other
533 )
534 }
535 }
536
537 let month_lengths = {
538 let mut excess_days = 0;
539 let mut month_lengths = core::array::from_fn(|month_idx| {
540 let days_in_month =
541 calendrical_calculations::islamic::observational_islamic_month_days(
542 extended_year,
543 month_idx as u8 + 1,
544 self.location(),
545 );
546 match days_in_month {
547 29 => false,
548 30 => true,
549 31 => {
550 icu_provider::log::trace!(
551 "({}) Found year {extended_year} AH with month length {days_in_month} for month {}.",
552 HijriSimulated::DEBUG_NAME,
553 month_idx + 1
554 );
555 excess_days += 1;
556 true
557 }
558 _ => {
559 debug_assert!(
560 false,
561 "({}) Found year {extended_year} AH with month length {days_in_month} for month {}!",
562 HijriSimulated::DEBUG_NAME,
563 month_idx + 1
564 );
565 false
566 }
567 }
568 });
569 if excess_days != 0 {
573 debug_assert_eq!(
574 excess_days,
575 1,
576 "({}) Found year {extended_year} AH with more than one excess day!",
577 HijriSimulated::DEBUG_NAME
578 );
579 if let Some(l) = month_lengths.iter_mut().find(|l| !(**l)) {
580 *l = true;
581 }
582 }
583 month_lengths
584 };
585 HijriYearInfo {
586 month_lengths,
587 start_day,
588 value: extended_year,
589 }
590 }
591}
592
593impl HijriSimulated {
594 pub(crate) const DEBUG_NAME: &'static str = "Hijri (simulated)";
595}
596
597impl<A: AsCalendar<Calendar = HijriSimulated>> Date<A> {
598 pub fn try_new_simulated_hijri_with_calendar(
615 year: i32,
616 month: u8,
617 day: u8,
618 calendar: A,
619 ) -> Result<Date<A>, RangeError> {
620 let y = calendar.as_calendar().load_or_compute_info(year);
621 ArithmeticDate::new_from_ordinals(y, month, day)
622 .map(HijriSimulatedDateInner)
623 .map(|inner| Date::from_raw(inner, calendar))
624 }
625}
626
627#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq, PartialOrd, Ord)]
628pub struct HijriUmmAlQuraDateInner(ArithmeticDate<HijriUmmAlQura>);
630
631impl CalendarArithmetic for HijriUmmAlQura {
632 type YearInfo = HijriYearInfo;
633
634 fn days_in_provided_month(year: Self::YearInfo, month: u8) -> u8 {
635 year.days_in_month(month)
636 }
637
638 fn months_in_provided_year(_year: HijriYearInfo) -> u8 {
639 12
640 }
641
642 fn days_in_provided_year(year: Self::YearInfo) -> u16 {
643 year.days_in_year()
644 }
645
646 fn provided_year_is_leap(year: Self::YearInfo) -> bool {
648 year.days_in_year() != SHORT_YEAR_LEN
649 }
650
651 fn last_month_day_in_provided_year(year: HijriYearInfo) -> (u8, u8) {
652 let days = Self::days_in_provided_month(year, 12);
653
654 (12, days)
655 }
656}
657
658impl crate::cal::scaffold::UnstableSealed for HijriUmmAlQura {}
659impl Calendar for HijriUmmAlQura {
660 type DateInner = HijriUmmAlQuraDateInner;
661 type Year = types::EraYear;
662 fn from_codes(
663 &self,
664 era: Option<&str>,
665 year: i32,
666 month_code: types::MonthCode,
667 day: u8,
668 ) -> Result<Self::DateInner, DateError> {
669 let year = match era {
670 Some("ah") | None => year_check(year, 1..)?,
671 Some("bh") => 1 - year_check(year, 1..)?,
672 Some(_) => return Err(DateError::UnknownEra),
673 };
674 let Some((month, false)) = month_code.parsed() else {
675 return Err(DateError::UnknownMonthCode(month_code));
676 };
677 Ok(HijriUmmAlQuraDateInner(ArithmeticDate::new_from_ordinals(
678 self.load_or_compute_info(year),
679 month,
680 day,
681 )?))
682 }
683
684 fn from_rata_die(&self, rd: RataDie) -> Self::DateInner {
685 let extended_year = ((rd - calendrical_calculations::islamic::ISLAMIC_EPOCH_FRIDAY) as f64
689 / calendrical_calculations::islamic::MEAN_YEAR_LENGTH)
690 as i32
691 + 1;
692
693 let year = self.load_or_compute_info(extended_year);
694
695 let y = if rd < year.start_day {
696 self.load_or_compute_info(extended_year - 1)
697 } else {
698 let next_year = self.load_or_compute_info(extended_year + 1);
699 if rd < next_year.start_day {
700 year
701 } else {
702 next_year
703 }
704 };
705 let (m, d) = y.md_from_rd(rd);
706 HijriUmmAlQuraDateInner(ArithmeticDate::new_unchecked(y, m, d))
707 }
708
709 fn to_rata_die(&self, date: &Self::DateInner) -> RataDie {
710 date.0.year.md_to_rd(date.0.month, date.0.day)
711 }
712
713 fn from_iso(&self, iso: IsoDateInner) -> Self::DateInner {
714 self.from_rata_die(Iso.to_rata_die(&iso))
715 }
716
717 fn to_iso(&self, date: &Self::DateInner) -> IsoDateInner {
718 Iso.from_rata_die(self.to_rata_die(date))
719 }
720
721 fn months_in_year(&self, date: &Self::DateInner) -> u8 {
722 date.0.months_in_year()
723 }
724
725 fn days_in_year(&self, date: &Self::DateInner) -> u16 {
726 date.0.days_in_year()
727 }
728
729 fn days_in_month(&self, date: &Self::DateInner) -> u8 {
730 date.0.days_in_month()
731 }
732
733 fn offset_date(&self, date: &mut Self::DateInner, offset: DateDuration<Self>) {
734 date.0.offset_date(offset, &HijriUmmAlQura)
735 }
736
737 fn until(
738 &self,
739 date1: &Self::DateInner,
740 date2: &Self::DateInner,
741 _calendar2: &Self,
742 _largest_unit: DateDurationUnit,
743 _smallest_unit: DateDurationUnit,
744 ) -> DateDuration<Self> {
745 date1.0.until(date2.0, _largest_unit, _smallest_unit)
746 }
747
748 fn debug_name(&self) -> &'static str {
749 Self::DEBUG_NAME
750 }
751
752 fn year_info(&self, date: &Self::DateInner) -> Self::Year {
753 era_year(self.extended_year(date))
754 }
755
756 fn extended_year(&self, date: &Self::DateInner) -> i32 {
757 date.0.extended_year()
758 }
759
760 fn is_in_leap_year(&self, date: &Self::DateInner) -> bool {
761 Self::provided_year_is_leap(date.0.year)
762 }
763
764 fn month(&self, date: &Self::DateInner) -> types::MonthInfo {
765 date.0.month()
766 }
767
768 fn day_of_month(&self, date: &Self::DateInner) -> types::DayOfMonth {
769 date.0.day_of_month()
770 }
771
772 fn day_of_year(&self, date: &Self::DateInner) -> types::DayOfYear {
773 date.0.day_of_year()
774 }
775
776 fn calendar_algorithm(&self) -> Option<crate::preferences::CalendarAlgorithm> {
777 let expected_calendar = crate::preferences::CalendarAlgorithm::Hijri(Some(
778 crate::preferences::HijriCalendarAlgorithm::Umalqura,
779 ));
780 Some(expected_calendar)
781 }
782}
783
784impl PrecomputedDataSource<HijriYearInfo> for HijriUmmAlQura {
785 fn load_or_compute_info(&self, year: i32) -> HijriYearInfo {
786 if let Some(&packed) = usize::try_from(year - UMMALQURA_DATA_STARTING_YEAR)
787 .ok()
788 .and_then(|i| UMMALQURA_DATA.get(i))
789 {
790 HijriYearInfo::unpack(year, packed)
791 } else {
792 HijriYearInfo {
793 value: year,
794 month_lengths: core::array::from_fn(|i| {
795 HijriTabular::days_in_provided_month(year, i as u8 + 1) == 30
796 }),
797 start_day: calendrical_calculations::islamic::fixed_from_tabular_islamic(
798 year,
799 1,
800 1,
801 ISLAMIC_EPOCH_FRIDAY,
802 ),
803 }
804 }
805 }
806}
807
808impl HijriUmmAlQura {
809 pub(crate) const DEBUG_NAME: &'static str = "Hijri (Umm al-Qura)";
810}
811
812impl Date<HijriUmmAlQura> {
813 pub fn try_new_ummalqura(
827 year: i32,
828 month: u8,
829 day: u8,
830 ) -> Result<Date<HijriUmmAlQura>, RangeError> {
831 let y = HijriUmmAlQura.load_or_compute_info(year);
832 Ok(Date::from_raw(
833 HijriUmmAlQuraDateInner(ArithmeticDate::new_from_ordinals(y, month, day)?),
834 HijriUmmAlQura,
835 ))
836 }
837}
838
839#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq, PartialOrd, Ord)]
842pub struct HijriTabularDateInner(ArithmeticDate<HijriTabular>);
843
844impl CalendarArithmetic for HijriTabular {
845 type YearInfo = i32;
846
847 fn days_in_provided_month(year: i32, month: u8) -> u8 {
848 match month {
849 1 | 3 | 5 | 7 | 9 | 11 => 30,
850 2 | 4 | 6 | 8 | 10 => 29,
851 12 if Self::provided_year_is_leap(year) => 30,
852 12 => 29,
853 _ => 0,
854 }
855 }
856
857 fn months_in_provided_year(_year: Self::YearInfo) -> u8 {
858 12
859 }
860
861 fn days_in_provided_year(year: i32) -> u16 {
862 if Self::provided_year_is_leap(year) {
863 LONG_YEAR_LEN
864 } else {
865 SHORT_YEAR_LEN
866 }
867 }
868
869 fn provided_year_is_leap(year: i32) -> bool {
870 (14 + 11 * year).rem_euclid(30) < 11
871 }
872
873 fn last_month_day_in_provided_year(year: i32) -> (u8, u8) {
874 if Self::provided_year_is_leap(year) {
875 (12, 30)
876 } else {
877 (12, 29)
878 }
879 }
880}
881
882impl crate::cal::scaffold::UnstableSealed for HijriTabular {}
883impl Calendar for HijriTabular {
884 type DateInner = HijriTabularDateInner;
885 type Year = types::EraYear;
886
887 fn from_codes(
888 &self,
889 era: Option<&str>,
890 year: i32,
891 month_code: types::MonthCode,
892 day: u8,
893 ) -> Result<Self::DateInner, DateError> {
894 let year = match era {
895 Some("ah") | None => year_check(year, 1..)?,
896 Some("bh") => 1 - year_check(year, 1..)?,
897 Some(_) => return Err(DateError::UnknownEra),
898 };
899
900 ArithmeticDate::new_from_codes(self, year, month_code, day).map(HijriTabularDateInner)
901 }
902
903 fn from_rata_die(&self, rd: RataDie) -> Self::DateInner {
904 let (y, m, d) = match self.leap_years {
905 HijriTabularLeapYears::TypeII => {
906 calendrical_calculations::islamic::tabular_islamic_from_fixed(
907 rd,
908 self.epoch.rata_die(),
909 )
910 }
911 };
912
913 debug_assert!(Date::try_new_hijri_tabular_with_calendar(y, m, d, crate::Ref(self)).is_ok());
914 HijriTabularDateInner(ArithmeticDate::new_unchecked(y, m, d))
915 }
916
917 fn to_rata_die(&self, date: &Self::DateInner) -> RataDie {
918 match self.leap_years {
919 HijriTabularLeapYears::TypeII => {
920 calendrical_calculations::islamic::fixed_from_tabular_islamic(
921 date.0.year,
922 date.0.month,
923 date.0.day,
924 self.epoch.rata_die(),
925 )
926 }
927 }
928 }
929
930 fn from_iso(&self, iso: IsoDateInner) -> Self::DateInner {
931 self.from_rata_die(Iso.to_rata_die(&iso))
932 }
933
934 fn to_iso(&self, date: &Self::DateInner) -> IsoDateInner {
935 Iso.from_rata_die(self.to_rata_die(date))
936 }
937
938 fn months_in_year(&self, date: &Self::DateInner) -> u8 {
939 date.0.months_in_year()
940 }
941
942 fn days_in_year(&self, date: &Self::DateInner) -> u16 {
943 date.0.days_in_year()
944 }
945
946 fn days_in_month(&self, date: &Self::DateInner) -> u8 {
947 date.0.days_in_month()
948 }
949
950 fn offset_date(&self, date: &mut Self::DateInner, offset: DateDuration<Self>) {
951 date.0.offset_date(offset, &())
952 }
953
954 fn until(
955 &self,
956 date1: &Self::DateInner,
957 date2: &Self::DateInner,
958 _calendar2: &Self,
959 _largest_unit: DateDurationUnit,
960 _smallest_unit: DateDurationUnit,
961 ) -> DateDuration<Self> {
962 date1.0.until(date2.0, _largest_unit, _smallest_unit)
963 }
964
965 fn debug_name(&self) -> &'static str {
966 match self.epoch {
967 HijriTabularEpoch::Friday => "Hijri (civil)",
968 HijriTabularEpoch::Thursday => "Hijri (astronomical)",
969 }
970 }
971
972 fn year_info(&self, date: &Self::DateInner) -> Self::Year {
973 era_year(self.extended_year(date))
974 }
975
976 fn extended_year(&self, date: &Self::DateInner) -> i32 {
977 date.0.extended_year()
978 }
979
980 fn is_in_leap_year(&self, date: &Self::DateInner) -> bool {
981 Self::provided_year_is_leap(date.0.year)
982 }
983
984 fn month(&self, date: &Self::DateInner) -> types::MonthInfo {
985 date.0.month()
986 }
987
988 fn day_of_month(&self, date: &Self::DateInner) -> types::DayOfMonth {
989 date.0.day_of_month()
990 }
991
992 fn day_of_year(&self, date: &Self::DateInner) -> types::DayOfYear {
993 date.0.day_of_year()
994 }
995
996 fn calendar_algorithm(&self) -> Option<crate::preferences::CalendarAlgorithm> {
997 let expected_calendar = match (self.epoch, self.leap_years) {
998 (crate::cal::HijriTabularEpoch::Friday, crate::cal::HijriTabularLeapYears::TypeII) => {
999 crate::preferences::CalendarAlgorithm::Hijri(Some(
1000 crate::preferences::HijriCalendarAlgorithm::Civil,
1001 ))
1002 }
1003 (
1004 crate::cal::HijriTabularEpoch::Thursday,
1005 crate::cal::HijriTabularLeapYears::TypeII,
1006 ) => crate::preferences::CalendarAlgorithm::Hijri(Some(
1007 crate::preferences::HijriCalendarAlgorithm::Tbla,
1008 )),
1009 };
1010 Some(expected_calendar)
1011 }
1012}
1013
1014impl<A: AsCalendar<Calendar = HijriTabular>> Date<A> {
1015 pub fn try_new_hijri_tabular_with_calendar(
1037 year: i32,
1038 month: u8,
1039 day: u8,
1040 calendar: A,
1041 ) -> Result<Date<A>, RangeError> {
1042 ArithmeticDate::new_from_ordinals(year, month, day)
1043 .map(HijriTabularDateInner)
1044 .map(|inner| Date::from_raw(inner, calendar))
1045 }
1046}
1047
1048#[cfg(test)]
1049mod test {
1050 use types::MonthCode;
1051
1052 use super::*;
1053 use crate::Ref;
1054
1055 const START_YEAR: i32 = -1245;
1056 const END_YEAR: i32 = 1518;
1057
1058 #[derive(Debug)]
1059 struct DateCase {
1060 year: i32,
1061 month: u8,
1062 day: u8,
1063 }
1064
1065 static TEST_RD: [i64; 33] = [
1066 -214193, -61387, 25469, 49217, 171307, 210155, 253427, 369740, 400085, 434355, 452605,
1067 470160, 473837, 507850, 524156, 544676, 567118, 569477, 601716, 613424, 626596, 645554,
1068 664224, 671401, 694799, 704424, 708842, 709409, 709580, 727274, 728714, 744313, 764652,
1069 ];
1070
1071 static UMMALQURA_CASES: [DateCase; 33] = [
1072 DateCase {
1073 year: -1245,
1074 month: 12,
1075 day: 9,
1076 },
1077 DateCase {
1078 year: -813,
1079 month: 2,
1080 day: 23,
1081 },
1082 DateCase {
1083 year: -568,
1084 month: 4,
1085 day: 1,
1086 },
1087 DateCase {
1088 year: -501,
1089 month: 4,
1090 day: 6,
1091 },
1092 DateCase {
1093 year: -157,
1094 month: 10,
1095 day: 17,
1096 },
1097 DateCase {
1098 year: -47,
1099 month: 6,
1100 day: 3,
1101 },
1102 DateCase {
1103 year: 75,
1104 month: 7,
1105 day: 13,
1106 },
1107 DateCase {
1108 year: 403,
1109 month: 10,
1110 day: 5,
1111 },
1112 DateCase {
1113 year: 489,
1114 month: 5,
1115 day: 22,
1116 },
1117 DateCase {
1118 year: 586,
1119 month: 2,
1120 day: 7,
1121 },
1122 DateCase {
1123 year: 637,
1124 month: 8,
1125 day: 7,
1126 },
1127 DateCase {
1128 year: 687,
1129 month: 2,
1130 day: 20,
1131 },
1132 DateCase {
1133 year: 697,
1134 month: 7,
1135 day: 7,
1136 },
1137 DateCase {
1138 year: 793,
1139 month: 7,
1140 day: 1,
1141 },
1142 DateCase {
1143 year: 839,
1144 month: 7,
1145 day: 6,
1146 },
1147 DateCase {
1148 year: 897,
1149 month: 6,
1150 day: 1,
1151 },
1152 DateCase {
1153 year: 960,
1154 month: 9,
1155 day: 30,
1156 },
1157 DateCase {
1158 year: 967,
1159 month: 5,
1160 day: 27,
1161 },
1162 DateCase {
1163 year: 1058,
1164 month: 5,
1165 day: 18,
1166 },
1167 DateCase {
1168 year: 1091,
1169 month: 6,
1170 day: 2,
1171 },
1172 DateCase {
1173 year: 1128,
1174 month: 8,
1175 day: 4,
1176 },
1177 DateCase {
1178 year: 1182,
1179 month: 2,
1180 day: 3,
1181 },
1182 DateCase {
1183 year: 1234,
1184 month: 10,
1185 day: 10,
1186 },
1187 DateCase {
1188 year: 1255,
1189 month: 1,
1190 day: 11,
1191 },
1192 DateCase {
1193 year: 1321,
1194 month: 1,
1195 day: 21,
1196 },
1197 DateCase {
1198 year: 1348,
1199 month: 3,
1200 day: 20,
1201 },
1202 DateCase {
1203 year: 1360,
1204 month: 9,
1205 day: 7,
1206 },
1207 DateCase {
1208 year: 1362,
1209 month: 4,
1210 day: 14,
1211 },
1212 DateCase {
1213 year: 1362,
1214 month: 10,
1215 day: 7,
1216 },
1217 DateCase {
1218 year: 1412,
1219 month: 9,
1220 day: 12,
1221 },
1222 DateCase {
1223 year: 1416,
1224 month: 10,
1225 day: 6,
1226 },
1227 DateCase {
1228 year: 1460,
1229 month: 10,
1230 day: 13,
1231 },
1232 DateCase {
1233 year: 1518,
1234 month: 3,
1235 day: 5,
1236 },
1237 ];
1238
1239 static SIMULATED_CASES: [DateCase; 33] = [
1240 DateCase {
1241 year: -1245,
1242 month: 12,
1243 day: 10,
1244 },
1245 DateCase {
1246 year: -813,
1247 month: 2,
1248 day: 25,
1249 },
1250 DateCase {
1251 year: -568,
1252 month: 4,
1253 day: 2,
1254 },
1255 DateCase {
1256 year: -501,
1257 month: 4,
1258 day: 7,
1259 },
1260 DateCase {
1261 year: -157,
1262 month: 10,
1263 day: 18,
1264 },
1265 DateCase {
1266 year: -47,
1267 month: 6,
1268 day: 3,
1269 },
1270 DateCase {
1271 year: 75,
1272 month: 7,
1273 day: 13,
1274 },
1275 DateCase {
1276 year: 403,
1277 month: 10,
1278 day: 5,
1279 },
1280 DateCase {
1281 year: 489,
1282 month: 5,
1283 day: 22,
1284 },
1285 DateCase {
1286 year: 586,
1287 month: 2,
1288 day: 7,
1289 },
1290 DateCase {
1291 year: 637,
1292 month: 8,
1293 day: 7,
1294 },
1295 DateCase {
1296 year: 687,
1297 month: 2,
1298 day: 21,
1299 },
1300 DateCase {
1301 year: 697,
1302 month: 7,
1303 day: 7,
1304 },
1305 DateCase {
1306 year: 793,
1307 month: 6,
1308 day: 29,
1309 },
1310 DateCase {
1311 year: 839,
1312 month: 7,
1313 day: 6,
1314 },
1315 DateCase {
1316 year: 897,
1317 month: 6,
1318 day: 2,
1319 },
1320 DateCase {
1321 year: 960,
1322 month: 9,
1323 day: 30,
1324 },
1325 DateCase {
1326 year: 967,
1327 month: 5,
1328 day: 27,
1329 },
1330 DateCase {
1331 year: 1058,
1332 month: 5,
1333 day: 18,
1334 },
1335 DateCase {
1336 year: 1091,
1337 month: 6,
1338 day: 3,
1339 },
1340 DateCase {
1341 year: 1128,
1342 month: 8,
1343 day: 4,
1344 },
1345 DateCase {
1346 year: 1182,
1347 month: 2,
1348 day: 4,
1349 },
1350 DateCase {
1351 year: 1234,
1352 month: 10,
1353 day: 10,
1354 },
1355 DateCase {
1356 year: 1255,
1357 month: 1,
1358 day: 11,
1359 },
1360 DateCase {
1361 year: 1321,
1362 month: 1,
1363 day: 20,
1364 },
1365 DateCase {
1366 year: 1348,
1367 month: 3,
1368 day: 19,
1369 },
1370 DateCase {
1371 year: 1360,
1372 month: 9,
1373 day: 7,
1374 },
1375 DateCase {
1376 year: 1362,
1377 month: 4,
1378 day: 13,
1379 },
1380 DateCase {
1381 year: 1362,
1382 month: 10,
1383 day: 7,
1384 },
1385 DateCase {
1386 year: 1412,
1387 month: 9,
1388 day: 12,
1389 },
1390 DateCase {
1391 year: 1416,
1392 month: 10,
1393 day: 5,
1394 },
1395 DateCase {
1396 year: 1460,
1397 month: 10,
1398 day: 12,
1399 },
1400 DateCase {
1401 year: 1518,
1402 month: 3,
1403 day: 5,
1404 },
1405 ];
1406
1407 static ARITHMETIC_CASES: [DateCase; 33] = [
1408 DateCase {
1409 year: -1245,
1410 month: 12,
1411 day: 9,
1412 },
1413 DateCase {
1414 year: -813,
1415 month: 2,
1416 day: 23,
1417 },
1418 DateCase {
1419 year: -568,
1420 month: 4,
1421 day: 1,
1422 },
1423 DateCase {
1424 year: -501,
1425 month: 4,
1426 day: 6,
1427 },
1428 DateCase {
1429 year: -157,
1430 month: 10,
1431 day: 17,
1432 },
1433 DateCase {
1434 year: -47,
1435 month: 6,
1436 day: 3,
1437 },
1438 DateCase {
1439 year: 75,
1440 month: 7,
1441 day: 13,
1442 },
1443 DateCase {
1444 year: 403,
1445 month: 10,
1446 day: 5,
1447 },
1448 DateCase {
1449 year: 489,
1450 month: 5,
1451 day: 22,
1452 },
1453 DateCase {
1454 year: 586,
1455 month: 2,
1456 day: 7,
1457 },
1458 DateCase {
1459 year: 637,
1460 month: 8,
1461 day: 7,
1462 },
1463 DateCase {
1464 year: 687,
1465 month: 2,
1466 day: 20,
1467 },
1468 DateCase {
1469 year: 697,
1470 month: 7,
1471 day: 7,
1472 },
1473 DateCase {
1474 year: 793,
1475 month: 7,
1476 day: 1,
1477 },
1478 DateCase {
1479 year: 839,
1480 month: 7,
1481 day: 6,
1482 },
1483 DateCase {
1484 year: 897,
1485 month: 6,
1486 day: 1,
1487 },
1488 DateCase {
1489 year: 960,
1490 month: 9,
1491 day: 30,
1492 },
1493 DateCase {
1494 year: 967,
1495 month: 5,
1496 day: 27,
1497 },
1498 DateCase {
1499 year: 1058,
1500 month: 5,
1501 day: 18,
1502 },
1503 DateCase {
1504 year: 1091,
1505 month: 6,
1506 day: 2,
1507 },
1508 DateCase {
1509 year: 1128,
1510 month: 8,
1511 day: 4,
1512 },
1513 DateCase {
1514 year: 1182,
1515 month: 2,
1516 day: 3,
1517 },
1518 DateCase {
1519 year: 1234,
1520 month: 10,
1521 day: 10,
1522 },
1523 DateCase {
1524 year: 1255,
1525 month: 1,
1526 day: 11,
1527 },
1528 DateCase {
1529 year: 1321,
1530 month: 1,
1531 day: 21,
1532 },
1533 DateCase {
1534 year: 1348,
1535 month: 3,
1536 day: 19,
1537 },
1538 DateCase {
1539 year: 1360,
1540 month: 9,
1541 day: 8,
1542 },
1543 DateCase {
1544 year: 1362,
1545 month: 4,
1546 day: 13,
1547 },
1548 DateCase {
1549 year: 1362,
1550 month: 10,
1551 day: 7,
1552 },
1553 DateCase {
1554 year: 1412,
1555 month: 9,
1556 day: 13,
1557 },
1558 DateCase {
1559 year: 1416,
1560 month: 10,
1561 day: 5,
1562 },
1563 DateCase {
1564 year: 1460,
1565 month: 10,
1566 day: 12,
1567 },
1568 DateCase {
1569 year: 1518,
1570 month: 3,
1571 day: 5,
1572 },
1573 ];
1574
1575 static ASTRONOMICAL_CASES: [DateCase; 33] = [
1576 DateCase {
1577 year: -1245,
1578 month: 12,
1579 day: 10,
1580 },
1581 DateCase {
1582 year: -813,
1583 month: 2,
1584 day: 24,
1585 },
1586 DateCase {
1587 year: -568,
1588 month: 4,
1589 day: 2,
1590 },
1591 DateCase {
1592 year: -501,
1593 month: 4,
1594 day: 7,
1595 },
1596 DateCase {
1597 year: -157,
1598 month: 10,
1599 day: 18,
1600 },
1601 DateCase {
1602 year: -47,
1603 month: 6,
1604 day: 4,
1605 },
1606 DateCase {
1607 year: 75,
1608 month: 7,
1609 day: 14,
1610 },
1611 DateCase {
1612 year: 403,
1613 month: 10,
1614 day: 6,
1615 },
1616 DateCase {
1617 year: 489,
1618 month: 5,
1619 day: 23,
1620 },
1621 DateCase {
1622 year: 586,
1623 month: 2,
1624 day: 8,
1625 },
1626 DateCase {
1627 year: 637,
1628 month: 8,
1629 day: 8,
1630 },
1631 DateCase {
1632 year: 687,
1633 month: 2,
1634 day: 21,
1635 },
1636 DateCase {
1637 year: 697,
1638 month: 7,
1639 day: 8,
1640 },
1641 DateCase {
1642 year: 793,
1643 month: 7,
1644 day: 2,
1645 },
1646 DateCase {
1647 year: 839,
1648 month: 7,
1649 day: 7,
1650 },
1651 DateCase {
1652 year: 897,
1653 month: 6,
1654 day: 2,
1655 },
1656 DateCase {
1657 year: 960,
1658 month: 10,
1659 day: 1,
1660 },
1661 DateCase {
1662 year: 967,
1663 month: 5,
1664 day: 28,
1665 },
1666 DateCase {
1667 year: 1058,
1668 month: 5,
1669 day: 19,
1670 },
1671 DateCase {
1672 year: 1091,
1673 month: 6,
1674 day: 3,
1675 },
1676 DateCase {
1677 year: 1128,
1678 month: 8,
1679 day: 5,
1680 },
1681 DateCase {
1682 year: 1182,
1683 month: 2,
1684 day: 4,
1685 },
1686 DateCase {
1687 year: 1234,
1688 month: 10,
1689 day: 11,
1690 },
1691 DateCase {
1692 year: 1255,
1693 month: 1,
1694 day: 12,
1695 },
1696 DateCase {
1697 year: 1321,
1698 month: 1,
1699 day: 22,
1700 },
1701 DateCase {
1702 year: 1348,
1703 month: 3,
1704 day: 20,
1705 },
1706 DateCase {
1707 year: 1360,
1708 month: 9,
1709 day: 9,
1710 },
1711 DateCase {
1712 year: 1362,
1713 month: 4,
1714 day: 14,
1715 },
1716 DateCase {
1717 year: 1362,
1718 month: 10,
1719 day: 8,
1720 },
1721 DateCase {
1722 year: 1412,
1723 month: 9,
1724 day: 14,
1725 },
1726 DateCase {
1727 year: 1416,
1728 month: 10,
1729 day: 6,
1730 },
1731 DateCase {
1732 year: 1460,
1733 month: 10,
1734 day: 13,
1735 },
1736 DateCase {
1737 year: 1518,
1738 month: 3,
1739 day: 6,
1740 },
1741 ];
1742
1743 #[test]
1744 fn test_simulated_hijri_from_rd() {
1745 let calendar = HijriSimulated::new_mecca();
1746 let calendar = Ref(&calendar);
1747 for (case, f_date) in SIMULATED_CASES.iter().zip(TEST_RD.iter()) {
1748 let date = Date::try_new_simulated_hijri_with_calendar(
1749 case.year, case.month, case.day, calendar,
1750 )
1751 .unwrap();
1752 let iso = Date::from_rata_die(RataDie::new(*f_date), Iso);
1753
1754 assert_eq!(iso.to_calendar(calendar).inner, date.inner, "{case:?}");
1755 }
1756 }
1757
1758 #[test]
1759 fn test_rd_from_simulated_hijri() {
1760 let calendar = HijriSimulated::new_mecca();
1761 let calendar = Ref(&calendar);
1762 for (case, f_date) in SIMULATED_CASES.iter().zip(TEST_RD.iter()) {
1763 let date = Date::try_new_simulated_hijri_with_calendar(
1764 case.year, case.month, case.day, calendar,
1765 )
1766 .unwrap();
1767 assert_eq!(date.to_rata_die(), RataDie::new(*f_date), "{case:?}");
1768 }
1769 }
1770
1771 #[test]
1772 fn test_rd_from_hijri() {
1773 let calendar = HijriTabular::new(HijriTabularLeapYears::TypeII, HijriTabularEpoch::Friday);
1774 let calendar = Ref(&calendar);
1775 for (case, f_date) in ARITHMETIC_CASES.iter().zip(TEST_RD.iter()) {
1776 let date = Date::try_new_hijri_tabular_with_calendar(
1777 case.year, case.month, case.day, calendar,
1778 )
1779 .unwrap();
1780 assert_eq!(date.to_rata_die(), RataDie::new(*f_date), "{case:?}");
1781 }
1782 }
1783
1784 #[test]
1785 fn test_hijri_from_rd() {
1786 let calendar = HijriTabular::new(HijriTabularLeapYears::TypeII, HijriTabularEpoch::Friday);
1787 let calendar = Ref(&calendar);
1788 for (case, f_date) in ARITHMETIC_CASES.iter().zip(TEST_RD.iter()) {
1789 let date = Date::try_new_hijri_tabular_with_calendar(
1790 case.year, case.month, case.day, calendar,
1791 )
1792 .unwrap();
1793 let date_rd = Date::from_rata_die(RataDie::new(*f_date), calendar);
1794
1795 assert_eq!(date, date_rd, "{case:?}");
1796 }
1797 }
1798
1799 #[test]
1800 fn test_rd_from_hijri_tbla() {
1801 let calendar =
1802 HijriTabular::new(HijriTabularLeapYears::TypeII, HijriTabularEpoch::Thursday);
1803 let calendar = Ref(&calendar);
1804 for (case, f_date) in ASTRONOMICAL_CASES.iter().zip(TEST_RD.iter()) {
1805 let date = Date::try_new_hijri_tabular_with_calendar(
1806 case.year, case.month, case.day, calendar,
1807 )
1808 .unwrap();
1809 assert_eq!(date.to_rata_die(), RataDie::new(*f_date), "{case:?}");
1810 }
1811 }
1812
1813 #[test]
1814 fn test_hijri_tbla_from_rd() {
1815 let calendar =
1816 HijriTabular::new(HijriTabularLeapYears::TypeII, HijriTabularEpoch::Thursday);
1817 let calendar = Ref(&calendar);
1818 for (case, f_date) in ASTRONOMICAL_CASES.iter().zip(TEST_RD.iter()) {
1819 let date = Date::try_new_hijri_tabular_with_calendar(
1820 case.year, case.month, case.day, calendar,
1821 )
1822 .unwrap();
1823 let date_rd = Date::from_rata_die(RataDie::new(*f_date), calendar);
1824
1825 assert_eq!(date, date_rd, "{case:?}");
1826 }
1827 }
1828
1829 #[test]
1830 fn test_saudi_hijri_from_rd() {
1831 let calendar = HijriUmmAlQura::new();
1832 let calendar = Ref(&calendar);
1833 for (case, f_date) in UMMALQURA_CASES.iter().zip(TEST_RD.iter()) {
1834 let date = Date::try_new_ummalqura(case.year, case.month, case.day).unwrap();
1835 let date_rd = Date::from_rata_die(RataDie::new(*f_date), calendar);
1836
1837 assert_eq!(date, date_rd, "{case:?}");
1838 }
1839 }
1840
1841 #[test]
1842 fn test_rd_from_saudi_hijri() {
1843 for (case, f_date) in UMMALQURA_CASES.iter().zip(TEST_RD.iter()) {
1844 let date = Date::try_new_ummalqura(case.year, case.month, case.day).unwrap();
1845 assert_eq!(date.to_rata_die(), RataDie::new(*f_date), "{case:?}");
1846 }
1847 }
1848
1849 #[ignore] #[test]
1851 fn test_days_in_provided_year_simulated() {
1852 let calendar = HijriSimulated::new_mecca();
1853 let calendar = Ref(&calendar);
1854 let sum_days_in_year: i64 = (START_YEAR..END_YEAR)
1857 .map(|year| {
1858 HijriSimulated::days_in_provided_year(
1859 HijriSimulatedLocation::Mecca.compute_year_info(year),
1860 ) as i64
1861 })
1862 .sum();
1863 let expected_number_of_days =
1864 Date::try_new_simulated_hijri_with_calendar(END_YEAR, 1, 1, calendar)
1865 .unwrap()
1866 .to_rata_die()
1867 - Date::try_new_simulated_hijri_with_calendar(START_YEAR, 1, 1, calendar)
1868 .unwrap()
1869 .to_rata_die(); let tolerance = 1; assert!(
1873 (sum_days_in_year - expected_number_of_days).abs() <= tolerance,
1874 "Difference between sum_days_in_year and expected_number_of_days is more than the tolerance"
1875 );
1876 }
1877
1878 #[ignore] #[test]
1880 fn test_days_in_provided_year_ummalqura() {
1881 let sum_days_in_year: i64 = (START_YEAR..END_YEAR)
1884 .map(|year| {
1885 HijriUmmAlQura::days_in_provided_year(HijriUmmAlQura.load_or_compute_info(year))
1886 as i64
1887 })
1888 .sum();
1889 let expected_number_of_days = Date::try_new_ummalqura(END_YEAR, 1, 1)
1890 .unwrap()
1891 .to_rata_die()
1892 - (Date::try_new_ummalqura(START_YEAR, 1, 1).unwrap()).to_rata_die(); assert_eq!(sum_days_in_year, expected_number_of_days);
1895 }
1896
1897 #[test]
1898 fn test_regression_3868() {
1899 let iso = Date::try_new_iso(2011, 4, 4).unwrap();
1901 let hijri = iso.to_calendar(HijriUmmAlQura::new());
1902 assert_eq!(hijri.day_of_month().0, 30);
1904 assert_eq!(hijri.month().ordinal, 4);
1905 assert_eq!(hijri.era_year().year, 1432);
1906 }
1907
1908 #[test]
1909 fn test_regression_4914() {
1910 let dt = HijriUmmAlQura::new()
1912 .from_codes(Some("bh"), 6824, MonthCode::new_normal(1).unwrap(), 1)
1913 .unwrap();
1914 assert_eq!(dt.0.day, 1);
1915 assert_eq!(dt.0.month, 1);
1916 assert_eq!(dt.0.year.value, -6823);
1917 }
1918
1919 #[test]
1920 fn test_regression_5069_uaq() {
1921 let cached = HijriUmmAlQura::new();
1922
1923 let cached = crate::Ref(&cached);
1924
1925 let dt_cached = Date::try_new_ummalqura(1391, 1, 29).unwrap();
1926
1927 assert_eq!(dt_cached.to_iso().to_calendar(cached), dt_cached);
1928 }
1929
1930 #[test]
1931 fn test_regression_5069_obs() {
1932 let cached = HijriSimulated::new_mecca();
1933 let comp = HijriSimulated::new_mecca_always_calculating();
1934
1935 let cached = crate::Ref(&cached);
1936 let comp = crate::Ref(&comp);
1937
1938 let dt_cached = Date::try_new_simulated_hijri_with_calendar(1390, 1, 30, cached).unwrap();
1939 let dt_comp = Date::try_new_simulated_hijri_with_calendar(1390, 1, 30, comp).unwrap();
1940
1941 assert_eq!(dt_cached.to_iso(), dt_comp.to_iso());
1942
1943 assert_eq!(dt_comp.to_iso().to_calendar(comp), dt_comp);
1944 assert_eq!(dt_cached.to_iso().to_calendar(cached), dt_cached);
1945
1946 let dt = Date::try_new_iso(2000, 5, 5).unwrap();
1947
1948 assert!(dt.to_calendar(comp).day_of_month().0 > 0);
1949 assert!(dt.to_calendar(cached).day_of_month().0 > 0);
1950 }
1951
1952 #[test]
1953 fn test_regression_6197() {
1954 let cached = HijriUmmAlQura::new();
1955
1956 let cached = crate::Ref(&cached);
1957
1958 let iso = Date::try_new_iso(2025, 2, 26).unwrap();
1959
1960 let cached = iso.to_calendar(cached);
1961
1962 assert_eq!(
1964 (
1965 cached.day_of_month().0,
1966 cached.month().ordinal,
1967 cached.era_year().year
1968 ),
1969 (27, 8, 1446)
1970 );
1971 }
1972
1973 #[test]
1974 fn test_uaq_icu4c_agreement() {
1975 const ICU4C_ENCODED_MONTH_LENGTHS: [u16; 1601 - 1300] = [
1977 0x0AAA, 0x0D54, 0x0EC9, 0x06D4, 0x06EA, 0x036C, 0x0AAD, 0x0555, 0x06A9, 0x0792, 0x0BA9,
1978 0x05D4, 0x0ADA, 0x055C, 0x0D2D, 0x0695, 0x074A, 0x0B54, 0x0B6A, 0x05AD, 0x04AE, 0x0A4F,
1979 0x0517, 0x068B, 0x06A5, 0x0AD5, 0x02D6, 0x095B, 0x049D, 0x0A4D, 0x0D26, 0x0D95, 0x05AC,
1980 0x09B6, 0x02BA, 0x0A5B, 0x052B, 0x0A95, 0x06CA, 0x0AE9, 0x02F4, 0x0976, 0x02B6, 0x0956,
1981 0x0ACA, 0x0BA4, 0x0BD2, 0x05D9, 0x02DC, 0x096D, 0x054D, 0x0AA5, 0x0B52, 0x0BA5, 0x05B4,
1982 0x09B6, 0x0557, 0x0297, 0x054B, 0x06A3, 0x0752, 0x0B65, 0x056A, 0x0AAB, 0x052B, 0x0C95,
1983 0x0D4A, 0x0DA5, 0x05CA, 0x0AD6, 0x0957, 0x04AB, 0x094B, 0x0AA5, 0x0B52, 0x0B6A, 0x0575,
1984 0x0276, 0x08B7, 0x045B, 0x0555, 0x05A9, 0x05B4, 0x09DA, 0x04DD, 0x026E, 0x0936, 0x0AAA,
1985 0x0D54, 0x0DB2, 0x05D5, 0x02DA, 0x095B, 0x04AB, 0x0A55, 0x0B49, 0x0B64, 0x0B71, 0x05B4,
1986 0x0AB5, 0x0A55, 0x0D25, 0x0E92, 0x0EC9, 0x06D4, 0x0AE9, 0x096B, 0x04AB, 0x0A93, 0x0D49,
1987 0x0DA4, 0x0DB2, 0x0AB9, 0x04BA, 0x0A5B, 0x052B, 0x0A95, 0x0B2A, 0x0B55, 0x055C, 0x04BD,
1988 0x023D, 0x091D, 0x0A95, 0x0B4A, 0x0B5A, 0x056D, 0x02B6, 0x093B, 0x049B, 0x0655, 0x06A9,
1989 0x0754, 0x0B6A, 0x056C, 0x0AAD, 0x0555, 0x0B29, 0x0B92, 0x0BA9, 0x05D4, 0x0ADA, 0x055A,
1990 0x0AAB, 0x0595, 0x0749, 0x0764, 0x0BAA, 0x05B5, 0x02B6, 0x0A56, 0x0E4D, 0x0B25, 0x0B52,
1991 0x0B6A, 0x05AD, 0x02AE, 0x092F, 0x0497, 0x064B, 0x06A5, 0x06AC, 0x0AD6, 0x055D, 0x049D,
1992 0x0A4D, 0x0D16, 0x0D95, 0x05AA, 0x05B5, 0x02DA, 0x095B, 0x04AD, 0x0595, 0x06CA, 0x06E4,
1993 0x0AEA, 0x04F5, 0x02B6, 0x0956, 0x0AAA, 0x0B54, 0x0BD2, 0x05D9, 0x02EA, 0x096D, 0x04AD,
1994 0x0A95, 0x0B4A, 0x0BA5, 0x05B2, 0x09B5, 0x04D6, 0x0A97, 0x0547, 0x0693, 0x0749, 0x0B55,
1995 0x056A, 0x0A6B, 0x052B, 0x0A8B, 0x0D46, 0x0DA3, 0x05CA, 0x0AD6, 0x04DB, 0x026B, 0x094B,
1996 0x0AA5, 0x0B52, 0x0B69, 0x0575, 0x0176, 0x08B7, 0x025B, 0x052B, 0x0565, 0x05B4, 0x09DA,
1997 0x04ED, 0x016D, 0x08B6, 0x0AA6, 0x0D52, 0x0DA9, 0x05D4, 0x0ADA, 0x095B, 0x04AB, 0x0653,
1998 0x0729, 0x0762, 0x0BA9, 0x05B2, 0x0AB5, 0x0555, 0x0B25, 0x0D92, 0x0EC9, 0x06D2, 0x0AE9,
1999 0x056B, 0x04AB, 0x0A55, 0x0D29, 0x0D54, 0x0DAA, 0x09B5, 0x04BA, 0x0A3B, 0x049B, 0x0A4D,
2000 0x0AAA, 0x0AD5, 0x02DA, 0x095D, 0x045E, 0x0A2E, 0x0C9A, 0x0D55, 0x06B2, 0x06B9, 0x04BA,
2001 0x0A5D, 0x052D, 0x0A95, 0x0B52, 0x0BA8, 0x0BB4, 0x05B9, 0x02DA, 0x095A, 0x0B4A, 0x0DA4,
2002 0x0ED1, 0x06E8, 0x0B6A, 0x056D, 0x0535, 0x0695, 0x0D4A, 0x0DA8, 0x0DD4, 0x06DA, 0x055B,
2003 0x029D, 0x062B, 0x0B15, 0x0B4A, 0x0B95, 0x05AA, 0x0AAE, 0x092E, 0x0C8F, 0x0527, 0x0695,
2004 0x06AA, 0x0AD6, 0x055D, 0x029D,
2005 ];
2006
2007 const ICU4C_YEAR_START_ESTIMATE_FIX: [i64; 1601 - 1300] = [
2009 0, 0, -1, 0, -1, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, -1, 0, 1, 0, 1, 1, 0, 0, 0, 0,
2010 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, -1, -1, 0, 0, 0, 1, 0, 0, -1, 0, 0,
2011 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 1, 1, 0, 0, -1, 0, 1, 0, 1, 1, 0, 0, -1,
2012 0, 1, 0, 0, 0, -1, 0, 1, 0, 1, 0, 0, 0, -1, 0, 0, 0, 0, -1, -1, 0, -1, 0, 1, 0, 0, 0,
2013 -1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, -1, -1, 0, 0, 0, 1, 0, 0, -1, -1, 0, -1, 0, 0,
2014 -1, -1, 0, -1, 0, -1, 0, 0, -1, -1, 0, 0, 0, 0, 0, 0, -1, 0, 1, 0, 1, 1, 0, 0, -1, 0,
2015 1, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, -1, 0, 1, 0, 0, -1, -1, 0, 0, 0, 1, 0, 0, 0, 0, 0,
2016 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, -1, 0, 0, 0, 1, 1, 0, 0, -1, 0, 1, 0, 1, 1, 0, 0, 0,
2017 0, 1, 0, 0, 0, -1, 0, 0, 0, 1, 0, 0, 0, -1, 0, 0, 0, 0, 0, -1, 0, -1, 0, 1, 0, 0, 0,
2018 -1, 0, 1, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, -1, 0, 0, 0, 0, 1, 0, 0, 0, -1, 0, 0, 0, 0, -1,
2019 -1, 0, -1, 0, 1, 0, 0, -1, -1, 0, 0, 1, 1, 0, 0, -1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1,
2020 ];
2021
2022 let icu4c = ICU4C_ENCODED_MONTH_LENGTHS
2023 .into_iter()
2024 .zip(ICU4C_YEAR_START_ESTIMATE_FIX)
2025 .enumerate()
2026 .map(
2027 |(years_since_1300, (encoded_months_lengths, year_start_estimate_fix))| {
2028 let month_lengths =
2030 core::array::from_fn(|i| (1 << (11 - i)) & encoded_months_lengths != 0);
2031 let year_start = ((354.36720 * years_since_1300 as f64) + 460322.05 + 0.5)
2033 as i64
2034 + year_start_estimate_fix;
2035 HijriYearInfo {
2036 value: 1300 + years_since_1300 as i32,
2037 month_lengths,
2038 start_day: ISLAMIC_EPOCH_FRIDAY + year_start,
2039 }
2040 },
2041 )
2042 .collect::<Vec<_>>();
2043
2044 let icu4x = (1300..=1600)
2045 .map(|y| HijriUmmAlQura.load_or_compute_info(y))
2046 .collect::<Vec<_>>();
2047
2048 assert_eq!(icu4x, icu4c);
2049 }
2050}