//! This module contains types and implementations for the Islamic calendars. //! //! ```rust //! use icu::calendar::islamic::IslamicObservational; //! use icu::calendar::{Date, DateTime, Ref}; //! //! let islamic = IslamicObservational::new_always_calculating(); //! let islamic = Ref(&islamic); // to avoid cloning //! //! // `Date` type //! let islamic_date = //! Date::try_new_observational_islamic_date(1348, 10, 11, islamic) //! .expect("Failed to initialize islamic Date instance."); //! //! // `DateTime` type //! let islamic_datetime = DateTime::try_new_observational_islamic_datetime( //! 1348, 10, 11, 13, 1, 0, islamic, //! ) //! .expect("Failed to initialize islamic DateTime instance."); //! //! // `Date` checks //! assert_eq!(islamic_date.year().number, 1348); //! assert_eq!(islamic_date.month().ordinal, 10); //! assert_eq!(islamic_date.day_of_month().0, 11); //! //! // `DateTime` checks //! assert_eq!(islamic_datetime.date.year().number, 1348); //! assert_eq!(islamic_datetime.date.month().ordinal, 10); //! assert_eq!(islamic_datetime.date.day_of_month().0, 11); //! assert_eq!(islamic_datetime.time.hour.number(), 13); //! assert_eq!(islamic_datetime.time.minute.number(), 1); //! assert_eq!(islamic_datetime.time.second.number(), 0); //! ```
usecrate::calendar_arithmetic::PrecomputedDataSource; usecrate::calendar_arithmetic::{ArithmeticDate, CalendarArithmetic}; usecrate::provider::islamic::{
IslamicCacheV1, IslamicObservationalCacheV1Marker, IslamicUmmAlQuraCacheV1Marker,
PackedIslamicYearInfo,
}; usecrate::AnyCalendarKind; usecrate::AsCalendar; usecrate::Iso; usecrate::{types, Calendar, CalendarError, Date, DateDuration, DateDurationUnit, DateTime, Time}; use calendrical_calculations::islamic::{
IslamicBasedMarker, ObservationalIslamicMarker, SaudiIslamicMarker,
}; use calendrical_calculations::rata_die::RataDie; use core::marker::PhantomData; use icu_provider::prelude::*; use tinystr::tinystr;
/// Islamic Observational Calendar (Default) /// /// # Era codes /// /// This calendar supports a single era code, Anno Mundi, with code `"ah"` /// /// # Month codes /// /// This calendar is a pure lunar calendar with no leap months. It uses month codes /// `"M01" - "M12"`. #[derive(Clone, Debug, Default)] pubstruct IslamicObservational {
data: Option<DataPayload<IslamicObservationalCacheV1Marker>>,
}
/// Civil / Arithmetical Islamic Calendar (Used for administrative purposes) /// /// # Era codes /// /// This calendar supports a single era code, Anno Mundi, with code `"ah"` /// /// # Month codes /// /// This calendar is a pure lunar calendar with no leap months. It uses month codes /// `"M01" - "M12"`. #[derive(Copy, Clone, Debug, Default, Hash, Eq, PartialEq, PartialOrd, Ord)] #[allow(clippy::exhaustive_structs)] // unit struct pubstruct IslamicCivil;
/// Umm al-Qura Hijri Calendar (Used in Saudi Arabia) /// /// # Era codes /// /// This calendar supports a single era code, Anno Mundi, with code `"ah"` /// /// # Month codes /// /// This calendar is a pure lunar calendar with no leap months. It uses month codes /// `"M01" - "M12"`. #[derive(Clone, Debug, Default)] pubstruct IslamicUmmAlQura {
data: Option<DataPayload<IslamicUmmAlQuraCacheV1Marker>>,
}
/// A Tabular version of the Arithmetical Islamic Calendar /// /// # Era codes /// /// This calendar supports a single era code, Anno Mundi, with code `"ah"` /// /// # Month codes /// /// This calendar is a pure lunar calendar with no leap months. It uses month codes /// `"M01" - "M12"`. #[derive(Copy, Clone, Debug, Default, Hash, Eq, PartialEq, PartialOrd, Ord)] #[allow(clippy::exhaustive_structs)] // unit struct pubstruct IslamicTabular;
impl IslamicObservational { /// Creates a new [`IslamicObservational`] with some compiled data containing precomputed calendrical calculations. /// /// ✨ *Enabled with the `compiled_data` Cargo feature.* /// /// [ Help choosing a constructor](icu_provider::constructors) #[cfg(feature = "compiled_data")] pubconstfn new() -> Self { Self {
data: Some(DataPayload::from_static_ref( crate::provider::Baked::SINGLETON_CALENDAR_ISLAMICOBSERVATIONALCACHE_V1,
)),
}
}
/// Construct a new [`IslamicObservational`] without any precomputed calendrical calculations. pubfn new_always_calculating() -> Self { Self { data: None }
}
}
impl IslamicCivil { /// Construct a new [`IslamicCivil`] pubfn new() -> Self { Self
}
/// Construct a new [`IslamicCivil`] (deprecated: we will not add precomputation to this calendar) #[deprecated = "Precomputation not needed for this calendar"] pubfn new_always_calculating() -> Self { Self
}
}
impl IslamicUmmAlQura { /// Creates a new [`IslamicUmmAlQura`] with some compiled data containing precomputed calendrical calculations. /// /// ✨ *Enabled with the `compiled_data` Cargo feature.* /// /// [ Help choosing a constructor](icu_provider::constructors) #[cfg(feature = "compiled_data")] pubconstfn new() -> Self { Self {
data: Some(DataPayload::from_static_ref( crate::provider::Baked::SINGLETON_CALENDAR_ISLAMICUMMALQURACACHE_V1,
)),
}
}
/// Construct a new [`IslamicUmmAlQura`] without any precomputed calendrical calculations. pubfn new_always_calculating() -> Self { Self { data: None }
}
}
impl IslamicTabular { /// Construct a new [`IslamicTabular`] pubfn new() -> Self { Self
}
/// Construct a new [`IslamicTabular`] (deprecated: we will not add precomputation to this calendar) #[deprecated = "Precomputation not needed for this calendar"] pubfn new_always_calculating() -> Self { Self
}
}
/// Compact representation of the length of an Islamic year. #[derive(Copy, Clone, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)] enum IslamicYearLength { /// Long (355-day) Islamic year
L355, /// Short (354-day) Islamic year
L354, /// Unexpectedly Short (353-day) Islamic year /// /// It is probably a bug when this year length is returned. See: /// <https://github.com/unicode-org/icu4x/issues/4930>
L353,
}
impl IslamicYearInfo { pub(crate) const LONG_YEAR_LEN: u16 = 355; const SHORT_YEAR_LEN: u16 = 354; pub(crate) fn new(
prev_packed: PackedIslamicYearInfo,
this_packed: PackedIslamicYearInfo,
extended_year: i32,
) -> (Self, i32) { let days_in_year = prev_packed.days_in_year(); let days_in_year = match IslamicYearLength::try_from_int(days_in_year as i64) {
Some(x) => x,
None => {
debug_assert!(false, "Found wrong year length for Islamic year {extended_year}: Expected 355, 354, or 353, got {days_in_year}");
Default::default()
}
}; let year_info = Self {
prev_year_length: days_in_year,
packed_data: this_packed,
};
(year_info, extended_year)
}
fn compute<IB: IslamicBasedMarker>(extended_year: i32) -> Self { let ny = IB::fixed_from_islamic(extended_year, 1, 1); let packed_data = PackedIslamicYearInfo::compute_with_ny::<IB>(extended_year, ny); let prev_ny = IB::fixed_from_islamic(extended_year - 1, 1, 1); let rd_diff = ny - prev_ny; let rd_diff = match IslamicYearLength::try_from_int(rd_diff) {
Some(x) => x,
None => {
debug_assert!(false, "({}) Found wrong year length for Islamic year {extended_year}: Expected 355, 354, or 353, got {rd_diff}", IB::DEBUG_NAME);
Default::default()
}
}; Self {
prev_year_length: rd_diff,
packed_data,
}
} /// Get the new year R.D. given the extended year that this yearinfo is for fn new_year<IB: IslamicBasedMarker>(self, extended_year: i32) -> RataDie {
IB::mean_synodic_ny(extended_year) + i64::from(self.packed_data.ny_offset())
}
/// Get the date's R.D. given (y, m, d) in this info's year fn rd_for<IB: IslamicBasedMarker>(self, extended_year: i32, month: u8, day: u8) -> RataDie { let ny = self.new_year::<IB>(extended_year); let month_offset = if month == 1 { 0
} else { self.packed_data.last_day_of_month(month - 1)
}; // -1 since the offset is 1-indexed but the new year is also day 1
ny - 1 + month_offset.into() + day.into()
}
/// Contains any loaded precomputed data. If constructed with Default, will /// *not* contain any extra data and will always compute stuff from scratch #[derive(Default)] pub(crate) struct IslamicPrecomputedData<'a, IB: IslamicBasedMarker> {
data: Option<&'a IslamicCacheV1<'a>>,
_ib: PhantomData<IB>,
}
/// Given a year info and the first month it is possible for this date to be in, return the /// month and day this is in fn compute_month_day(info: IslamicYearInfo, mut possible_month: u8, day_of_year: u16) -> (u8, u8) { letmut last_day_of_month = info.packed_data.last_day_of_month(possible_month); letmut last_day_of_prev_month = if possible_month == 1 { 0
} else {
info.packed_data.last_day_of_month(possible_month - 1)
};
while day_of_year > last_day_of_month && possible_month <= 12 {
possible_month += 1;
last_day_of_prev_month = last_day_of_month;
last_day_of_month = info.packed_data.last_day_of_month(possible_month);
} let day = u8::try_from(day_of_year - last_day_of_prev_month);
debug_assert!(
day.is_ok(), "Found day {} that doesn't fit in month!",
day_of_year - last_day_of_prev_month
);
(possible_month, day.unwrap_or(29))
} impl<'b, IB: IslamicBasedMarker> IslamicPrecomputedData<'b, IB> { pub(crate) fn new(data: Option<&'b IslamicCacheV1<'b>>) -> Self { Self {
data,
_ib: PhantomData,
}
} /// Given an ISO date (in both ArithmeticDate and R.D. format), returns the IslamicYearInfo and extended year for that date, loading /// from cache or computing. fn load_or_compute_info_for_iso(&self, fixed: RataDie) -> (IslamicYearInfo, i32, u8, u8) { let cached = self.data.and_then(|d| d.get_for_fixed::<IB>(fixed)); iflet Some((cached, year)) = cached { let ny = cached.packed_data.ny::<IB>(year); let day_of_year = (fixed - ny) as u16 + 1;
debug_assert!(day_of_year < 360); // We divide by 30, not 29, to account for the case where all months before this // were length 30 (possible near the beginning of the year) // We add +1 because months are 1-indexed let possible_month = u8::try_from(1 + (day_of_year / 30)).unwrap_or(1); let (m, d) = compute_month_day(cached, possible_month, day_of_year); return (cached, year, m, d);
}; // compute
let (y, m, d) = IB::islamic_from_fixed(fixed); let info = IslamicYearInfo::compute::<IB>(y); let ny = info.packed_data.ny::<IB>(y); let day_of_year = (fixed - ny) as u16 + 1; // We can't use the m/d from islamic_from_fixed because that code // occasionally throws up 31-day months, which we normalize out. So we instead back-compute, starting with the previous month let (m, d) = if m > 1 {
compute_month_day(info, m - 1, day_of_year)
} else {
(m, d)
};
(info, y, m, d)
}
}
/// The inner date type used for representing [`Date`]s of [`IslamicObservational`]. See [`Date`] and [`IslamicObservational`] for more details.
// As an true lunar calendar, it does not have leap years. fn is_leap_year(_year: i32, year_info: IslamicYearInfo) -> bool {
year_info.packed_data.days_in_year() != IslamicYearInfo::SHORT_YEAR_LEN
}
fn last_month_day_in_year(year: i32, year_info: IslamicYearInfo) -> (u8, u8) { let days = Self::month_days(year, 12, year_info);
let (year_info, y, m, d) = self
.precomputed_data()
.load_or_compute_info_for_iso(fixed_iso);
IslamicDateInner(ArithmeticDate::new_unchecked_with_info(y, m, d, year_info))
}
#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq, PartialOrd, Ord)] /// The inner date type used for representing [`Date`]s of [`IslamicUmmAlQura`]. See [`Date`] and [`IslamicUmmAlQura`] for more details. pubstruct IslamicUmmAlQuraDateInner(ArithmeticDate<IslamicUmmAlQura>);
impl CalendarArithmetic for IslamicUmmAlQura { type YearInfo = IslamicYearInfo;
// As an true lunar calendar, it does not have leap years. fn is_leap_year(_year: i32, year_info: IslamicYearInfo) -> bool {
year_info.packed_data.days_in_year() != IslamicYearInfo::SHORT_YEAR_LEN
}
fn last_month_day_in_year(year: i32, year_info: IslamicYearInfo) -> (u8, u8) { let days = Self::month_days(year, 12, year_info);
(12, days)
}
}
impl Calendar for IslamicUmmAlQura { type DateInner = IslamicUmmAlQuraDateInner; fn date_from_codes(
&self,
era: types::Era,
year: i32,
month_code: types::MonthCode,
day: u8,
) -> Result<Self::DateInner, CalendarError> { let year = if era.0 == tinystr!(16, "islamic-umalqura")
|| era.0 == tinystr!(16, "islamic")
|| era.0 == tinystr!(16, "ah")
{
year
} else { return Err(CalendarError::UnknownEra(era.0, self.debug_name()));
};
let (year_info, y, m, d) = self
.precomputed_data()
.load_or_compute_info_for_iso(fixed_iso);
IslamicUmmAlQuraDateInner(ArithmeticDate::new_unchecked_with_info(y, m, d, year_info))
}
#[test] fn test_observational_islamic_from_fixed() { let calendar = IslamicObservational::new(); let calendar = Ref(&calendar); for (case, f_date) in OBSERVATIONAL_CASES.iter().zip(TEST_FIXED_DATE.iter()) { let date =
Date::try_new_observational_islamic_date(case.year, case.month, case.day, calendar)
.unwrap(); let iso = Iso::iso_from_fixed(RataDie::new(*f_date));
#[test] fn test_fixed_from_observational_islamic() { let calendar = IslamicObservational::new(); let calendar = Ref(&calendar); for (case, f_date) in OBSERVATIONAL_CASES.iter().zip(TEST_FIXED_DATE.iter()) { let date =
Date::try_new_observational_islamic_date(case.year, case.month, case.day, calendar)
.unwrap();
assert_eq!(date.to_fixed(), RataDie::new(*f_date), "{case:?}");
}
}
#[test] fn test_fixed_from_islamic() { let calendar = IslamicCivil::new(); let calendar = Ref(&calendar); for (case, f_date) in ARITHMETIC_CASES.iter().zip(TEST_FIXED_DATE.iter()) { let date = Date::try_new_islamic_civil_date_with_calendar(
case.year, case.month, case.day, calendar,
)
.unwrap();
assert_eq!(date.to_fixed(), RataDie::new(*f_date), "{case:?}");
}
}
#[test] fn test_islamic_from_fixed() { let calendar = IslamicCivil::new(); let calendar = Ref(&calendar); for (case, f_date) in ARITHMETIC_CASES.iter().zip(TEST_FIXED_DATE.iter()) { let date = Date::try_new_islamic_civil_date_with_calendar(
case.year, case.month, case.day, calendar,
)
.unwrap(); let iso = Iso::iso_from_fixed(RataDie::new(*f_date));
#[test] fn test_fixed_from_islamic_tbla() { let calendar = IslamicTabular::new(); let calendar = Ref(&calendar); for (case, f_date) in TABULAR_CASES.iter().zip(TEST_FIXED_DATE.iter()) { let date = Date::try_new_islamic_tabular_date_with_calendar(
case.year, case.month, case.day, calendar,
)
.unwrap();
assert_eq!(date.to_fixed(), RataDie::new(*f_date), "{case:?}");
}
}
#[test] fn test_islamic_tbla_from_fixed() { let calendar = IslamicTabular::new(); let calendar = Ref(&calendar); for (case, f_date) in TABULAR_CASES.iter().zip(TEST_FIXED_DATE.iter()) { let date = Date::try_new_islamic_tabular_date_with_calendar(
case.year, case.month, case.day, calendar,
)
.unwrap(); let iso = Iso::iso_from_fixed(RataDie::new(*f_date));
#[test] fn test_saudi_islamic_from_fixed() { let calendar = IslamicUmmAlQura::new(); let calendar = Ref(&calendar); for (case, f_date) in UMMALQURA_DATE_EXPECTED
.iter()
.zip(TEST_FIXED_DATE_UMMALQURA.iter())
{ let date =
Date::try_new_ummalqura_date(case.year, case.month, case.day, calendar).unwrap(); let iso = Iso::iso_from_fixed(RataDie::new(*f_date));
#[test] fn test_fixed_from_saudi_islamic() { let calendar = IslamicUmmAlQura::new(); let calendar = Ref(&calendar); for (case, f_date) in UMMALQURA_DATE_EXPECTED
.iter()
.zip(TEST_FIXED_DATE_UMMALQURA.iter())
{ let date =
Date::try_new_ummalqura_date(case.year, case.month, case.day, calendar).unwrap();
assert_eq!(date.to_fixed(), RataDie::new(*f_date), "{case:?}");
}
}
#[ignore] #[test] fn test_days_in_provided_year_observational() { let calendar = IslamicObservational::new(); let calendar = Ref(&calendar); // -1245 1 1 = -214526 (R.D Date) // 1518 1 1 = 764589 (R.D Date) let sum_days_in_year: i64 = (START_YEAR..END_YEAR)
.map(|year| {
IslamicObservational::days_in_provided_year(
year,
IslamicYearInfo::compute::<ObservationalIslamicMarker>(year),
) as i64
})
.sum(); let expected_number_of_days =
Date::try_new_observational_islamic_date(END_YEAR, 1, 1, calendar)
.unwrap()
.to_fixed()
- Date::try_new_observational_islamic_date(START_YEAR, 1, 1, calendar)
.unwrap()
.to_fixed(); // The number of days between Islamic years -1245 and 1518 let tolerance = 1; // One day tolerance (See Astronomical::month_length for more context)
assert!(
(sum_days_in_year - expected_number_of_days).abs() <= tolerance, "Difference between sum_days_in_year and expected_number_of_days is more than the tolerance"
);
}
#[ignore] #[test] fn test_days_in_provided_year_ummalqura() { let calendar = IslamicUmmAlQura::new(); let calendar = Ref(&calendar); // -1245 1 1 = -214528 (R.D Date) // 1518 1 1 = 764588 (R.D Date) let sum_days_in_year: i64 = (START_YEAR..END_YEAR)
.map(|year| {
IslamicUmmAlQura::days_in_provided_year(
year,
IslamicYearInfo::compute::<SaudiIslamicMarker>(year),
) as i64
})
.sum(); let expected_number_of_days = Date::try_new_ummalqura_date(END_YEAR, 1, 1, calendar)
.unwrap()
.to_fixed()
- Date::try_new_ummalqura_date(START_YEAR, 1, 1, calendar)
.unwrap()
.to_fixed(); // The number of days between Umm al-Qura Islamic years -1245 and 1518
#[test] fn test_regression_3868() { // This date used to panic on creation let iso = Date::try_new_iso_date(2011, 4, 4).unwrap(); let islamic = iso.to_calendar(IslamicUmmAlQura::new()); // Data from https://www.ummulqura.org.sa/Index.aspx
assert_eq!(islamic.day_of_month().0, 30);
assert_eq!(islamic.month().ordinal, 4);
assert_eq!(islamic.year().number, 1432);
}
#[test] fn test_regression_4914() { // https://github.com/unicode-org/icu4x/issues/4914 let cal = IslamicUmmAlQura::new_always_calculating(); let era = "ah".parse().unwrap(); let year = -6823; let month_code = "M01".parse().unwrap(); let dt = cal.date_from_codes(era, year, month_code, 1).unwrap();
assert_eq!(dt.0.day, 1);
assert_eq!(dt.0.month, 1);
assert_eq!(dt.0.year, -6823);
}
}
Messung V0.5 in Prozent
¤ Dauer der Verarbeitung: 0.33 Sekunden
(vorverarbeitet am 2026-06-19)
¤