icu_calendar/
islamic.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 Islamic calendars.
6//!
7//! ```rust
8//! use icu::calendar::islamic::IslamicObservational;
9//! use icu::calendar::{Date, DateTime, Ref};
10//!
11//! let islamic = IslamicObservational::new_always_calculating();
12//! let islamic = Ref(&islamic); // to avoid cloning
13//!
14//! // `Date` type
15//! let islamic_date =
16//!     Date::try_new_observational_islamic_date(1348, 10, 11, islamic)
17//!         .expect("Failed to initialize islamic Date instance.");
18//!
19//! // `DateTime` type
20//! let islamic_datetime = DateTime::try_new_observational_islamic_datetime(
21//!     1348, 10, 11, 13, 1, 0, islamic,
22//! )
23//! .expect("Failed to initialize islamic DateTime instance.");
24//!
25//! // `Date` checks
26//! assert_eq!(islamic_date.year().number, 1348);
27//! assert_eq!(islamic_date.month().ordinal, 10);
28//! assert_eq!(islamic_date.day_of_month().0, 11);
29//!
30//! // `DateTime` checks
31//! assert_eq!(islamic_datetime.date.year().number, 1348);
32//! assert_eq!(islamic_datetime.date.month().ordinal, 10);
33//! assert_eq!(islamic_datetime.date.day_of_month().0, 11);
34//! assert_eq!(islamic_datetime.time.hour.number(), 13);
35//! assert_eq!(islamic_datetime.time.minute.number(), 1);
36//! assert_eq!(islamic_datetime.time.second.number(), 0);
37//! ```
38
39use crate::calendar_arithmetic::PrecomputedDataSource;
40use crate::calendar_arithmetic::{ArithmeticDate, CalendarArithmetic};
41use crate::provider::islamic::{
42    IslamicCacheV1, IslamicObservationalCacheV1Marker, IslamicUmmAlQuraCacheV1Marker,
43    PackedIslamicYearInfo,
44};
45use crate::AnyCalendarKind;
46use crate::AsCalendar;
47use crate::Iso;
48use crate::{types, Calendar, CalendarError, Date, DateDuration, DateDurationUnit, DateTime, Time};
49use calendrical_calculations::islamic::{
50    IslamicBasedMarker, ObservationalIslamicMarker, SaudiIslamicMarker,
51};
52use calendrical_calculations::rata_die::RataDie;
53use core::marker::PhantomData;
54use icu_provider::prelude::*;
55use tinystr::tinystr;
56
57/// Islamic Observational Calendar (Default)
58///
59/// # Era codes
60///
61/// This calendar supports a single era code, Anno Mundi, with code `"ah"`
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, Default)]
68pub struct IslamicObservational {
69    data: Option<DataPayload<IslamicObservationalCacheV1Marker>>,
70}
71
72/// Civil / Arithmetical Islamic Calendar (Used for administrative purposes)
73///
74/// # Era codes
75///
76/// This calendar supports a single era code, Anno Mundi, with code `"ah"`
77///
78/// # Month codes
79///
80/// This calendar is a pure lunar calendar with no leap months. It uses month codes
81/// `"M01" - "M12"`.
82#[derive(Copy, Clone, Debug, Default, Hash, Eq, PartialEq, PartialOrd, Ord)]
83#[allow(clippy::exhaustive_structs)] // unit struct
84pub struct IslamicCivil;
85
86/// Umm al-Qura Hijri Calendar (Used in Saudi Arabia)
87///
88/// # Era codes
89///
90/// This calendar supports a single era code, Anno Mundi, with code `"ah"`
91///
92/// # Month codes
93///
94/// This calendar is a pure lunar calendar with no leap months. It uses month codes
95/// `"M01" - "M12"`.
96#[derive(Clone, Debug, Default)]
97pub struct IslamicUmmAlQura {
98    data: Option<DataPayload<IslamicUmmAlQuraCacheV1Marker>>,
99}
100
101/// A Tabular version of the Arithmetical Islamic Calendar
102///
103/// # Era codes
104///
105/// This calendar supports a single era code, Anno Mundi, with code `"ah"`
106///
107/// # Month codes
108///
109/// This calendar is a pure lunar calendar with no leap months. It uses month codes
110/// `"M01" - "M12"`.
111#[derive(Copy, Clone, Debug, Default, Hash, Eq, PartialEq, PartialOrd, Ord)]
112#[allow(clippy::exhaustive_structs)] // unit struct
113pub struct IslamicTabular;
114
115impl IslamicObservational {
116    /// Creates a new [`IslamicObservational`] with some compiled data containing precomputed calendrical calculations.
117    ///
118    /// ✨ *Enabled with the `compiled_data` Cargo feature.*
119    ///
120    /// [📚 Help choosing a constructor](icu_provider::constructors)
121    #[cfg(feature = "compiled_data")]
122    pub const fn new() -> Self {
123        Self {
124            data: Some(DataPayload::from_static_ref(
125                crate::provider::Baked::SINGLETON_CALENDAR_ISLAMICOBSERVATIONALCACHE_V1,
126            )),
127        }
128    }
129
130    icu_provider::gen_any_buffer_data_constructors!(locale: skip, options: skip, error: CalendarError,
131        #[cfg(skip)]
132        functions: [
133            new,
134            try_new_with_any_provider,
135            try_new_with_buffer_provider,
136            try_new_unstable,
137            Self,
138    ]);
139
140    #[doc = icu_provider::gen_any_buffer_unstable_docs!(UNSTABLE, Self::new)]
141    pub fn try_new_unstable<D: DataProvider<IslamicObservationalCacheV1Marker> + ?Sized>(
142        provider: &D,
143    ) -> Result<Self, CalendarError> {
144        Ok(Self {
145            data: Some(provider.load(Default::default())?.take_payload()?),
146        })
147    }
148
149    /// Construct a new [`IslamicObservational`] without any precomputed calendrical calculations.
150    pub fn new_always_calculating() -> Self {
151        Self { data: None }
152    }
153}
154
155impl IslamicCivil {
156    /// Construct a new [`IslamicCivil`]
157    pub fn new() -> Self {
158        Self
159    }
160
161    /// Construct a new [`IslamicCivil`] (deprecated: we will not add precomputation to this calendar)
162    #[deprecated = "Precomputation not needed for this calendar"]
163    pub fn new_always_calculating() -> Self {
164        Self
165    }
166}
167
168impl IslamicUmmAlQura {
169    /// Creates a new [`IslamicUmmAlQura`] with some compiled data containing precomputed calendrical calculations.
170    ///
171    /// ✨ *Enabled with the `compiled_data` Cargo feature.*
172    ///
173    /// [📚 Help choosing a constructor](icu_provider::constructors)
174    #[cfg(feature = "compiled_data")]
175    pub const fn new() -> Self {
176        Self {
177            data: Some(DataPayload::from_static_ref(
178                crate::provider::Baked::SINGLETON_CALENDAR_ISLAMICUMMALQURACACHE_V1,
179            )),
180        }
181    }
182
183    icu_provider::gen_any_buffer_data_constructors!(locale: skip, options: skip, error: CalendarError,
184        #[cfg(skip)]
185        functions: [
186            new,
187            try_new_with_any_provider,
188            try_new_with_buffer_provider,
189            try_new_unstable,
190            Self,
191    ]);
192
193    #[doc = icu_provider::gen_any_buffer_unstable_docs!(UNSTABLE, Self::new)]
194    pub fn try_new_unstable<D: DataProvider<IslamicUmmAlQuraCacheV1Marker> + ?Sized>(
195        provider: &D,
196    ) -> Result<Self, CalendarError> {
197        Ok(Self {
198            data: Some(provider.load(Default::default())?.take_payload()?),
199        })
200    }
201
202    /// Construct a new [`IslamicUmmAlQura`] without any precomputed calendrical calculations.
203    pub fn new_always_calculating() -> Self {
204        Self { data: None }
205    }
206}
207
208impl IslamicTabular {
209    /// Construct a new [`IslamicTabular`]
210    pub fn new() -> Self {
211        Self
212    }
213
214    /// Construct a new [`IslamicTabular`] (deprecated: we will not add precomputation to this calendar)
215    #[deprecated = "Precomputation not needed for this calendar"]
216    pub fn new_always_calculating() -> Self {
217        Self
218    }
219}
220
221/// Compact representation of the length of an Islamic year.
222#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)]
223enum IslamicYearLength {
224    /// Long (355-day) Islamic year
225    L355,
226    /// Short (354-day) Islamic year
227    L354,
228    /// Unexpectedly Short (353-day) Islamic year
229    ///
230    /// It is probably a bug when this year length is returned. See:
231    /// <https://github.com/unicode-org/icu4x/issues/4930>
232    L353,
233}
234
235impl Default for IslamicYearLength {
236    fn default() -> Self {
237        Self::L354
238    }
239}
240
241impl IslamicYearLength {
242    fn try_from_int(value: i64) -> Option<Self> {
243        match value {
244            355 => Some(Self::L355),
245            354 => Some(Self::L354),
246            353 => Some(Self::L353),
247            _ => None,
248        }
249    }
250    fn to_int(self) -> u16 {
251        match self {
252            Self::L355 => 355,
253            Self::L354 => 354,
254            Self::L353 => 353,
255        }
256    }
257}
258
259#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)]
260pub(crate) struct IslamicYearInfo {
261    packed_data: PackedIslamicYearInfo,
262    prev_year_length: IslamicYearLength,
263}
264
265impl IslamicYearInfo {
266    pub(crate) const LONG_YEAR_LEN: u16 = 355;
267    const SHORT_YEAR_LEN: u16 = 354;
268    pub(crate) fn new(
269        prev_packed: PackedIslamicYearInfo,
270        this_packed: PackedIslamicYearInfo,
271        extended_year: i32,
272    ) -> (Self, i32) {
273        let days_in_year = prev_packed.days_in_year();
274        let days_in_year = match IslamicYearLength::try_from_int(days_in_year as i64) {
275            Some(x) => x,
276            None => {
277                debug_assert!(false, "Found wrong year length for Islamic year {extended_year}: Expected 355, 354, or 353, got {days_in_year}");
278                Default::default()
279            }
280        };
281        let year_info = Self {
282            prev_year_length: days_in_year,
283            packed_data: this_packed,
284        };
285        (year_info, extended_year)
286    }
287
288    fn compute<IB: IslamicBasedMarker>(extended_year: i32) -> Self {
289        let ny = IB::fixed_from_islamic(extended_year, 1, 1);
290        let packed_data = PackedIslamicYearInfo::compute_with_ny::<IB>(extended_year, ny);
291        let prev_ny = IB::fixed_from_islamic(extended_year - 1, 1, 1);
292        let rd_diff = ny - prev_ny;
293        let rd_diff = match IslamicYearLength::try_from_int(rd_diff) {
294            Some(x) => x,
295            None => {
296                debug_assert!(false, "({}) Found wrong year length for Islamic year {extended_year}: Expected 355, 354, or 353, got {rd_diff}", IB::DEBUG_NAME);
297                Default::default()
298            }
299        };
300        Self {
301            prev_year_length: rd_diff,
302            packed_data,
303        }
304    }
305    /// Get the new year R.D. given the extended year that this yearinfo is for    
306    fn new_year<IB: IslamicBasedMarker>(self, extended_year: i32) -> RataDie {
307        IB::mean_synodic_ny(extended_year) + i64::from(self.packed_data.ny_offset())
308    }
309
310    /// Get the date's R.D. given (y, m, d) in this info's year
311    fn rd_for<IB: IslamicBasedMarker>(self, extended_year: i32, month: u8, day: u8) -> RataDie {
312        let ny = self.new_year::<IB>(extended_year);
313        let month_offset = if month == 1 {
314            0
315        } else {
316            self.packed_data.last_day_of_month(month - 1)
317        };
318        // -1 since the offset is 1-indexed but the new year is also day 1
319        ny - 1 + month_offset.into() + day.into()
320    }
321
322    #[inline]
323    fn days_in_prev_year(self) -> u16 {
324        self.prev_year_length.to_int()
325    }
326}
327
328/// Contains any loaded precomputed data. If constructed with Default, will
329/// *not* contain any extra data and will always compute stuff from scratch
330#[derive(Default)]
331pub(crate) struct IslamicPrecomputedData<'a, IB: IslamicBasedMarker> {
332    data: Option<&'a IslamicCacheV1<'a>>,
333    _ib: PhantomData<IB>,
334}
335
336impl<'b, IB: IslamicBasedMarker> PrecomputedDataSource<IslamicYearInfo>
337    for IslamicPrecomputedData<'b, IB>
338{
339    fn load_or_compute_info(&self, extended_year: i32) -> IslamicYearInfo {
340        self.data
341            .and_then(|d| d.get_for_extended_year(extended_year))
342            .unwrap_or_else(|| IslamicYearInfo::compute::<IB>(extended_year))
343    }
344}
345
346/// Given a year info and the first month it is possible for this date to be in, return the
347/// month and day this is in
348fn compute_month_day(info: IslamicYearInfo, mut possible_month: u8, day_of_year: u16) -> (u8, u8) {
349    let mut last_day_of_month = info.packed_data.last_day_of_month(possible_month);
350    let mut last_day_of_prev_month = if possible_month == 1 {
351        0
352    } else {
353        info.packed_data.last_day_of_month(possible_month - 1)
354    };
355
356    while day_of_year > last_day_of_month && possible_month <= 12 {
357        possible_month += 1;
358        last_day_of_prev_month = last_day_of_month;
359        last_day_of_month = info.packed_data.last_day_of_month(possible_month);
360    }
361    let day = u8::try_from(day_of_year - last_day_of_prev_month);
362    debug_assert!(
363        day.is_ok(),
364        "Found day {} that doesn't fit in month!",
365        day_of_year - last_day_of_prev_month
366    );
367    (possible_month, day.unwrap_or(29))
368}
369impl<'b, IB: IslamicBasedMarker> IslamicPrecomputedData<'b, IB> {
370    pub(crate) fn new(data: Option<&'b IslamicCacheV1<'b>>) -> Self {
371        Self {
372            data,
373            _ib: PhantomData,
374        }
375    }
376    /// Given an ISO date (in both ArithmeticDate and R.D. format), returns the IslamicYearInfo and extended year for that date, loading
377    /// from cache or computing.
378    fn load_or_compute_info_for_iso(&self, fixed: RataDie) -> (IslamicYearInfo, i32, u8, u8) {
379        let cached = self.data.and_then(|d| d.get_for_fixed::<IB>(fixed));
380        if let Some((cached, year)) = cached {
381            let ny = cached.packed_data.ny::<IB>(year);
382            let day_of_year = (fixed - ny) as u16 + 1;
383            debug_assert!(day_of_year < 360);
384            // We divide by 30, not 29, to account for the case where all months before this
385            // were length 30 (possible near the beginning of the year)
386            // We add +1 because months are 1-indexed
387            let possible_month = u8::try_from(1 + (day_of_year / 30)).unwrap_or(1);
388            let (m, d) = compute_month_day(cached, possible_month, day_of_year);
389            return (cached, year, m, d);
390        };
391        // compute
392
393        let (y, m, d) = IB::islamic_from_fixed(fixed);
394        let info = IslamicYearInfo::compute::<IB>(y);
395        let ny = info.packed_data.ny::<IB>(y);
396        let day_of_year = (fixed - ny) as u16 + 1;
397        // We can't use the m/d from islamic_from_fixed because that code
398        // occasionally throws up 31-day months, which we normalize out. So we instead back-compute, starting with the previous month
399        let (m, d) = if m > 1 {
400            compute_month_day(info, m - 1, day_of_year)
401        } else {
402            (m, d)
403        };
404        (info, y, m, d)
405    }
406}
407
408/// The inner date type used for representing [`Date`]s of [`IslamicObservational`]. See [`Date`] and [`IslamicObservational`] for more details.
409
410#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq, PartialOrd, Ord)]
411pub struct IslamicDateInner(ArithmeticDate<IslamicObservational>);
412
413impl CalendarArithmetic for IslamicObservational {
414    type YearInfo = IslamicYearInfo;
415
416    fn month_days(_year: i32, month: u8, year_info: IslamicYearInfo) -> u8 {
417        year_info.packed_data.days_in_month(month)
418    }
419
420    fn months_for_every_year(_year: i32, _year_info: IslamicYearInfo) -> u8 {
421        12
422    }
423
424    fn days_in_provided_year(_year: i32, year_info: IslamicYearInfo) -> u16 {
425        year_info.packed_data.days_in_year()
426    }
427
428    // As an true lunar calendar, it does not have leap years.
429    fn is_leap_year(_year: i32, year_info: IslamicYearInfo) -> bool {
430        year_info.packed_data.days_in_year() != IslamicYearInfo::SHORT_YEAR_LEN
431    }
432
433    fn last_month_day_in_year(year: i32, year_info: IslamicYearInfo) -> (u8, u8) {
434        let days = Self::month_days(year, 12, year_info);
435
436        (12, days)
437    }
438}
439
440impl Calendar for IslamicObservational {
441    type DateInner = IslamicDateInner;
442    fn date_from_codes(
443        &self,
444        era: types::Era,
445        year: i32,
446        month_code: types::MonthCode,
447        day: u8,
448    ) -> Result<Self::DateInner, CalendarError> {
449        let year = if era.0 == tinystr!(16, "islamic") || era.0 == tinystr!(16, "ah") {
450            year
451        } else {
452            return Err(CalendarError::UnknownEra(era.0, self.debug_name()));
453        };
454        let month = if let Some((ordinal, false)) = month_code.parsed() {
455            ordinal
456        } else {
457            return Err(CalendarError::UnknownMonthCode(
458                month_code.0,
459                self.debug_name(),
460            ));
461        };
462        ArithmeticDate::new_from_ordinals_with_info(
463            year,
464            month,
465            day,
466            self.precomputed_data().load_or_compute_info(year),
467        )
468        .map(IslamicDateInner)
469    }
470
471    fn date_from_iso(&self, iso: Date<crate::Iso>) -> Self::DateInner {
472        let fixed_iso = Iso::fixed_from_iso(*iso.inner());
473
474        let (year_info, y, m, d) = self
475            .precomputed_data()
476            .load_or_compute_info_for_iso(fixed_iso);
477        IslamicDateInner(ArithmeticDate::new_unchecked_with_info(y, m, d, year_info))
478    }
479
480    fn date_to_iso(&self, date: &Self::DateInner) -> Date<crate::Iso> {
481        let fixed = date.0.year_info.rd_for::<ObservationalIslamicMarker>(
482            date.0.year,
483            date.0.month,
484            date.0.day,
485        );
486        Iso::iso_from_fixed(fixed)
487    }
488
489    fn months_in_year(&self, date: &Self::DateInner) -> u8 {
490        date.0.months_in_year()
491    }
492
493    fn days_in_year(&self, date: &Self::DateInner) -> u16 {
494        date.0.days_in_year()
495    }
496
497    fn days_in_month(&self, date: &Self::DateInner) -> u8 {
498        date.0.days_in_month()
499    }
500
501    fn day_of_week(&self, date: &Self::DateInner) -> types::IsoWeekday {
502        Iso.day_of_week(self.date_to_iso(date).inner())
503    }
504
505    fn offset_date(&self, date: &mut Self::DateInner, offset: DateDuration<Self>) {
506        date.0.offset_date(offset, &self.precomputed_data())
507    }
508
509    fn until(
510        &self,
511        date1: &Self::DateInner,
512        date2: &Self::DateInner,
513        _calendar2: &Self,
514        _largest_unit: DateDurationUnit,
515        _smallest_unit: DateDurationUnit,
516    ) -> DateDuration<Self> {
517        date1.0.until(date2.0, _largest_unit, _smallest_unit)
518    }
519
520    fn debug_name(&self) -> &'static str {
521        Self::DEBUG_NAME
522    }
523
524    fn year(&self, date: &Self::DateInner) -> types::FormattableYear {
525        Self::year_as_islamic(date.0.year)
526    }
527
528    fn is_in_leap_year(&self, date: &Self::DateInner) -> bool {
529        Self::is_leap_year(date.0.year, date.0.year_info)
530    }
531
532    fn month(&self, date: &Self::DateInner) -> types::FormattableMonth {
533        date.0.month()
534    }
535
536    fn day_of_month(&self, date: &Self::DateInner) -> types::DayOfMonth {
537        date.0.day_of_month()
538    }
539
540    fn day_of_year_info(&self, date: &Self::DateInner) -> types::DayOfYearInfo {
541        let prev_year = date.0.year.saturating_sub(1);
542        let next_year = date.0.year.saturating_add(1);
543        types::DayOfYearInfo {
544            day_of_year: date.0.day_of_year(),
545            days_in_year: date.0.days_in_year(),
546            prev_year: Self::year_as_islamic(prev_year),
547            days_in_prev_year: date.0.year_info.days_in_prev_year(),
548            next_year: Self::year_as_islamic(next_year),
549        }
550    }
551
552    fn any_calendar_kind(&self) -> Option<AnyCalendarKind> {
553        Some(AnyCalendarKind::IslamicObservational)
554    }
555}
556
557impl IslamicObservational {
558    fn precomputed_data(&self) -> IslamicPrecomputedData<ObservationalIslamicMarker> {
559        IslamicPrecomputedData::new(self.data.as_ref().map(|x| x.get()))
560    }
561
562    fn year_as_islamic(year: i32) -> types::FormattableYear {
563        types::FormattableYear {
564            era: types::Era(tinystr!(16, "islamic")),
565            number: year,
566            cyclic: None,
567            related_iso: None,
568        }
569    }
570    pub(crate) const DEBUG_NAME: &'static str = "Islamic (observational)";
571}
572
573impl<A: AsCalendar<Calendar = IslamicObservational>> Date<A> {
574    /// Construct new Islamic Observational Date.
575    ///
576    /// Has no negative years, only era is the AH.
577    ///
578    /// ```rust
579    /// use icu::calendar::islamic::IslamicObservational;
580    /// use icu::calendar::Date;
581    ///
582    /// let islamic = IslamicObservational::new_always_calculating();
583    ///
584    /// let date_islamic =
585    ///     Date::try_new_observational_islamic_date(1392, 4, 25, islamic)
586    ///         .expect("Failed to initialize Islamic Date instance.");
587    ///
588    /// assert_eq!(date_islamic.year().number, 1392);
589    /// assert_eq!(date_islamic.month().ordinal, 4);
590    /// assert_eq!(date_islamic.day_of_month().0, 25);
591    /// ```
592    pub fn try_new_observational_islamic_date(
593        year: i32,
594        month: u8,
595        day: u8,
596        calendar: A,
597    ) -> Result<Date<A>, CalendarError> {
598        let year_info = calendar
599            .as_calendar()
600            .precomputed_data()
601            .load_or_compute_info(year);
602        ArithmeticDate::new_from_ordinals_with_info(year, month, day, year_info)
603            .map(IslamicDateInner)
604            .map(|inner| Date::from_raw(inner, calendar))
605    }
606}
607
608impl<A: AsCalendar<Calendar = IslamicObservational>> DateTime<A> {
609    /// Construct a new Islamic Observational datetime from integers.
610    ///
611    /// ```rust
612    /// use icu::calendar::islamic::IslamicObservational;
613    /// use icu::calendar::DateTime;
614    ///
615    /// let islamic = IslamicObservational::new_always_calculating();
616    ///
617    /// let datetime_islamic = DateTime::try_new_observational_islamic_datetime(
618    ///     474, 10, 11, 13, 1, 0, islamic,
619    /// )
620    /// .expect("Failed to initialize Islamic DateTime instance.");
621    ///
622    /// assert_eq!(datetime_islamic.date.year().number, 474);
623    /// assert_eq!(datetime_islamic.date.month().ordinal, 10);
624    /// assert_eq!(datetime_islamic.date.day_of_month().0, 11);
625    /// assert_eq!(datetime_islamic.time.hour.number(), 13);
626    /// assert_eq!(datetime_islamic.time.minute.number(), 1);
627    /// assert_eq!(datetime_islamic.time.second.number(), 0);
628    /// ```
629    pub fn try_new_observational_islamic_datetime(
630        year: i32,
631        month: u8,
632        day: u8,
633        hour: u8,
634        minute: u8,
635        second: u8,
636        calendar: A,
637    ) -> Result<DateTime<A>, CalendarError> {
638        Ok(DateTime {
639            date: Date::try_new_observational_islamic_date(year, month, day, calendar)?,
640            time: Time::try_new(hour, minute, second, 0)?,
641        })
642    }
643}
644
645#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq, PartialOrd, Ord)]
646/// The inner date type used for representing [`Date`]s of [`IslamicUmmAlQura`]. See [`Date`] and [`IslamicUmmAlQura`] for more details.
647pub struct IslamicUmmAlQuraDateInner(ArithmeticDate<IslamicUmmAlQura>);
648
649impl CalendarArithmetic for IslamicUmmAlQura {
650    type YearInfo = IslamicYearInfo;
651
652    fn month_days(_year: i32, month: u8, year_info: IslamicYearInfo) -> u8 {
653        year_info.packed_data.days_in_month(month)
654    }
655
656    fn months_for_every_year(_year: i32, _year_info: IslamicYearInfo) -> u8 {
657        12
658    }
659
660    fn days_in_provided_year(_year: i32, year_info: IslamicYearInfo) -> u16 {
661        year_info.packed_data.days_in_year()
662    }
663
664    // As an true lunar calendar, it does not have leap years.
665    fn is_leap_year(_year: i32, year_info: IslamicYearInfo) -> bool {
666        year_info.packed_data.days_in_year() != IslamicYearInfo::SHORT_YEAR_LEN
667    }
668
669    fn last_month_day_in_year(year: i32, year_info: IslamicYearInfo) -> (u8, u8) {
670        let days = Self::month_days(year, 12, year_info);
671
672        (12, days)
673    }
674}
675
676impl Calendar for IslamicUmmAlQura {
677    type DateInner = IslamicUmmAlQuraDateInner;
678    fn date_from_codes(
679        &self,
680        era: types::Era,
681        year: i32,
682        month_code: types::MonthCode,
683        day: u8,
684    ) -> Result<Self::DateInner, CalendarError> {
685        let year = if era.0 == tinystr!(16, "islamic-umalqura")
686            || era.0 == tinystr!(16, "islamic")
687            || era.0 == tinystr!(16, "ah")
688        {
689            year
690        } else {
691            return Err(CalendarError::UnknownEra(era.0, self.debug_name()));
692        };
693
694        let month = if let Some((ordinal, false)) = month_code.parsed() {
695            ordinal
696        } else {
697            return Err(CalendarError::UnknownMonthCode(
698                month_code.0,
699                self.debug_name(),
700            ));
701        };
702        ArithmeticDate::new_from_ordinals_with_info(
703            year,
704            month,
705            day,
706            self.precomputed_data().load_or_compute_info(year),
707        )
708        .map(IslamicUmmAlQuraDateInner)
709    }
710
711    fn date_from_iso(&self, iso: Date<Iso>) -> Self::DateInner {
712        let fixed_iso = Iso::fixed_from_iso(*iso.inner());
713
714        let (year_info, y, m, d) = self
715            .precomputed_data()
716            .load_or_compute_info_for_iso(fixed_iso);
717        IslamicUmmAlQuraDateInner(ArithmeticDate::new_unchecked_with_info(y, m, d, year_info))
718    }
719
720    fn date_to_iso(&self, date: &Self::DateInner) -> Date<Iso> {
721        let fixed =
722            date.0
723                .year_info
724                .rd_for::<SaudiIslamicMarker>(date.0.year, date.0.month, date.0.day);
725        Iso::iso_from_fixed(fixed)
726    }
727
728    fn months_in_year(&self, date: &Self::DateInner) -> u8 {
729        date.0.months_in_year()
730    }
731
732    fn days_in_year(&self, date: &Self::DateInner) -> u16 {
733        date.0.days_in_year()
734    }
735
736    fn days_in_month(&self, date: &Self::DateInner) -> u8 {
737        date.0.days_in_month()
738    }
739
740    fn offset_date(&self, date: &mut Self::DateInner, offset: DateDuration<Self>) {
741        date.0.offset_date(offset, &self.precomputed_data())
742    }
743
744    fn until(
745        &self,
746        date1: &Self::DateInner,
747        date2: &Self::DateInner,
748        _calendar2: &Self,
749        _largest_unit: DateDurationUnit,
750        _smallest_unit: DateDurationUnit,
751    ) -> DateDuration<Self> {
752        date1.0.until(date2.0, _largest_unit, _smallest_unit)
753    }
754
755    fn debug_name(&self) -> &'static str {
756        Self::DEBUG_NAME
757    }
758
759    fn year(&self, date: &Self::DateInner) -> types::FormattableYear {
760        Self::year_as_islamic(date.0.year)
761    }
762
763    fn is_in_leap_year(&self, date: &Self::DateInner) -> bool {
764        Self::is_leap_year(date.0.year, date.0.year_info)
765    }
766
767    fn month(&self, date: &Self::DateInner) -> types::FormattableMonth {
768        date.0.month()
769    }
770
771    fn day_of_month(&self, date: &Self::DateInner) -> types::DayOfMonth {
772        date.0.day_of_month()
773    }
774
775    fn day_of_year_info(&self, date: &Self::DateInner) -> types::DayOfYearInfo {
776        let prev_year = date.0.year.saturating_sub(1);
777        let next_year = date.0.year.saturating_add(1);
778        types::DayOfYearInfo {
779            day_of_year: date.0.day_of_year(),
780            days_in_year: date.0.days_in_year(),
781            prev_year: Self::year_as_islamic(prev_year),
782            days_in_prev_year: date.0.year_info.days_in_prev_year(),
783            next_year: Self::year_as_islamic(next_year),
784        }
785    }
786
787    fn any_calendar_kind(&self) -> Option<AnyCalendarKind> {
788        Some(AnyCalendarKind::IslamicUmmAlQura)
789    }
790}
791
792impl IslamicUmmAlQura {
793    fn precomputed_data(&self) -> IslamicPrecomputedData<SaudiIslamicMarker> {
794        IslamicPrecomputedData::new(self.data.as_ref().map(|x| x.get()))
795    }
796
797    fn year_as_islamic(year: i32) -> types::FormattableYear {
798        types::FormattableYear {
799            era: types::Era(tinystr!(16, "islamic")),
800            number: year,
801            cyclic: None,
802            related_iso: None,
803        }
804    }
805    pub(crate) const DEBUG_NAME: &'static str = "Islamic (Umm al-Qura)";
806}
807
808impl<A: AsCalendar<Calendar = IslamicUmmAlQura>> Date<A> {
809    /// Construct new Islamic Umm al-Qura Date.
810    ///
811    /// Has no negative years, only era is the AH.
812    ///
813    /// ```rust
814    /// use icu::calendar::islamic::IslamicUmmAlQura;
815    /// use icu::calendar::Date;
816    ///
817    /// let islamic = IslamicUmmAlQura::new_always_calculating();
818    ///
819    /// let date_islamic = Date::try_new_ummalqura_date(1392, 4, 25, islamic)
820    ///     .expect("Failed to initialize Islamic Date instance.");
821    ///
822    /// assert_eq!(date_islamic.year().number, 1392);
823    /// assert_eq!(date_islamic.month().ordinal, 4);
824    /// assert_eq!(date_islamic.day_of_month().0, 25);
825    /// ```
826    pub fn try_new_ummalqura_date(
827        year: i32,
828        month: u8,
829        day: u8,
830        calendar: A,
831    ) -> Result<Date<A>, CalendarError> {
832        let year_info = calendar
833            .as_calendar()
834            .precomputed_data()
835            .load_or_compute_info(year);
836        ArithmeticDate::new_from_ordinals_with_info(year, month, day, year_info)
837            .map(IslamicUmmAlQuraDateInner)
838            .map(|inner| Date::from_raw(inner, calendar))
839    }
840}
841
842impl<A: AsCalendar<Calendar = IslamicUmmAlQura>> DateTime<A> {
843    /// Construct a new Islamic Umm al-Qura datetime from integers.
844    ///
845    /// ```rust
846    /// use icu::calendar::islamic::IslamicUmmAlQura;
847    /// use icu::calendar::DateTime;
848    ///
849    /// let islamic = IslamicUmmAlQura::new_always_calculating();
850    ///
851    /// let datetime_islamic =
852    ///     DateTime::try_new_ummalqura_datetime(474, 10, 11, 13, 1, 0, islamic)
853    ///         .expect("Failed to initialize Islamic DateTime instance.");
854    ///
855    /// assert_eq!(datetime_islamic.date.year().number, 474);
856    /// assert_eq!(datetime_islamic.date.month().ordinal, 10);
857    /// assert_eq!(datetime_islamic.date.day_of_month().0, 11);
858    /// assert_eq!(datetime_islamic.time.hour.number(), 13);
859    /// assert_eq!(datetime_islamic.time.minute.number(), 1);
860    /// assert_eq!(datetime_islamic.time.second.number(), 0);
861    /// ```
862    pub fn try_new_ummalqura_datetime(
863        year: i32,
864        month: u8,
865        day: u8,
866        hour: u8,
867        minute: u8,
868        second: u8,
869        calendar: A,
870    ) -> Result<DateTime<A>, CalendarError> {
871        Ok(DateTime {
872            date: Date::try_new_ummalqura_date(year, month, day, calendar)?,
873            time: Time::try_new(hour, minute, second, 0)?,
874        })
875    }
876}
877
878/// The inner date type used for representing [`Date`]s of [`IslamicCivil`]. See [`Date`] and [`IslamicCivil`] for more details.
879
880#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq, PartialOrd, Ord)]
881pub struct IslamicCivilDateInner(ArithmeticDate<IslamicCivil>);
882
883impl CalendarArithmetic for IslamicCivil {
884    type YearInfo = ();
885
886    fn month_days(year: i32, month: u8, _data: ()) -> u8 {
887        match month {
888            1 | 3 | 5 | 7 | 9 | 11 => 30,
889            2 | 4 | 6 | 8 | 10 => 29,
890            12 if Self::is_leap_year(year, ()) => 30,
891            12 => 29,
892            _ => 0,
893        }
894    }
895
896    fn months_for_every_year(_year: i32, _data: ()) -> u8 {
897        12
898    }
899
900    fn days_in_provided_year(year: i32, _data: ()) -> u16 {
901        if Self::is_leap_year(year, ()) {
902            IslamicYearInfo::LONG_YEAR_LEN
903        } else {
904            IslamicYearInfo::SHORT_YEAR_LEN
905        }
906    }
907
908    fn is_leap_year(year: i32, _data: ()) -> bool {
909        (14 + 11 * year).rem_euclid(30) < 11
910    }
911
912    fn last_month_day_in_year(year: i32, _data: ()) -> (u8, u8) {
913        if Self::is_leap_year(year, ()) {
914            (12, 30)
915        } else {
916            (12, 29)
917        }
918    }
919}
920
921impl Calendar for IslamicCivil {
922    type DateInner = IslamicCivilDateInner;
923
924    fn date_from_codes(
925        &self,
926        era: types::Era,
927        year: i32,
928        month_code: types::MonthCode,
929        day: u8,
930    ) -> Result<Self::DateInner, CalendarError> {
931        let year = if era.0 == tinystr!(16, "islamic-civil")
932            || era.0 == tinystr!(16, "islamicc")
933            || era.0 == tinystr!(16, "islamic")
934            || era.0 == tinystr!(16, "ah")
935        {
936            // TODO: Check name and alias
937            year
938        } else {
939            return Err(CalendarError::UnknownEra(era.0, self.debug_name()));
940        };
941
942        ArithmeticDate::new_from_codes(self, year, month_code, day).map(IslamicCivilDateInner)
943    }
944
945    fn date_from_iso(&self, iso: Date<Iso>) -> Self::DateInner {
946        let fixed_iso = Iso::fixed_from_iso(*iso.inner());
947        Self::islamic_from_fixed(fixed_iso).inner
948    }
949
950    fn date_to_iso(&self, date: &Self::DateInner) -> Date<Iso> {
951        let fixed_islamic = Self::fixed_from_islamic(*date);
952        Iso::iso_from_fixed(fixed_islamic)
953    }
954
955    fn months_in_year(&self, date: &Self::DateInner) -> u8 {
956        date.0.months_in_year()
957    }
958
959    fn days_in_year(&self, date: &Self::DateInner) -> u16 {
960        date.0.days_in_year()
961    }
962
963    fn days_in_month(&self, date: &Self::DateInner) -> u8 {
964        date.0.days_in_month()
965    }
966
967    fn day_of_week(&self, date: &Self::DateInner) -> types::IsoWeekday {
968        Iso.day_of_week(self.date_to_iso(date).inner())
969    }
970
971    fn offset_date(&self, date: &mut Self::DateInner, offset: DateDuration<Self>) {
972        date.0.offset_date(offset, &())
973    }
974
975    fn until(
976        &self,
977        date1: &Self::DateInner,
978        date2: &Self::DateInner,
979        _calendar2: &Self,
980        _largest_unit: DateDurationUnit,
981        _smallest_unit: DateDurationUnit,
982    ) -> DateDuration<Self> {
983        date1.0.until(date2.0, _largest_unit, _smallest_unit)
984    }
985
986    fn debug_name(&self) -> &'static str {
987        "Islamic (civil)"
988    }
989
990    fn year(&self, date: &Self::DateInner) -> types::FormattableYear {
991        Self::year_as_islamic(date.0.year)
992    }
993
994    fn is_in_leap_year(&self, date: &Self::DateInner) -> bool {
995        Self::is_leap_year(date.0.year, ())
996    }
997
998    fn month(&self, date: &Self::DateInner) -> types::FormattableMonth {
999        date.0.month()
1000    }
1001
1002    fn day_of_month(&self, date: &Self::DateInner) -> types::DayOfMonth {
1003        date.0.day_of_month()
1004    }
1005
1006    fn day_of_year_info(&self, date: &Self::DateInner) -> types::DayOfYearInfo {
1007        let prev_year = date.0.year.saturating_sub(1);
1008        let next_year = date.0.year.saturating_add(1);
1009        types::DayOfYearInfo {
1010            day_of_year: date.0.day_of_year(),
1011            days_in_year: date.0.days_in_year(),
1012            prev_year: Self::year_as_islamic(prev_year),
1013            days_in_prev_year: Self::days_in_provided_year(prev_year, ()),
1014            next_year: Self::year_as_islamic(next_year),
1015        }
1016    }
1017    fn any_calendar_kind(&self) -> Option<AnyCalendarKind> {
1018        Some(AnyCalendarKind::IslamicCivil)
1019    }
1020}
1021
1022impl IslamicCivil {
1023    fn fixed_from_islamic(i_date: IslamicCivilDateInner) -> RataDie {
1024        calendrical_calculations::islamic::fixed_from_islamic_civil(
1025            i_date.0.year,
1026            i_date.0.month,
1027            i_date.0.day,
1028        )
1029    }
1030
1031    fn islamic_from_fixed(date: RataDie) -> Date<IslamicCivil> {
1032        let (y, m, d) = calendrical_calculations::islamic::islamic_civil_from_fixed(date);
1033
1034        debug_assert!(
1035            Date::try_new_islamic_civil_date_with_calendar(y, m, d, IslamicCivil).is_ok()
1036        );
1037        Date::from_raw(
1038            IslamicCivilDateInner(ArithmeticDate::new_unchecked(y, m, d)),
1039            IslamicCivil,
1040        )
1041    }
1042
1043    fn year_as_islamic(year: i32) -> types::FormattableYear {
1044        types::FormattableYear {
1045            era: types::Era(tinystr!(16, "islamic")),
1046            number: year,
1047            cyclic: None,
1048            related_iso: None,
1049        }
1050    }
1051}
1052
1053impl<A: AsCalendar<Calendar = IslamicCivil>> Date<A> {
1054    /// Construct new Civil Islamic Date.
1055    ///
1056    /// Has no negative years, only era is the AH.
1057    ///
1058    /// ```rust
1059    /// use icu::calendar::islamic::IslamicCivil;
1060    /// use icu::calendar::Date;
1061    ///
1062    /// let islamic = IslamicCivil::new_always_calculating();
1063    ///
1064    /// let date_islamic =
1065    ///     Date::try_new_islamic_civil_date_with_calendar(1392, 4, 25, islamic)
1066    ///         .expect("Failed to initialize Islamic Date instance.");
1067    ///
1068    /// assert_eq!(date_islamic.year().number, 1392);
1069    /// assert_eq!(date_islamic.month().ordinal, 4);
1070    /// assert_eq!(date_islamic.day_of_month().0, 25);
1071    /// ```
1072    pub fn try_new_islamic_civil_date_with_calendar(
1073        year: i32,
1074        month: u8,
1075        day: u8,
1076        calendar: A,
1077    ) -> Result<Date<A>, CalendarError> {
1078        ArithmeticDate::new_from_ordinals(year, month, day)
1079            .map(IslamicCivilDateInner)
1080            .map(|inner| Date::from_raw(inner, calendar))
1081    }
1082}
1083
1084impl<A: AsCalendar<Calendar = IslamicCivil>> DateTime<A> {
1085    /// Construct a new Civil Islamic datetime from integers.
1086    ///
1087    /// ```rust
1088    /// use icu::calendar::islamic::IslamicCivil;
1089    /// use icu::calendar::DateTime;
1090    ///
1091    /// let islamic = IslamicCivil::new_always_calculating();
1092    ///
1093    /// let datetime_islamic =
1094    ///     DateTime::try_new_islamic_civil_datetime_with_calendar(
1095    ///         474, 10, 11, 13, 1, 0, islamic,
1096    ///     )
1097    ///     .expect("Failed to initialize Islamic DateTime instance.");
1098    ///
1099    /// assert_eq!(datetime_islamic.date.year().number, 474);
1100    /// assert_eq!(datetime_islamic.date.month().ordinal, 10);
1101    /// assert_eq!(datetime_islamic.date.day_of_month().0, 11);
1102    /// assert_eq!(datetime_islamic.time.hour.number(), 13);
1103    /// assert_eq!(datetime_islamic.time.minute.number(), 1);
1104    /// assert_eq!(datetime_islamic.time.second.number(), 0);
1105    /// ```
1106    pub fn try_new_islamic_civil_datetime_with_calendar(
1107        year: i32,
1108        month: u8,
1109        day: u8,
1110        hour: u8,
1111        minute: u8,
1112        second: u8,
1113        calendar: A,
1114    ) -> Result<DateTime<A>, CalendarError> {
1115        Ok(DateTime {
1116            date: Date::try_new_islamic_civil_date_with_calendar(year, month, day, calendar)?,
1117            time: Time::try_new(hour, minute, second, 0)?,
1118        })
1119    }
1120}
1121
1122/// The inner date type used for representing [`Date`]s of [`IslamicTabular`]. See [`Date`] and [`IslamicTabular`] for more details.
1123
1124#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq, PartialOrd, Ord)]
1125pub struct IslamicTabularDateInner(ArithmeticDate<IslamicTabular>);
1126
1127impl CalendarArithmetic for IslamicTabular {
1128    type YearInfo = ();
1129
1130    fn month_days(year: i32, month: u8, _data: ()) -> u8 {
1131        match month {
1132            1 | 3 | 5 | 7 | 9 | 11 => 30,
1133            2 | 4 | 6 | 8 | 10 => 29,
1134            12 if Self::is_leap_year(year, ()) => 30,
1135            12 => 29,
1136            _ => 0,
1137        }
1138    }
1139
1140    fn months_for_every_year(_year: i32, _data: ()) -> u8 {
1141        12
1142    }
1143
1144    fn days_in_provided_year(year: i32, _data: ()) -> u16 {
1145        if Self::is_leap_year(year, ()) {
1146            IslamicYearInfo::LONG_YEAR_LEN
1147        } else {
1148            IslamicYearInfo::SHORT_YEAR_LEN
1149        }
1150    }
1151
1152    fn is_leap_year(year: i32, _data: ()) -> bool {
1153        (14 + 11 * year).rem_euclid(30) < 11
1154    }
1155
1156    fn last_month_day_in_year(year: i32, _data: ()) -> (u8, u8) {
1157        if Self::is_leap_year(year, ()) {
1158            (12, 30)
1159        } else {
1160            (12, 29)
1161        }
1162    }
1163}
1164
1165impl Calendar for IslamicTabular {
1166    type DateInner = IslamicTabularDateInner;
1167
1168    fn date_from_codes(
1169        &self,
1170        era: types::Era,
1171        year: i32,
1172        month_code: types::MonthCode,
1173        day: u8,
1174    ) -> Result<Self::DateInner, CalendarError> {
1175        let year = if era.0 == tinystr!(16, "islamic-tbla")
1176            || era.0 == tinystr!(16, "islamic")
1177            || era.0 == tinystr!(16, "ah")
1178        {
1179            year
1180        } else {
1181            return Err(CalendarError::UnknownEra(era.0, self.debug_name()));
1182        };
1183
1184        ArithmeticDate::new_from_codes(self, year, month_code, day).map(IslamicTabularDateInner)
1185    }
1186
1187    fn date_from_iso(&self, iso: Date<Iso>) -> Self::DateInner {
1188        let fixed_iso = Iso::fixed_from_iso(*iso.inner());
1189        Self::islamic_from_fixed(fixed_iso).inner
1190    }
1191
1192    fn date_to_iso(&self, date: &Self::DateInner) -> Date<Iso> {
1193        let fixed_islamic = Self::fixed_from_islamic(*date);
1194        Iso::iso_from_fixed(fixed_islamic)
1195    }
1196
1197    fn months_in_year(&self, date: &Self::DateInner) -> u8 {
1198        date.0.months_in_year()
1199    }
1200
1201    fn days_in_year(&self, date: &Self::DateInner) -> u16 {
1202        date.0.days_in_year()
1203    }
1204
1205    fn days_in_month(&self, date: &Self::DateInner) -> u8 {
1206        date.0.days_in_month()
1207    }
1208
1209    fn day_of_week(&self, date: &Self::DateInner) -> types::IsoWeekday {
1210        Iso.day_of_week(self.date_to_iso(date).inner())
1211    }
1212
1213    fn offset_date(&self, date: &mut Self::DateInner, offset: DateDuration<Self>) {
1214        date.0.offset_date(offset, &())
1215    }
1216
1217    fn until(
1218        &self,
1219        date1: &Self::DateInner,
1220        date2: &Self::DateInner,
1221        _calendar2: &Self,
1222        _largest_unit: DateDurationUnit,
1223        _smallest_unit: DateDurationUnit,
1224    ) -> DateDuration<Self> {
1225        date1.0.until(date2.0, _largest_unit, _smallest_unit)
1226    }
1227
1228    fn debug_name(&self) -> &'static str {
1229        "Islamic (tabular)"
1230    }
1231
1232    fn year(&self, date: &Self::DateInner) -> types::FormattableYear {
1233        Self::year_as_islamic(date.0.year)
1234    }
1235
1236    fn is_in_leap_year(&self, date: &Self::DateInner) -> bool {
1237        Self::is_leap_year(date.0.year, ())
1238    }
1239
1240    fn month(&self, date: &Self::DateInner) -> types::FormattableMonth {
1241        date.0.month()
1242    }
1243
1244    fn day_of_month(&self, date: &Self::DateInner) -> types::DayOfMonth {
1245        date.0.day_of_month()
1246    }
1247
1248    fn day_of_year_info(&self, date: &Self::DateInner) -> types::DayOfYearInfo {
1249        let prev_year = date.0.year.saturating_sub(1);
1250        let next_year = date.0.year.saturating_add(1);
1251        types::DayOfYearInfo {
1252            day_of_year: date.0.day_of_year(),
1253            days_in_year: date.0.days_in_year(),
1254            prev_year: Self::year_as_islamic(prev_year),
1255            days_in_prev_year: Self::days_in_provided_year(prev_year, ()),
1256            next_year: Self::year_as_islamic(next_year),
1257        }
1258    }
1259    fn any_calendar_kind(&self) -> Option<AnyCalendarKind> {
1260        Some(AnyCalendarKind::IslamicTabular)
1261    }
1262}
1263
1264impl IslamicTabular {
1265    fn fixed_from_islamic(i_date: IslamicTabularDateInner) -> RataDie {
1266        calendrical_calculations::islamic::fixed_from_islamic_tabular(
1267            i_date.0.year,
1268            i_date.0.month,
1269            i_date.0.day,
1270        )
1271    }
1272
1273    fn islamic_from_fixed(date: RataDie) -> Date<IslamicTabular> {
1274        let (y, m, d) = calendrical_calculations::islamic::islamic_tabular_from_fixed(date);
1275
1276        debug_assert!(
1277            Date::try_new_islamic_civil_date_with_calendar(y, m, d, IslamicCivil).is_ok()
1278        );
1279        Date::from_raw(
1280            IslamicTabularDateInner(ArithmeticDate::new_unchecked(y, m, d)),
1281            IslamicTabular,
1282        )
1283    }
1284
1285    fn year_as_islamic(year: i32) -> types::FormattableYear {
1286        types::FormattableYear {
1287            era: types::Era(tinystr!(16, "islamic")),
1288            number: year,
1289            cyclic: None,
1290            related_iso: None,
1291        }
1292    }
1293}
1294
1295impl<A: AsCalendar<Calendar = IslamicTabular>> Date<A> {
1296    /// Construct new Tabular Islamic Date.
1297    ///
1298    /// Has no negative years, only era is the AH.
1299    ///
1300    /// ```rust
1301    /// use icu::calendar::islamic::IslamicTabular;
1302    /// use icu::calendar::Date;
1303    ///
1304    /// let islamic = IslamicTabular::new_always_calculating();
1305    ///
1306    /// let date_islamic =
1307    ///     Date::try_new_islamic_tabular_date_with_calendar(1392, 4, 25, islamic)
1308    ///         .expect("Failed to initialize Islamic Date instance.");
1309    ///
1310    /// assert_eq!(date_islamic.year().number, 1392);
1311    /// assert_eq!(date_islamic.month().ordinal, 4);
1312    /// assert_eq!(date_islamic.day_of_month().0, 25);
1313    /// ```
1314    pub fn try_new_islamic_tabular_date_with_calendar(
1315        year: i32,
1316        month: u8,
1317        day: u8,
1318        calendar: A,
1319    ) -> Result<Date<A>, CalendarError> {
1320        ArithmeticDate::new_from_ordinals(year, month, day)
1321            .map(IslamicTabularDateInner)
1322            .map(|inner| Date::from_raw(inner, calendar))
1323    }
1324}
1325
1326impl<A: AsCalendar<Calendar = IslamicTabular>> DateTime<A> {
1327    /// Construct a new Tabular Islamic datetime from integers.
1328    ///
1329    /// ```rust
1330    /// use icu::calendar::islamic::IslamicTabular;
1331    /// use icu::calendar::DateTime;
1332    ///
1333    /// let islamic = IslamicTabular::new_always_calculating();
1334    ///
1335    /// let datetime_islamic =
1336    ///     DateTime::try_new_islamic_tabular_datetime_with_calendar(
1337    ///         474, 10, 11, 13, 1, 0, islamic,
1338    ///     )
1339    ///     .expect("Failed to initialize Islamic DateTime instance.");
1340    ///
1341    /// assert_eq!(datetime_islamic.date.year().number, 474);
1342    /// assert_eq!(datetime_islamic.date.month().ordinal, 10);
1343    /// assert_eq!(datetime_islamic.date.day_of_month().0, 11);
1344    /// assert_eq!(datetime_islamic.time.hour.number(), 13);
1345    /// assert_eq!(datetime_islamic.time.minute.number(), 1);
1346    /// assert_eq!(datetime_islamic.time.second.number(), 0);
1347    /// ```
1348    pub fn try_new_islamic_tabular_datetime_with_calendar(
1349        year: i32,
1350        month: u8,
1351        day: u8,
1352        hour: u8,
1353        minute: u8,
1354        second: u8,
1355        calendar: A,
1356    ) -> Result<DateTime<A>, CalendarError> {
1357        Ok(DateTime {
1358            date: Date::try_new_islamic_tabular_date_with_calendar(year, month, day, calendar)?,
1359            time: Time::try_new(hour, minute, second, 0)?,
1360        })
1361    }
1362}
1363
1364#[cfg(test)]
1365mod test {
1366    use super::*;
1367    use crate::Ref;
1368
1369    const START_YEAR: i32 = -1245;
1370    const END_YEAR: i32 = 1518;
1371
1372    #[derive(Debug)]
1373    struct DateCase {
1374        year: i32,
1375        month: u8,
1376        day: u8,
1377    }
1378
1379    static TEST_FIXED_DATE: [i64; 33] = [
1380        -214193, -61387, 25469, 49217, 171307, 210155, 253427, 369740, 400085, 434355, 452605,
1381        470160, 473837, 507850, 524156, 544676, 567118, 569477, 601716, 613424, 626596, 645554,
1382        664224, 671401, 694799, 704424, 708842, 709409, 709580, 727274, 728714, 744313, 764652,
1383    ];
1384    // Removed: 601716 and 727274 fixed dates
1385    static TEST_FIXED_DATE_UMMALQURA: [i64; 31] = [
1386        -214193, -61387, 25469, 49217, 171307, 210155, 253427, 369740, 400085, 434355, 452605,
1387        470160, 473837, 507850, 524156, 544676, 567118, 569477, 613424, 626596, 645554, 664224,
1388        671401, 694799, 704424, 708842, 709409, 709580, 728714, 744313, 764652,
1389    ];
1390
1391    static UMMALQURA_DATE_EXPECTED: [DateCase; 31] = [
1392        DateCase {
1393            year: -1245,
1394            month: 12,
1395            day: 11,
1396        },
1397        DateCase {
1398            year: -813,
1399            month: 2,
1400            day: 26,
1401        },
1402        DateCase {
1403            year: -568,
1404            month: 4,
1405            day: 3,
1406        },
1407        DateCase {
1408            year: -501,
1409            month: 4,
1410            day: 8,
1411        },
1412        DateCase {
1413            year: -157,
1414            month: 10,
1415            day: 18,
1416        },
1417        DateCase {
1418            year: -47,
1419            month: 6,
1420            day: 4,
1421        },
1422        DateCase {
1423            year: 75,
1424            month: 7,
1425            day: 14,
1426        },
1427        DateCase {
1428            year: 403,
1429            month: 10,
1430            day: 6,
1431        },
1432        DateCase {
1433            year: 489,
1434            month: 5,
1435            day: 23,
1436        },
1437        DateCase {
1438            year: 586,
1439            month: 2,
1440            day: 8,
1441        },
1442        DateCase {
1443            year: 637,
1444            month: 8,
1445            day: 8,
1446        },
1447        DateCase {
1448            year: 687,
1449            month: 2,
1450            day: 22,
1451        },
1452        DateCase {
1453            year: 697,
1454            month: 7,
1455            day: 8,
1456        },
1457        DateCase {
1458            year: 793,
1459            month: 7,
1460            day: 1,
1461        },
1462        DateCase {
1463            year: 839,
1464            month: 7,
1465            day: 7,
1466        },
1467        DateCase {
1468            year: 897,
1469            month: 6,
1470            day: 3,
1471        },
1472        DateCase {
1473            year: 960,
1474            month: 10,
1475            day: 1,
1476        },
1477        DateCase {
1478            year: 967,
1479            month: 5,
1480            day: 28,
1481        },
1482        DateCase {
1483            year: 1091,
1484            month: 6,
1485            day: 4,
1486        },
1487        DateCase {
1488            year: 1128,
1489            month: 8,
1490            day: 5,
1491        },
1492        DateCase {
1493            year: 1182,
1494            month: 2,
1495            day: 4,
1496        },
1497        DateCase {
1498            year: 1234,
1499            month: 10,
1500            day: 11,
1501        },
1502        DateCase {
1503            year: 1255,
1504            month: 1,
1505            day: 11,
1506        },
1507        DateCase {
1508            year: 1321,
1509            month: 1,
1510            day: 21,
1511        },
1512        DateCase {
1513            year: 1348,
1514            month: 3,
1515            day: 20,
1516        },
1517        DateCase {
1518            year: 1360,
1519            month: 9,
1520            day: 8,
1521        },
1522        DateCase {
1523            year: 1362,
1524            month: 4,
1525            day: 14,
1526        },
1527        DateCase {
1528            year: 1362,
1529            month: 10,
1530            day: 8,
1531        },
1532        DateCase {
1533            year: 1416,
1534            month: 10,
1535            day: 6,
1536        },
1537        DateCase {
1538            year: 1460,
1539            month: 10,
1540            day: 13,
1541        },
1542        DateCase {
1543            year: 1518,
1544            month: 3,
1545            day: 6,
1546        },
1547    ];
1548
1549    static OBSERVATIONAL_CASES: [DateCase; 33] = [
1550        DateCase {
1551            year: -1245,
1552            month: 12,
1553            day: 11,
1554        },
1555        DateCase {
1556            year: -813,
1557            month: 2,
1558            day: 25,
1559        },
1560        DateCase {
1561            year: -568,
1562            month: 4,
1563            day: 2,
1564        },
1565        DateCase {
1566            year: -501,
1567            month: 4,
1568            day: 7,
1569        },
1570        DateCase {
1571            year: -157,
1572            month: 10,
1573            day: 18,
1574        },
1575        DateCase {
1576            year: -47,
1577            month: 6,
1578            day: 3,
1579        },
1580        DateCase {
1581            year: 75,
1582            month: 7,
1583            day: 13,
1584        },
1585        DateCase {
1586            year: 403,
1587            month: 10,
1588            day: 5,
1589        },
1590        DateCase {
1591            year: 489,
1592            month: 5,
1593            day: 22,
1594        },
1595        DateCase {
1596            year: 586,
1597            month: 2,
1598            day: 7,
1599        },
1600        DateCase {
1601            year: 637,
1602            month: 8,
1603            day: 7,
1604        },
1605        DateCase {
1606            year: 687,
1607            month: 2,
1608            day: 21,
1609        },
1610        DateCase {
1611            year: 697,
1612            month: 7,
1613            day: 7,
1614        },
1615        DateCase {
1616            year: 793,
1617            month: 6,
1618            day: 30,
1619        },
1620        DateCase {
1621            year: 839,
1622            month: 7,
1623            day: 6,
1624        },
1625        DateCase {
1626            year: 897,
1627            month: 6,
1628            day: 2,
1629        },
1630        DateCase {
1631            year: 960,
1632            month: 9,
1633            day: 30,
1634        },
1635        DateCase {
1636            year: 967,
1637            month: 5,
1638            day: 27,
1639        },
1640        DateCase {
1641            year: 1058,
1642            month: 5,
1643            day: 18,
1644        },
1645        DateCase {
1646            year: 1091,
1647            month: 6,
1648            day: 3,
1649        },
1650        DateCase {
1651            year: 1128,
1652            month: 8,
1653            day: 4,
1654        },
1655        DateCase {
1656            year: 1182,
1657            month: 2,
1658            day: 4,
1659        },
1660        DateCase {
1661            year: 1234,
1662            month: 10,
1663            day: 10,
1664        },
1665        DateCase {
1666            year: 1255,
1667            month: 1,
1668            day: 11,
1669        },
1670        DateCase {
1671            year: 1321,
1672            month: 1,
1673            day: 20,
1674        },
1675        DateCase {
1676            year: 1348,
1677            month: 3,
1678            day: 19,
1679        },
1680        DateCase {
1681            year: 1360,
1682            month: 9,
1683            day: 7,
1684        },
1685        DateCase {
1686            year: 1362,
1687            month: 4,
1688            day: 14,
1689        },
1690        DateCase {
1691            year: 1362,
1692            month: 10,
1693            day: 7,
1694        },
1695        DateCase {
1696            year: 1412,
1697            month: 9,
1698            day: 12,
1699        },
1700        DateCase {
1701            year: 1416,
1702            month: 10,
1703            day: 5,
1704        },
1705        DateCase {
1706            year: 1460,
1707            month: 10,
1708            day: 12,
1709        },
1710        DateCase {
1711            year: 1518,
1712            month: 3,
1713            day: 5,
1714        },
1715    ];
1716
1717    static ARITHMETIC_CASES: [DateCase; 33] = [
1718        DateCase {
1719            year: -1245,
1720            month: 12,
1721            day: 9,
1722        },
1723        DateCase {
1724            year: -813,
1725            month: 2,
1726            day: 23,
1727        },
1728        DateCase {
1729            year: -568,
1730            month: 4,
1731            day: 1,
1732        },
1733        DateCase {
1734            year: -501,
1735            month: 4,
1736            day: 6,
1737        },
1738        DateCase {
1739            year: -157,
1740            month: 10,
1741            day: 17,
1742        },
1743        DateCase {
1744            year: -47,
1745            month: 6,
1746            day: 3,
1747        },
1748        DateCase {
1749            year: 75,
1750            month: 7,
1751            day: 13,
1752        },
1753        DateCase {
1754            year: 403,
1755            month: 10,
1756            day: 5,
1757        },
1758        DateCase {
1759            year: 489,
1760            month: 5,
1761            day: 22,
1762        },
1763        DateCase {
1764            year: 586,
1765            month: 2,
1766            day: 7,
1767        },
1768        DateCase {
1769            year: 637,
1770            month: 8,
1771            day: 7,
1772        },
1773        DateCase {
1774            year: 687,
1775            month: 2,
1776            day: 20,
1777        },
1778        DateCase {
1779            year: 697,
1780            month: 7,
1781            day: 7,
1782        },
1783        DateCase {
1784            year: 793,
1785            month: 7,
1786            day: 1,
1787        },
1788        DateCase {
1789            year: 839,
1790            month: 7,
1791            day: 6,
1792        },
1793        DateCase {
1794            year: 897,
1795            month: 6,
1796            day: 1,
1797        },
1798        DateCase {
1799            year: 960,
1800            month: 9,
1801            day: 30,
1802        },
1803        DateCase {
1804            year: 967,
1805            month: 5,
1806            day: 27,
1807        },
1808        DateCase {
1809            year: 1058,
1810            month: 5,
1811            day: 18,
1812        },
1813        DateCase {
1814            year: 1091,
1815            month: 6,
1816            day: 2,
1817        },
1818        DateCase {
1819            year: 1128,
1820            month: 8,
1821            day: 4,
1822        },
1823        DateCase {
1824            year: 1182,
1825            month: 2,
1826            day: 3,
1827        },
1828        DateCase {
1829            year: 1234,
1830            month: 10,
1831            day: 10,
1832        },
1833        DateCase {
1834            year: 1255,
1835            month: 1,
1836            day: 11,
1837        },
1838        DateCase {
1839            year: 1321,
1840            month: 1,
1841            day: 21,
1842        },
1843        DateCase {
1844            year: 1348,
1845            month: 3,
1846            day: 19,
1847        },
1848        DateCase {
1849            year: 1360,
1850            month: 9,
1851            day: 8,
1852        },
1853        DateCase {
1854            year: 1362,
1855            month: 4,
1856            day: 13,
1857        },
1858        DateCase {
1859            year: 1362,
1860            month: 10,
1861            day: 7,
1862        },
1863        DateCase {
1864            year: 1412,
1865            month: 9,
1866            day: 13,
1867        },
1868        DateCase {
1869            year: 1416,
1870            month: 10,
1871            day: 5,
1872        },
1873        DateCase {
1874            year: 1460,
1875            month: 10,
1876            day: 12,
1877        },
1878        DateCase {
1879            year: 1518,
1880            month: 3,
1881            day: 5,
1882        },
1883    ];
1884
1885    static TABULAR_CASES: [DateCase; 33] = [
1886        DateCase {
1887            year: -1245,
1888            month: 12,
1889            day: 10,
1890        },
1891        DateCase {
1892            year: -813,
1893            month: 2,
1894            day: 24,
1895        },
1896        DateCase {
1897            year: -568,
1898            month: 4,
1899            day: 2,
1900        },
1901        DateCase {
1902            year: -501,
1903            month: 4,
1904            day: 7,
1905        },
1906        DateCase {
1907            year: -157,
1908            month: 10,
1909            day: 18,
1910        },
1911        DateCase {
1912            year: -47,
1913            month: 6,
1914            day: 4,
1915        },
1916        DateCase {
1917            year: 75,
1918            month: 7,
1919            day: 14,
1920        },
1921        DateCase {
1922            year: 403,
1923            month: 10,
1924            day: 6,
1925        },
1926        DateCase {
1927            year: 489,
1928            month: 5,
1929            day: 23,
1930        },
1931        DateCase {
1932            year: 586,
1933            month: 2,
1934            day: 8,
1935        },
1936        DateCase {
1937            year: 637,
1938            month: 8,
1939            day: 8,
1940        },
1941        DateCase {
1942            year: 687,
1943            month: 2,
1944            day: 21,
1945        },
1946        DateCase {
1947            year: 697,
1948            month: 7,
1949            day: 8,
1950        },
1951        DateCase {
1952            year: 793,
1953            month: 7,
1954            day: 2,
1955        },
1956        DateCase {
1957            year: 839,
1958            month: 7,
1959            day: 7,
1960        },
1961        DateCase {
1962            year: 897,
1963            month: 6,
1964            day: 2,
1965        },
1966        DateCase {
1967            year: 960,
1968            month: 10,
1969            day: 1,
1970        },
1971        DateCase {
1972            year: 967,
1973            month: 5,
1974            day: 28,
1975        },
1976        DateCase {
1977            year: 1058,
1978            month: 5,
1979            day: 19,
1980        },
1981        DateCase {
1982            year: 1091,
1983            month: 6,
1984            day: 3,
1985        },
1986        DateCase {
1987            year: 1128,
1988            month: 8,
1989            day: 5,
1990        },
1991        DateCase {
1992            year: 1182,
1993            month: 2,
1994            day: 4,
1995        },
1996        DateCase {
1997            year: 1234,
1998            month: 10,
1999            day: 11,
2000        },
2001        DateCase {
2002            year: 1255,
2003            month: 1,
2004            day: 12,
2005        },
2006        DateCase {
2007            year: 1321,
2008            month: 1,
2009            day: 22,
2010        },
2011        DateCase {
2012            year: 1348,
2013            month: 3,
2014            day: 20,
2015        },
2016        DateCase {
2017            year: 1360,
2018            month: 9,
2019            day: 9,
2020        },
2021        DateCase {
2022            year: 1362,
2023            month: 4,
2024            day: 14,
2025        },
2026        DateCase {
2027            year: 1362,
2028            month: 10,
2029            day: 8,
2030        },
2031        DateCase {
2032            year: 1412,
2033            month: 9,
2034            day: 14,
2035        },
2036        DateCase {
2037            year: 1416,
2038            month: 10,
2039            day: 6,
2040        },
2041        DateCase {
2042            year: 1460,
2043            month: 10,
2044            day: 13,
2045        },
2046        DateCase {
2047            year: 1518,
2048            month: 3,
2049            day: 6,
2050        },
2051    ];
2052
2053    #[test]
2054    fn test_observational_islamic_from_fixed() {
2055        let calendar = IslamicObservational::new();
2056        let calendar = Ref(&calendar);
2057        for (case, f_date) in OBSERVATIONAL_CASES.iter().zip(TEST_FIXED_DATE.iter()) {
2058            let date =
2059                Date::try_new_observational_islamic_date(case.year, case.month, case.day, calendar)
2060                    .unwrap();
2061            let iso = Iso::iso_from_fixed(RataDie::new(*f_date));
2062
2063            assert_eq!(iso.to_calendar(calendar).inner, date.inner, "{case:?}");
2064        }
2065    }
2066
2067    #[test]
2068    fn test_fixed_from_observational_islamic() {
2069        let calendar = IslamicObservational::new();
2070        let calendar = Ref(&calendar);
2071        for (case, f_date) in OBSERVATIONAL_CASES.iter().zip(TEST_FIXED_DATE.iter()) {
2072            let date =
2073                Date::try_new_observational_islamic_date(case.year, case.month, case.day, calendar)
2074                    .unwrap();
2075            assert_eq!(date.to_fixed(), RataDie::new(*f_date), "{case:?}");
2076        }
2077    }
2078
2079    #[test]
2080    fn test_fixed_from_islamic() {
2081        let calendar = IslamicCivil::new();
2082        let calendar = Ref(&calendar);
2083        for (case, f_date) in ARITHMETIC_CASES.iter().zip(TEST_FIXED_DATE.iter()) {
2084            let date = Date::try_new_islamic_civil_date_with_calendar(
2085                case.year, case.month, case.day, calendar,
2086            )
2087            .unwrap();
2088            assert_eq!(date.to_fixed(), RataDie::new(*f_date), "{case:?}");
2089        }
2090    }
2091
2092    #[test]
2093    fn test_islamic_from_fixed() {
2094        let calendar = IslamicCivil::new();
2095        let calendar = Ref(&calendar);
2096        for (case, f_date) in ARITHMETIC_CASES.iter().zip(TEST_FIXED_DATE.iter()) {
2097            let date = Date::try_new_islamic_civil_date_with_calendar(
2098                case.year, case.month, case.day, calendar,
2099            )
2100            .unwrap();
2101            let iso = Iso::iso_from_fixed(RataDie::new(*f_date));
2102
2103            assert_eq!(iso.to_calendar(calendar).inner, date.inner, "{case:?}");
2104        }
2105    }
2106
2107    #[test]
2108    fn test_fixed_from_islamic_tbla() {
2109        let calendar = IslamicTabular::new();
2110        let calendar = Ref(&calendar);
2111        for (case, f_date) in TABULAR_CASES.iter().zip(TEST_FIXED_DATE.iter()) {
2112            let date = Date::try_new_islamic_tabular_date_with_calendar(
2113                case.year, case.month, case.day, calendar,
2114            )
2115            .unwrap();
2116            assert_eq!(date.to_fixed(), RataDie::new(*f_date), "{case:?}");
2117        }
2118    }
2119
2120    #[test]
2121    fn test_islamic_tbla_from_fixed() {
2122        let calendar = IslamicTabular::new();
2123        let calendar = Ref(&calendar);
2124        for (case, f_date) in TABULAR_CASES.iter().zip(TEST_FIXED_DATE.iter()) {
2125            let date = Date::try_new_islamic_tabular_date_with_calendar(
2126                case.year, case.month, case.day, calendar,
2127            )
2128            .unwrap();
2129            let iso = Iso::iso_from_fixed(RataDie::new(*f_date));
2130
2131            assert_eq!(iso.to_calendar(calendar).inner, date.inner, "{case:?}");
2132        }
2133    }
2134
2135    #[test]
2136    fn test_saudi_islamic_from_fixed() {
2137        let calendar = IslamicUmmAlQura::new();
2138        let calendar = Ref(&calendar);
2139        for (case, f_date) in UMMALQURA_DATE_EXPECTED
2140            .iter()
2141            .zip(TEST_FIXED_DATE_UMMALQURA.iter())
2142        {
2143            let date =
2144                Date::try_new_ummalqura_date(case.year, case.month, case.day, calendar).unwrap();
2145            let iso = Iso::iso_from_fixed(RataDie::new(*f_date));
2146
2147            assert_eq!(iso.to_calendar(calendar).inner, date.inner, "{case:?}");
2148        }
2149    }
2150
2151    #[test]
2152    fn test_fixed_from_saudi_islamic() {
2153        let calendar = IslamicUmmAlQura::new();
2154        let calendar = Ref(&calendar);
2155        for (case, f_date) in UMMALQURA_DATE_EXPECTED
2156            .iter()
2157            .zip(TEST_FIXED_DATE_UMMALQURA.iter())
2158        {
2159            let date =
2160                Date::try_new_ummalqura_date(case.year, case.month, case.day, calendar).unwrap();
2161            assert_eq!(date.to_fixed(), RataDie::new(*f_date), "{case:?}");
2162        }
2163    }
2164
2165    #[ignore]
2166    #[test]
2167    fn test_days_in_provided_year_observational() {
2168        let calendar = IslamicObservational::new();
2169        let calendar = Ref(&calendar);
2170        // -1245 1 1 = -214526 (R.D Date)
2171        // 1518 1 1 = 764589 (R.D Date)
2172        let sum_days_in_year: i64 = (START_YEAR..END_YEAR)
2173            .map(|year| {
2174                IslamicObservational::days_in_provided_year(
2175                    year,
2176                    IslamicYearInfo::compute::<ObservationalIslamicMarker>(year),
2177                ) as i64
2178            })
2179            .sum();
2180        let expected_number_of_days =
2181            Date::try_new_observational_islamic_date(END_YEAR, 1, 1, calendar)
2182                .unwrap()
2183                .to_fixed()
2184                - Date::try_new_observational_islamic_date(START_YEAR, 1, 1, calendar)
2185                    .unwrap()
2186                    .to_fixed(); // The number of days between Islamic years -1245 and 1518
2187        let tolerance = 1; // One day tolerance (See Astronomical::month_length for more context)
2188
2189        assert!(
2190            (sum_days_in_year - expected_number_of_days).abs() <= tolerance,
2191            "Difference between sum_days_in_year and expected_number_of_days is more than the tolerance"
2192        );
2193    }
2194
2195    #[ignore]
2196    #[test]
2197    fn test_days_in_provided_year_ummalqura() {
2198        let calendar = IslamicUmmAlQura::new();
2199        let calendar = Ref(&calendar);
2200        // -1245 1 1 = -214528 (R.D Date)
2201        // 1518 1 1 = 764588 (R.D Date)
2202        let sum_days_in_year: i64 = (START_YEAR..END_YEAR)
2203            .map(|year| {
2204                IslamicUmmAlQura::days_in_provided_year(
2205                    year,
2206                    IslamicYearInfo::compute::<SaudiIslamicMarker>(year),
2207                ) as i64
2208            })
2209            .sum();
2210        let expected_number_of_days = Date::try_new_ummalqura_date(END_YEAR, 1, 1, calendar)
2211            .unwrap()
2212            .to_fixed()
2213            - Date::try_new_ummalqura_date(START_YEAR, 1, 1, calendar)
2214                .unwrap()
2215                .to_fixed(); // The number of days between Umm al-Qura Islamic years -1245 and 1518
2216
2217        assert_eq!(sum_days_in_year, expected_number_of_days);
2218    }
2219
2220    #[test]
2221    fn test_regression_3868() {
2222        // This date used to panic on creation
2223        let iso = Date::try_new_iso_date(2011, 4, 4).unwrap();
2224        let islamic = iso.to_calendar(IslamicUmmAlQura::new());
2225        // Data from https://www.ummulqura.org.sa/Index.aspx
2226        assert_eq!(islamic.day_of_month().0, 30);
2227        assert_eq!(islamic.month().ordinal, 4);
2228        assert_eq!(islamic.year().number, 1432);
2229    }
2230
2231    #[test]
2232    fn test_regression_4914() {
2233        // https://github.com/unicode-org/icu4x/issues/4914
2234        let cal = IslamicUmmAlQura::new_always_calculating();
2235        let era = "ah".parse().unwrap();
2236        let year = -6823;
2237        let month_code = "M01".parse().unwrap();
2238        let dt = cal.date_from_codes(era, year, month_code, 1).unwrap();
2239        assert_eq!(dt.0.day, 1);
2240        assert_eq!(dt.0.month, 1);
2241        assert_eq!(dt.0.year, -6823);
2242    }
2243}