icu_calendar/cal/
hijri.rs

1// This file is part of ICU4X. For terms of use, please see the file
2// called LICENSE at the top level of the ICU4X source tree
3// (online at: https://github.com/unicode-org/icu4x/blob/main/LICENSE ).
4
5//! This module contains types and implementations for the Hijri calendars.
6//!
7//! ```rust
8//! use icu::calendar::cal::HijriSimulated;
9//! use icu::calendar::Date;
10//!
11//! let hijri = HijriSimulated::new_mecca_always_calculating();
12//! let hijri_date =
13//!     Date::try_new_simulated_hijri_with_calendar(1348, 10, 11, hijri)
14//!         .expect("Failed to initialize Hijri Date instance.");
15//!
16//! assert_eq!(hijri_date.era_year().year, 1348);
17//! assert_eq!(hijri_date.month().ordinal, 10);
18//! assert_eq!(hijri_date.day_of_month().0, 11);
19//! ```
20
21use 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/// The [simulated Hijri Calendar](https://en.wikipedia.org/wiki/Islamic_calendar)
58///
59/// # Era codes
60///
61/// This calendar uses two era codes: `ah`, and `bh`, corresponding to the Anno Hegirae and Before Hijrah eras
62///
63/// # Month codes
64///
65/// This calendar is a pure lunar calendar with no leap months. It uses month codes
66/// `"M01" - "M12"`.
67#[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/// The [Umm al-Qura Hijri Calendar](https://en.wikipedia.org/wiki/Islamic_calendar#Saudi_Arabia's_Umm_al-Qura_calendar)
87///
88/// This calendar is the official calendar in Saudi Arabia.
89///
90/// # Era codes
91///
92/// This calendar uses two era codes: `ah`, and `bh`, corresponding to the Anno Hegirae and Before Hijrah eras
93///
94/// # Month codes
95///
96/// This calendar is a pure lunar calendar with no leap months. It uses month codes
97/// `"M01" - "M12"`.
98#[derive(Clone, Debug, Default)]
99#[non_exhaustive]
100pub struct HijriUmmAlQura;
101
102/// The [tabular Hijri Calendar](https://en.wikipedia.org/wiki/Tabular_Islamic_calendar).
103///
104/// See [`HijriTabularEpoch`] and [`HijriTabularLeapYears`] for customization.
105///
106/// The most common version of this calendar uses [`HijriTabularEpoch::Friday`] and [`HijriTabularLeapYears::TypeII`].
107///
108/// # Era codes
109///
110/// This calendar uses two era codes: `ah`, and `bh`, corresponding to the Anno Hegirae and Before Hijrah eras
111///
112/// # Month codes
113///
114/// This calendar is a pure lunar calendar with no leap months. It uses month codes
115/// `"M01" - "M12"`.
116#[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    /// Creates a new [`HijriSimulated`] for reference location Mecca, with some compiled data containing precomputed calendrical calculations.
124    ///
125    /// ✨ *Enabled with the `compiled_data` Cargo feature.*
126    ///
127    /// [📚 Help choosing a constructor](icu_provider::constructors)
128    #[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    /// Construct a new [`HijriSimulated`] for reference location Mecca, without any precomputed calendrical calculations.
157    pub const fn new_mecca_always_calculating() -> Self {
158        Self {
159            location: HijriSimulatedLocation::Mecca,
160            data: None,
161        }
162    }
163
164    /// Compute a cache for this calendar
165    #[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    /// Creates a new [`HijriUmmAlQura`].
180    pub const fn new() -> Self {
181        Self
182    }
183}
184
185/// The epoch for the [`HijriTabular`] calendar.
186#[non_exhaustive]
187#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq, PartialOrd, Ord)]
188pub enum HijriTabularEpoch {
189    /// Thusday July 15, 622 AD (0622-07-18 ISO)
190    Thursday,
191    /// Friday July 16, 622 AD (0622-07-19 ISO)
192    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/// The leap year rule for the [`HijriTabular`] calendar.
205///
206/// This specifies which years of a 30-year cycle have an additional day at
207/// the end of the year.
208#[non_exhaustive]
209#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq, PartialOrd, Ord)]
210pub enum HijriTabularLeapYears {
211    /// Leap years 2, 5, 7, 10, 13, 16, 18, 21, 24, 26, 29
212    TypeII,
213}
214
215impl HijriTabular {
216    /// Construct a new [`HijriTabular`] with the given leap year rule and epoch.
217    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    /// Get the cached data for a given extended year
237    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    /// The number of days in a given 1-indexed month
266    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    /// Get the date's R.D. given (m, d) in this info's year
283    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        // We divide by 30, not 29, to account for the case where all months before this
296        // were length 30 (possible near the beginning of the year)
297        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    // Which day of year is the last day of a month (month is 1-indexed)
322    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/// The inner date type used for representing [`Date`]s of [`HijriSimulated`]. See [`Date`] and [`HijriSimulated`] for more details.
344
345#[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    // As an true lunar calendar, it does not have leap years.
364    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        // +1 because the epoch is new year of year 1
403        // truncating instead of flooring does not matter, as this is well-defined for
404        // positive years only
405        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            // To maintain invariants for calendar arithmetic, if astronomy finds
570            // a 31-day month, "move" the day to the first 29-day month in the
571            // same year to maintain all months at 29 or 30 days.
572            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    /// Construct new simulated Hijri Date.
599    ///
600    /// ```rust
601    /// use icu::calendar::cal::HijriSimulated;
602    /// use icu::calendar::Date;
603    ///
604    /// let hijri = HijriSimulated::new_mecca_always_calculating();
605    ///
606    /// let date_hijri =
607    ///     Date::try_new_simulated_hijri_with_calendar(1392, 4, 25, hijri)
608    ///         .expect("Failed to initialize Hijri Date instance.");
609    ///
610    /// assert_eq!(date_hijri.era_year().year, 1392);
611    /// assert_eq!(date_hijri.month().ordinal, 4);
612    /// assert_eq!(date_hijri.day_of_month().0, 25);
613    /// ```
614    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)]
628/// The inner date type used for representing [`Date`]s of [`HijriUmmAlQura`]. See [`Date`] and [`HijriUmmAlQura`] for more details.
629pub 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    // As an true lunar calendar, it does not have leap years.
647    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        // +1 because the epoch is new year of year 1
686        // truncating instead of flooring does not matter, as this is well-defined for
687        // positive years only
688        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    /// Construct new Hijri Umm al-Qura Date.
814    ///
815    /// ```rust
816    /// use icu::calendar::cal::HijriUmmAlQura;
817    /// use icu::calendar::Date;
818    ///
819    /// let date_hijri = Date::try_new_ummalqura(1392, 4, 25)
820    ///     .expect("Failed to initialize Hijri Date instance.");
821    ///
822    /// assert_eq!(date_hijri.era_year().year, 1392);
823    /// assert_eq!(date_hijri.month().ordinal, 4);
824    /// assert_eq!(date_hijri.day_of_month().0, 25);
825    /// ```
826    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/// The inner date type used for representing [`Date`]s of [`HijriTabular`]. See [`Date`] and [`HijriTabular`] for more details.
840
841#[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    /// Construct new Tabular Hijri Date.
1016    ///
1017    /// ```rust
1018    /// use icu::calendar::cal::{
1019    ///     HijriTabular, HijriTabularEpoch, HijriTabularLeapYears,
1020    /// };
1021    /// use icu::calendar::Date;
1022    ///
1023    /// let hijri = HijriTabular::new(
1024    ///     HijriTabularLeapYears::TypeII,
1025    ///     HijriTabularEpoch::Thursday,
1026    /// );
1027    ///
1028    /// let date_hijri =
1029    ///     Date::try_new_hijri_tabular_with_calendar(1392, 4, 25, hijri)
1030    ///         .expect("Failed to initialize Hijri Date instance.");
1031    ///
1032    /// assert_eq!(date_hijri.era_year().year, 1392);
1033    /// assert_eq!(date_hijri.month().ordinal, 4);
1034    /// assert_eq!(date_hijri.day_of_month().0, 25);
1035    /// ```
1036    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] // slow
1850    #[test]
1851    fn test_days_in_provided_year_simulated() {
1852        let calendar = HijriSimulated::new_mecca();
1853        let calendar = Ref(&calendar);
1854        // -1245 1 1 = -214526 (R.D Date)
1855        // 1518 1 1 = 764589 (R.D Date)
1856        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(); // The number of days between Hijri years -1245 and 1518
1870        let tolerance = 1; // One day tolerance (See Astronomical::month_length for more context)
1871
1872        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] // slow
1879    #[test]
1880    fn test_days_in_provided_year_ummalqura() {
1881        // -1245 1 1 = -214528 (R.D Date)
1882        // 1518 1 1 = 764588 (R.D Date)
1883        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(); // The number of days between Umm al-Qura Hijri years -1245 and 1518
1893
1894        assert_eq!(sum_days_in_year, expected_number_of_days);
1895    }
1896
1897    #[test]
1898    fn test_regression_3868() {
1899        // This date used to panic on creation
1900        let iso = Date::try_new_iso(2011, 4, 4).unwrap();
1901        let hijri = iso.to_calendar(HijriUmmAlQura::new());
1902        // Data from https://www.ummulqura.org.sa/Index.aspx
1903        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        // https://github.com/unicode-org/icu4x/issues/4914
1911        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        // Data from https://www.ummulqura.org.sa/
1963        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        // From https://github.com/unicode-org/icu/blob/1bf6bf774dbc8c6c2051963a81100ea1114b497f/icu4c/source/i18n/islamcal.cpp#L87
1976        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        // From https://github.com/unicode-org/icu/blob/1bf6bf774dbc8c6c2051963a81100ea1114b497f/icu4c/source/i18n/islamcal.cpp#L264
2008        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                    // https://github.com/unicode-org/icu/blob/1bf6bf774dbc8c6c2051963a81100ea1114b497f/icu4c/source/i18n/islamcal.cpp#L858
2029                    let month_lengths =
2030                        core::array::from_fn(|i| (1 << (11 - i)) & encoded_months_lengths != 0);
2031                    // From https://github.com/unicode-org/icu/blob/1bf6bf774dbc8c6c2051963a81100ea1114b497f/icu4c/source/i18n/islamcal.cpp#L813
2032                    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}