use crate::calendar_arithmetic::ArithmeticDate;
use crate::calendar_arithmetic::DateFieldsResolver;
use crate::calendar_arithmetic::ToExtendedYear;
use crate::error::{DateError, DateFromFieldsError, EcmaReferenceYearError, UnknownEraError};
use crate::options::DateFromFieldsOptions;
use crate::options::{DateAddOptions, DateDifferenceOptions};
use crate::types::DateFields;
use crate::{types, Calendar, Date};
use crate::{AsCalendar, RangeError};
use calendrical_calculations::islamic::{
ISLAMIC_EPOCH_FRIDAY, ISLAMIC_EPOCH_THURSDAY, WELL_BEHAVED_ASTRONOMICAL_RANGE,
};
use calendrical_calculations::rata_die::RataDie;
use core::fmt::Debug;
use icu_locale_core::preferences::extensions::unicode::keywords::{
CalendarAlgorithm, HijriCalendarAlgorithm,
};
use icu_provider::prelude::*;
use tinystr::tinystr;
#[path = "hijri/simulated_mecca_data.rs"]
mod simulated_mecca_data;
#[path = "hijri/ummalqura_data.rs"]
mod ummalqura_data;
#[derive(Clone, Debug, Default, Copy)]
#[allow(clippy::exhaustive_structs)] pub struct Hijri<S>(pub S);
pub trait Rules: Clone + Debug + crate::cal::scaffold::UnstableSealed {
fn year_data(&self, extended_year: i32) -> HijriYearData;
fn ecma_reference_year(
&self,
_month_code: (u8, bool),
_day: u8,
) -> Result<i32, EcmaReferenceYearError> {
Err(EcmaReferenceYearError::Unimplemented)
}
fn calendar_algorithm(&self) -> Option<CalendarAlgorithm> {
None
}
fn debug_name(&self) -> &'static str {
"Hijri (custom rules)"
}
}
#[derive(Copy, Clone, Debug)]
pub struct AstronomicalSimulation {
pub(crate) location: SimulatedLocation,
}
#[derive(Clone, Debug, Copy, PartialEq)]
pub(crate) enum SimulatedLocation {
Mecca,
}
impl crate::cal::scaffold::UnstableSealed for AstronomicalSimulation {}
impl Rules for AstronomicalSimulation {
fn debug_name(&self) -> &'static str {
match self.location {
SimulatedLocation::Mecca => "Hijri (simulated, Mecca)",
}
}
fn year_data(&self, extended_year: i32) -> HijriYearData {
if let Some(data) = HijriYearData::lookup(
extended_year,
simulated_mecca_data::STARTING_YEAR,
simulated_mecca_data::DATA,
) {
return data;
}
let location = match self.location {
SimulatedLocation::Mecca => calendrical_calculations::islamic::MECCA,
};
let start_day = calendrical_calculations::islamic::fixed_from_observational_islamic(
extended_year,
1,
1,
location,
);
let next_start_day = calendrical_calculations::islamic::fixed_from_observational_islamic(
extended_year + 1,
1,
1,
location,
);
match (next_start_day - start_day) as u16 {
355 | 354 => (),
353 => {
icu_provider::log::trace!(
"({}) Found year {extended_year} AH with length {}. See <https://github.com/unicode-org/icu4x/issues/4930>",
self.debug_name(),
next_start_day - start_day
);
}
other => {
debug_assert!(
!WELL_BEHAVED_ASTRONOMICAL_RANGE.contains(&start_day),
"({}) Found year {extended_year} AH with length {}!",
self.debug_name(),
other
)
}
}
let month_lengths = {
let mut excess_days = 0;
let mut month_lengths = core::array::from_fn(|month_idx| {
let days_in_month =
calendrical_calculations::islamic::observational_islamic_month_days(
extended_year,
month_idx as u8 + 1,
location,
);
match days_in_month {
29 => false,
30 => true,
31 => {
icu_provider::log::trace!(
"({}) Found year {extended_year} AH with month length {days_in_month} for month {}.",
self.debug_name(),
month_idx + 1
);
excess_days += 1;
true
}
_ => {
debug_assert!(
!WELL_BEHAVED_ASTRONOMICAL_RANGE.contains(&start_day),
"({}) Found year {extended_year} AH with month length {days_in_month} for month {}!",
self.debug_name(),
month_idx + 1
);
false
}
}
});
if excess_days != 0 {
debug_assert!(
excess_days == 1 || !WELL_BEHAVED_ASTRONOMICAL_RANGE.contains(&start_day),
"({}) Found year {extended_year} AH with more than one excess day!",
self.debug_name()
);
if let Some(l) = month_lengths.iter_mut().find(|l| !(**l)) {
*l = true;
}
}
month_lengths
};
HijriYearData::try_new(extended_year, start_day, month_lengths)
.unwrap_or_else(|| UmmAlQura.year_data(extended_year))
}
}
#[derive(Copy, Clone, Debug, Default)]
#[non_exhaustive]
pub struct UmmAlQura;
impl crate::cal::scaffold::UnstableSealed for UmmAlQura {}
impl Rules for UmmAlQura {
fn calendar_algorithm(&self) -> Option<CalendarAlgorithm> {
Some(CalendarAlgorithm::Hijri(Some(
HijriCalendarAlgorithm::Umalqura,
)))
}
fn ecma_reference_year(
&self,
month_code: (u8, bool),
day: u8,
) -> Result<i32, EcmaReferenceYearError> {
let (ordinal_month, false) = month_code else {
return Err(EcmaReferenceYearError::MonthCodeNotInCalendar);
};
let extended_year = match (ordinal_month, day) {
(1, _) => 1392,
(2, 30..) => 1390,
(2, _) => 1392,
(3, 30..) => 1391,
(3, _) => 1392,
(4, _) => 1392,
(5, 30..) => 1391,
(5, _) => 1392,
(6, _) => 1392,
(7, 30..) => 1389,
(7, _) => 1392,
(8, _) => 1392,
(9, _) => 1392,
(10, 30..) => 1390,
(10, _) => 1392,
(11, ..=25) => 1392,
(11, _) => 1391,
(12, 30..) => 1390,
(12, _) => 1391,
_ => return Err(EcmaReferenceYearError::MonthCodeNotInCalendar),
};
Ok(extended_year)
}
fn debug_name(&self) -> &'static str {
"Hijri (Umm al-Qura)"
}
fn year_data(&self, extended_year: i32) -> HijriYearData {
if let Some(data) = HijriYearData::lookup(
extended_year,
ummalqura_data::STARTING_YEAR,
ummalqura_data::DATA,
) {
data
} else {
TabularAlgorithm {
leap_years: TabularAlgorithmLeapYears::TypeII,
epoch: TabularAlgorithmEpoch::Friday,
}
.year_data(extended_year)
}
}
}
#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq, PartialOrd, Ord)]
pub struct TabularAlgorithm {
pub(crate) leap_years: TabularAlgorithmLeapYears,
pub(crate) epoch: TabularAlgorithmEpoch,
}
impl TabularAlgorithm {
pub const fn new(leap_years: TabularAlgorithmLeapYears, epoch: TabularAlgorithmEpoch) -> Self {
Self { epoch, leap_years }
}
}
impl crate::cal::scaffold::UnstableSealed for TabularAlgorithm {}
impl Rules for TabularAlgorithm {
fn calendar_algorithm(&self) -> Option<CalendarAlgorithm> {
Some(match (self.epoch, self.leap_years) {
(TabularAlgorithmEpoch::Friday, TabularAlgorithmLeapYears::TypeII) => {
CalendarAlgorithm::Hijri(Some(HijriCalendarAlgorithm::Civil))
}
(TabularAlgorithmEpoch::Thursday, TabularAlgorithmLeapYears::TypeII) => {
CalendarAlgorithm::Hijri(Some(HijriCalendarAlgorithm::Tbla))
}
})
}
fn ecma_reference_year(
&self,
month_code: (u8, bool),
day: u8,
) -> Result<i32, EcmaReferenceYearError> {
let (ordinal_month, false) = month_code else {
return Err(EcmaReferenceYearError::MonthCodeNotInCalendar);
};
Ok(match (ordinal_month, day) {
(1, _) => 1392,
(2, 30..) => 1389,
(2, _) => 1392,
(3, _) => 1392,
(4, 30..) => 1389,
(4, _) => 1392,
(5, _) => 1392,
(6, 30..) => 1389,
(6, _) => 1392,
(7, _) => 1392,
(8, 30..) => 1389,
(8, _) => 1392,
(9, _) => 1392,
(10, 30..) => 1389,
(10, _) => 1392,
(11, ..=26) if self.epoch == TabularAlgorithmEpoch::Thursday => 1392,
(11, ..=25) if self.epoch == TabularAlgorithmEpoch::Friday => 1392,
(11, _) => 1391,
(12, 30..) => 1390,
(12, _) => 1391,
_ => return Err(EcmaReferenceYearError::MonthCodeNotInCalendar),
})
}
fn debug_name(&self) -> &'static str {
match self.epoch {
TabularAlgorithmEpoch::Friday => "Hijri (civil)",
TabularAlgorithmEpoch::Thursday => "Hijri (astronomical)",
}
}
fn year_data(&self, extended_year: i32) -> HijriYearData {
let start_day = calendrical_calculations::islamic::fixed_from_tabular_islamic(
extended_year,
1,
1,
self.epoch.rata_die(),
);
let month_lengths = core::array::from_fn(|m| {
m % 2 == 0
|| m == 11
&& match self.leap_years {
TabularAlgorithmLeapYears::TypeII => {
(14 + 11 * extended_year as i64).rem_euclid(30) < 11
}
}
});
HijriYearData {
packed: PackedHijriYearData::new_unchecked(extended_year, month_lengths, start_day),
extended_year,
}
}
}
impl Hijri<AstronomicalSimulation> {
#[cfg(feature = "compiled_data")]
#[deprecated(since = "2.1.0", note = "use `Hijri::new_simulated_mecca`")]
pub const fn new_mecca() -> Self {
Self::new_simulated_mecca()
}
pub const fn new_simulated_mecca() -> Self {
Self(AstronomicalSimulation {
location: SimulatedLocation::Mecca,
})
}
#[cfg(feature = "serde")]
#[doc = icu_provider::gen_buffer_unstable_docs!(BUFFER,Self::new)]
#[deprecated(since = "2.1.0", note = "use `Hijri::new_simulated_mecca`")]
pub fn try_new_mecca_with_buffer_provider(
_provider: &(impl icu_provider::buf::BufferProvider + ?Sized),
) -> Result<Self, DataError> {
Ok(Self::new_simulated_mecca())
}
#[doc = icu_provider::gen_buffer_unstable_docs!(UNSTABLE, Self::new_mecca)]
#[deprecated(since = "2.1.0", note = "use `Hijri::new_simulated_mecca`")]
pub fn try_new_mecca_unstable<D: ?Sized>(_provider: &D) -> Result<Self, DataError> {
Ok(Self::new_simulated_mecca())
}
#[deprecated(since = "2.1.0", note = "use `Hijri::new_simulated_mecca`")]
pub const fn new_mecca_always_calculating() -> Self {
Self::new_simulated_mecca()
}
}
impl Hijri<UmmAlQura> {
#[deprecated(since = "2.1.0", note = "use `Self::new_umm_al_qura`")]
pub const fn new() -> Self {
Self(UmmAlQura)
}
pub const fn new_umm_al_qura() -> Self {
Self(UmmAlQura)
}
}
#[non_exhaustive]
#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq, PartialOrd, Ord)]
pub enum TabularAlgorithmEpoch {
Thursday,
Friday,
}
impl TabularAlgorithmEpoch {
fn rata_die(self) -> RataDie {
match self {
Self::Thursday => ISLAMIC_EPOCH_THURSDAY,
Self::Friday => ISLAMIC_EPOCH_FRIDAY,
}
}
}
#[non_exhaustive]
#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq, PartialOrd, Ord)]
pub enum TabularAlgorithmLeapYears {
TypeII,
}
impl Hijri<TabularAlgorithm> {
#[deprecated(since = "2.1.0", note = "use `Hijri::new_tabular`")]
pub const fn new(leap_years: TabularAlgorithmLeapYears, epoch: TabularAlgorithmEpoch) -> Self {
Hijri::new_tabular(leap_years, epoch)
}
pub const fn new_tabular(
leap_years: TabularAlgorithmLeapYears,
epoch: TabularAlgorithmEpoch,
) -> Self {
Self(TabularAlgorithm::new(leap_years, epoch))
}
}
#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)]
pub struct HijriYearData {
packed: PackedHijriYearData,
extended_year: i32,
}
impl ToExtendedYear for HijriYearData {
fn to_extended_year(&self) -> i32 {
self.extended_year
}
}
impl HijriYearData {
pub fn try_new(
extended_year: i32,
start_day: RataDie,
month_lengths: [bool; 12],
) -> Option<Self> {
Some(Self {
packed: PackedHijriYearData::try_new(extended_year, month_lengths, start_day)?,
extended_year,
})
}
fn lookup(
extended_year: i32,
starting_year: i32,
data: &[PackedHijriYearData],
) -> Option<Self> {
Some(extended_year)
.and_then(|e| usize::try_from(e.checked_sub(starting_year)?).ok())
.and_then(|i| data.get(i))
.map(|&packed| Self {
extended_year,
packed,
})
}
fn new_year(self) -> RataDie {
self.packed.new_year(self.extended_year)
}
}
#[derive(Copy, Clone, Hash, PartialEq, Eq, PartialOrd, Ord, Debug)]
struct PackedHijriYearData(u16);
impl PackedHijriYearData {
const fn try_new(
extended_year: i32,
month_lengths: [bool; 12],
start_day: RataDie,
) -> Option<Self> {
let start_offset = start_day.since(Self::mean_tabular_start_day(extended_year));
if !(-8 < start_offset && start_offset < 8
|| calendrical_calculations::islamic::WELL_BEHAVED_ASTRONOMICAL_RANGE
.start
.to_i64_date()
> start_day.to_i64_date()
|| calendrical_calculations::islamic::WELL_BEHAVED_ASTRONOMICAL_RANGE
.end
.to_i64_date()
< start_day.to_i64_date())
{
return None;
}
let start_offset = start_offset as i8 & 0b1000_0111u8 as i8;
let mut all = 0u16;
let mut num_days = 29 * 12;
let mut i = 0;
while i < 12 {
#[expect(clippy::indexing_slicing)]
if month_lengths[i] {
all |= 1 << i;
num_days += 1;
}
i += 1;
}
if !matches!(num_days, 354 | 355) {
return None;
}
if start_offset < 0 {
all |= 1 << 12;
}
all |= (start_offset.unsigned_abs() as u16) << 13;
Some(Self(all))
}
const fn new_unchecked(
extended_year: i32,
month_lengths: [bool; 12],
start_day: RataDie,
) -> Self {
let start_offset = start_day.since(Self::mean_tabular_start_day(extended_year));
let start_offset = start_offset as i8 & 0b1000_0111u8 as i8;
let mut all = 0u16;
let mut i = 0;
while i < 12 {
#[expect(clippy::indexing_slicing)]
if month_lengths[i] {
all |= 1 << i;
}
i += 1;
}
if start_offset < 0 {
all |= 1 << 12;
}
all |= (start_offset.unsigned_abs() as u16) << 13;
Self(all)
}
fn new_year(self, extended_year: i32) -> RataDie {
let start_offset = if (self.0 & 0b1_0000_0000_0000) != 0 {
-((self.0 >> 13) as i64)
} else {
(self.0 >> 13) as i64
};
Self::mean_tabular_start_day(extended_year) + start_offset
}
fn month_has_30_days(self, month: u8) -> bool {
self.0 & (1 << (month - 1) as u16) != 0
}
fn is_leap(self) -> bool {
(self.0 & ((1 << 12) - 1)).count_ones() == 7
}
fn last_day_of_month(self, month: u8) -> u16 {
let mut prev_month_lengths = 29 * month as u16;
let long_month_bits = self.0 & ((1 << month as u16) - 1);
prev_month_lengths += long_month_bits.count_ones().try_into().unwrap_or(0);
prev_month_lengths
}
fn days_in_year(self) -> u16 {
self.last_day_of_month(12)
}
const fn mean_tabular_start_day(extended_year: i32) -> RataDie {
calendrical_calculations::islamic::ISLAMIC_EPOCH_FRIDAY
.add((extended_year as i64 - 1) * (354 * 30 + 11) / 30)
}
}
impl<A: AsCalendar<Calendar = Hijri<AstronomicalSimulation>>> Date<A> {
#[deprecated(since = "2.1.0", note = "use `Date::try_new_hijri_with_calendar`")]
pub fn try_new_simulated_hijri_with_calendar(
year: i32,
month: u8,
day: u8,
calendar: A,
) -> Result<Date<A>, RangeError> {
Date::try_new_hijri_with_calendar(year, month, day, calendar)
}
}
#[test]
fn computer_reference_years() {
let rules = UmmAlQura;
fn compute_hijri_reference_year<C>(
ordinal_month: u8,
day: u8,
cal: &C,
year_info_from_extended: impl Fn(i32) -> C::YearInfo,
) -> Result<C::YearInfo, DateError>
where
C: DateFieldsResolver,
{
let dec_31 = Date::from_rata_die(
crate::cal::abstract_gregorian::LAST_DAY_OF_REFERENCE_YEAR,
crate::Ref(cal),
);
debug_assert_eq!(dec_31.month().ordinal, 11);
let (y0, y1, y2, y3) =
if ordinal_month < 11 || (ordinal_month == 11 && day <= dec_31.day_of_month().0) {
(1389, 1390, 1391, 1392)
} else {
(1388, 1389, 1390, 1391)
};
let year_info = year_info_from_extended(y3);
if day <= C::days_in_provided_month(year_info, ordinal_month) {
return Ok(year_info);
}
let year_info = year_info_from_extended(y2);
if day <= C::days_in_provided_month(year_info, ordinal_month) {
return Ok(year_info);
}
let year_info = year_info_from_extended(y1);
if day <= C::days_in_provided_month(year_info, ordinal_month) {
return Ok(year_info);
}
let year_info = year_info_from_extended(y0);
if day <= 29 {
debug_assert!(
day <= C::days_in_provided_month(year_info, ordinal_month),
"{ordinal_month}/{day}"
);
}
Ok(year_info)
}
for month in 1..=12 {
for day in [30, 29] {
let y = compute_hijri_reference_year(month, day, &Hijri(rules), |e| rules.year_data(e))
.unwrap()
.extended_year;
if day == 30 {
println!("({month}, {day}) => {y},")
} else {
println!("({month}, _) => {y},")
}
}
}
}
#[allow(clippy::derived_hash_with_manual_eq)] #[derive(Clone, Debug, Hash)]
pub struct HijriDateInner<R: Rules>(ArithmeticDate<Hijri<R>>);
impl<R: Rules> Copy for HijriDateInner<R> {}
impl<R: Rules> PartialEq for HijriDateInner<R> {
fn eq(&self, other: &Self) -> bool {
self.0 == other.0
}
}
impl<R: Rules> Eq for HijriDateInner<R> {}
impl<R: Rules> PartialOrd for HijriDateInner<R> {
fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl<R: Rules> Ord for HijriDateInner<R> {
fn cmp(&self, other: &Self) -> core::cmp::Ordering {
self.0.cmp(&other.0)
}
}
impl<R: Rules> DateFieldsResolver for Hijri<R> {
type YearInfo = HijriYearData;
fn days_in_provided_month(year: Self::YearInfo, month: u8) -> u8 {
29 + year.packed.month_has_30_days(month) as u8
}
fn months_in_provided_year(_year: Self::YearInfo) -> u8 {
12
}
#[inline]
fn year_info_from_era(
&self,
era: &[u8],
era_year: i32,
) -> Result<Self::YearInfo, UnknownEraError> {
let extended_year = match era {
b"ah" => era_year,
b"bh" => 1 - era_year,
_ => return Err(UnknownEraError),
};
Ok(self.year_info_from_extended(extended_year))
}
#[inline]
fn year_info_from_extended(&self, extended_year: i32) -> Self::YearInfo {
self.0.year_data(extended_year)
}
#[inline]
fn reference_year_from_month_day(
&self,
month_code: types::ValidMonthCode,
day: u8,
) -> Result<Self::YearInfo, EcmaReferenceYearError> {
self.0
.ecma_reference_year(month_code.to_tuple(), day)
.map(|y| self.0.year_data(y))
}
}
impl<R: Rules> crate::cal::scaffold::UnstableSealed for Hijri<R> {}
impl<R: Rules> Calendar for Hijri<R> {
type DateInner = HijriDateInner<R>;
type Year = types::EraYear;
type DifferenceError = core::convert::Infallible;
fn from_codes(
&self,
era: Option<&str>,
year: i32,
month_code: types::MonthCode,
day: u8,
) -> Result<Self::DateInner, DateError> {
ArithmeticDate::from_codes(era, year, month_code, day, self).map(HijriDateInner)
}
#[cfg(feature = "unstable")]
fn from_fields(
&self,
fields: DateFields,
options: DateFromFieldsOptions,
) -> Result<Self::DateInner, DateFromFieldsError> {
ArithmeticDate::from_fields(fields, options, self).map(HijriDateInner)
}
fn from_rata_die(&self, rd: RataDie) -> Self::DateInner {
let extended_year = (rd - calendrical_calculations::islamic::ISLAMIC_EPOCH_FRIDAY) * 30
/ (354 * 30 + 11)
+ (rd >= calendrical_calculations::islamic::ISLAMIC_EPOCH_FRIDAY) as i64;
let extended_year = extended_year.clamp(i32::MIN as i64, i32::MAX as i64) as i32;
let mut year = self.0.year_data(extended_year);
if rd >= year.new_year() + year.packed.days_in_year() as i64 && extended_year < i32::MAX {
year = self.0.year_data(year.extended_year + 1)
}
let rd = rd.clamp(
year.new_year(),
year.new_year() + year.packed.days_in_year() as i64,
);
let day_of_year = (rd - year.new_year()) as u16;
let mut month = (day_of_year / 30) as u8 + 1;
let mut last_day_of_month = year.packed.last_day_of_month(month);
let mut last_day_of_prev_month = year.packed.last_day_of_month(month - 1);
while day_of_year >= last_day_of_month {
month += 1;
last_day_of_prev_month = last_day_of_month;
last_day_of_month = year.packed.last_day_of_month(month);
}
let day = (day_of_year + 1 - last_day_of_prev_month) as u8;
HijriDateInner(ArithmeticDate::new_unchecked(year, month, day))
}
fn to_rata_die(&self, date: &Self::DateInner) -> RataDie {
date.0.year.new_year()
+ date.0.year.packed.last_day_of_month(date.0.month - 1) as i64
+ (date.0.day - 1) as i64
}
fn has_cheap_iso_conversion(&self) -> bool {
false
}
fn months_in_year(&self, date: &Self::DateInner) -> u8 {
Self::months_in_provided_year(date.0.year)
}
fn days_in_year(&self, date: &Self::DateInner) -> u16 {
date.0.year.packed.days_in_year()
}
fn days_in_month(&self, date: &Self::DateInner) -> u8 {
Self::days_in_provided_month(date.0.year, date.0.month)
}
#[cfg(feature = "unstable")]
fn add(
&self,
date: &Self::DateInner,
duration: types::DateDuration,
options: DateAddOptions,
) -> Result<Self::DateInner, DateError> {
date.0.added(duration, self, options).map(HijriDateInner)
}
#[cfg(feature = "unstable")]
fn until(
&self,
date1: &Self::DateInner,
date2: &Self::DateInner,
options: DateDifferenceOptions,
) -> Result<types::DateDuration, Self::DifferenceError> {
Ok(date1.0.until(&date2.0, self, options))
}
fn debug_name(&self) -> &'static str {
self.0.debug_name()
}
fn year_info(&self, date: &Self::DateInner) -> Self::Year {
let extended_year = date.0.year.extended_year;
if extended_year > 0 {
types::EraYear {
era: tinystr!(16, "ah"),
era_index: Some(0),
year: extended_year,
extended_year,
ambiguity: types::YearAmbiguity::CenturyRequired,
}
} else {
types::EraYear {
era: tinystr!(16, "bh"),
era_index: Some(1),
year: 1 - extended_year,
extended_year,
ambiguity: types::YearAmbiguity::CenturyRequired,
}
}
}
fn is_in_leap_year(&self, date: &Self::DateInner) -> bool {
date.0.year.packed.is_leap()
}
fn month(&self, date: &Self::DateInner) -> types::MonthInfo {
types::MonthInfo::non_lunisolar(date.0.month)
}
fn day_of_month(&self, date: &Self::DateInner) -> types::DayOfMonth {
types::DayOfMonth(date.0.day)
}
fn day_of_year(&self, date: &Self::DateInner) -> types::DayOfYear {
types::DayOfYear(date.0.year.packed.last_day_of_month(date.0.month - 1) + date.0.day as u16)
}
fn calendar_algorithm(&self) -> Option<crate::preferences::CalendarAlgorithm> {
self.0.calendar_algorithm()
}
}
impl<A: AsCalendar<Calendar = Hijri<R>>, R: Rules> Date<A> {
pub fn try_new_hijri_with_calendar(
year: i32,
month: u8,
day: u8,
calendar: A,
) -> Result<Self, RangeError> {
let y = calendar.as_calendar().0.year_data(year);
Ok(Date::from_raw(
HijriDateInner(ArithmeticDate::try_from_ymd(y, month, day)?),
calendar,
))
}
}
impl Date<Hijri<UmmAlQura>> {
#[deprecated(since = "2.1.0", note = "use `Date::try_new_hijri_with_calendar")]
pub fn try_new_ummalqura(year: i32, month: u8, day: u8) -> Result<Self, RangeError> {
Date::try_new_hijri_with_calendar(year, month, day, Hijri::new_umm_al_qura())
}
}
impl<A: AsCalendar<Calendar = Hijri<TabularAlgorithm>>> Date<A> {
#[deprecated(since = "2.1.0", note = "use `Date::try_new_hijri_with_calendar")]
pub fn try_new_hijri_tabular_with_calendar(
year: i32,
month: u8,
day: u8,
calendar: A,
) -> Result<Date<A>, RangeError> {
Date::try_new_hijri_with_calendar(year, month, day, calendar)
}
}
#[cfg(test)]
mod test {
use types::MonthCode;
use super::*;
const START_YEAR: i32 = -1245;
const END_YEAR: i32 = 1518;
#[derive(Debug)]
struct DateCase {
year: i32,
month: u8,
day: u8,
}
static TEST_RD: [i64; 33] = [
-214193, -61387, 25469, 49217, 171307, 210155, 253427, 369740, 400085, 434355, 452605,
470160, 473837, 507850, 524156, 544676, 567118, 569477, 601716, 613424, 626596, 645554,
664224, 671401, 694799, 704424, 708842, 709409, 709580, 727274, 728714, 744313, 764652,
];
static UMMALQURA_CASES: [DateCase; 33] = [
DateCase {
year: -1245,
month: 12,
day: 9,
},
DateCase {
year: -813,
month: 2,
day: 23,
},
DateCase {
year: -568,
month: 4,
day: 1,
},
DateCase {
year: -501,
month: 4,
day: 6,
},
DateCase {
year: -157,
month: 10,
day: 17,
},
DateCase {
year: -47,
month: 6,
day: 3,
},
DateCase {
year: 75,
month: 7,
day: 13,
},
DateCase {
year: 403,
month: 10,
day: 5,
},
DateCase {
year: 489,
month: 5,
day: 22,
},
DateCase {
year: 586,
month: 2,
day: 7,
},
DateCase {
year: 637,
month: 8,
day: 7,
},
DateCase {
year: 687,
month: 2,
day: 20,
},
DateCase {
year: 697,
month: 7,
day: 7,
},
DateCase {
year: 793,
month: 7,
day: 1,
},
DateCase {
year: 839,
month: 7,
day: 6,
},
DateCase {
year: 897,
month: 6,
day: 1,
},
DateCase {
year: 960,
month: 9,
day: 30,
},
DateCase {
year: 967,
month: 5,
day: 27,
},
DateCase {
year: 1058,
month: 5,
day: 18,
},
DateCase {
year: 1091,
month: 6,
day: 2,
},
DateCase {
year: 1128,
month: 8,
day: 4,
},
DateCase {
year: 1182,
month: 2,
day: 3,
},
DateCase {
year: 1234,
month: 10,
day: 10,
},
DateCase {
year: 1255,
month: 1,
day: 11,
},
DateCase {
year: 1321,
month: 1,
day: 21,
},
DateCase {
year: 1348,
month: 3,
day: 20,
},
DateCase {
year: 1360,
month: 9,
day: 7,
},
DateCase {
year: 1362,
month: 4,
day: 14,
},
DateCase {
year: 1362,
month: 10,
day: 7,
},
DateCase {
year: 1412,
month: 9,
day: 12,
},
DateCase {
year: 1416,
month: 10,
day: 6,
},
DateCase {
year: 1460,
month: 10,
day: 13,
},
DateCase {
year: 1518,
month: 3,
day: 5,
},
];
static SIMULATED_CASES: [DateCase; 33] = [
DateCase {
year: -1245,
month: 12,
day: 10,
},
DateCase {
year: -813,
month: 2,
day: 25,
},
DateCase {
year: -568,
month: 4,
day: 2,
},
DateCase {
year: -501,
month: 4,
day: 7,
},
DateCase {
year: -157,
month: 10,
day: 18,
},
DateCase {
year: -47,
month: 6,
day: 3,
},
DateCase {
year: 75,
month: 7,
day: 13,
},
DateCase {
year: 403,
month: 10,
day: 5,
},
DateCase {
year: 489,
month: 5,
day: 22,
},
DateCase {
year: 586,
month: 2,
day: 7,
},
DateCase {
year: 637,
month: 8,
day: 7,
},
DateCase {
year: 687,
month: 2,
day: 21,
},
DateCase {
year: 697,
month: 7,
day: 7,
},
DateCase {
year: 793,
month: 6,
day: 29,
},
DateCase {
year: 839,
month: 7,
day: 6,
},
DateCase {
year: 897,
month: 6,
day: 2,
},
DateCase {
year: 960,
month: 9,
day: 30,
},
DateCase {
year: 967,
month: 5,
day: 27,
},
DateCase {
year: 1058,
month: 5,
day: 18,
},
DateCase {
year: 1091,
month: 6,
day: 3,
},
DateCase {
year: 1128,
month: 8,
day: 4,
},
DateCase {
year: 1182,
month: 2,
day: 4,
},
DateCase {
year: 1234,
month: 10,
day: 10,
},
DateCase {
year: 1255,
month: 1,
day: 11,
},
DateCase {
year: 1321,
month: 1,
day: 20,
},
DateCase {
year: 1348,
month: 3,
day: 19,
},
DateCase {
year: 1360,
month: 9,
day: 7,
},
DateCase {
year: 1362,
month: 4,
day: 13,
},
DateCase {
year: 1362,
month: 10,
day: 7,
},
DateCase {
year: 1412,
month: 9,
day: 12,
},
DateCase {
year: 1416,
month: 10,
day: 5,
},
DateCase {
year: 1460,
month: 10,
day: 12,
},
DateCase {
year: 1518,
month: 3,
day: 5,
},
];
static ARITHMETIC_CASES: [DateCase; 33] = [
DateCase {
year: -1245,
month: 12,
day: 9,
},
DateCase {
year: -813,
month: 2,
day: 23,
},
DateCase {
year: -568,
month: 4,
day: 1,
},
DateCase {
year: -501,
month: 4,
day: 6,
},
DateCase {
year: -157,
month: 10,
day: 17,
},
DateCase {
year: -47,
month: 6,
day: 3,
},
DateCase {
year: 75,
month: 7,
day: 13,
},
DateCase {
year: 403,
month: 10,
day: 5,
},
DateCase {
year: 489,
month: 5,
day: 22,
},
DateCase {
year: 586,
month: 2,
day: 7,
},
DateCase {
year: 637,
month: 8,
day: 7,
},
DateCase {
year: 687,
month: 2,
day: 20,
},
DateCase {
year: 697,
month: 7,
day: 7,
},
DateCase {
year: 793,
month: 7,
day: 1,
},
DateCase {
year: 839,
month: 7,
day: 6,
},
DateCase {
year: 897,
month: 6,
day: 1,
},
DateCase {
year: 960,
month: 9,
day: 30,
},
DateCase {
year: 967,
month: 5,
day: 27,
},
DateCase {
year: 1058,
month: 5,
day: 18,
},
DateCase {
year: 1091,
month: 6,
day: 2,
},
DateCase {
year: 1128,
month: 8,
day: 4,
},
DateCase {
year: 1182,
month: 2,
day: 3,
},
DateCase {
year: 1234,
month: 10,
day: 10,
},
DateCase {
year: 1255,
month: 1,
day: 11,
},
DateCase {
year: 1321,
month: 1,
day: 21,
},
DateCase {
year: 1348,
month: 3,
day: 19,
},
DateCase {
year: 1360,
month: 9,
day: 8,
},
DateCase {
year: 1362,
month: 4,
day: 13,
},
DateCase {
year: 1362,
month: 10,
day: 7,
},
DateCase {
year: 1412,
month: 9,
day: 13,
},
DateCase {
year: 1416,
month: 10,
day: 5,
},
DateCase {
year: 1460,
month: 10,
day: 12,
},
DateCase {
year: 1518,
month: 3,
day: 5,
},
];
static ASTRONOMICAL_CASES: [DateCase; 33] = [
DateCase {
year: -1245,
month: 12,
day: 10,
},
DateCase {
year: -813,
month: 2,
day: 24,
},
DateCase {
year: -568,
month: 4,
day: 2,
},
DateCase {
year: -501,
month: 4,
day: 7,
},
DateCase {
year: -157,
month: 10,
day: 18,
},
DateCase {
year: -47,
month: 6,
day: 4,
},
DateCase {
year: 75,
month: 7,
day: 14,
},
DateCase {
year: 403,
month: 10,
day: 6,
},
DateCase {
year: 489,
month: 5,
day: 23,
},
DateCase {
year: 586,
month: 2,
day: 8,
},
DateCase {
year: 637,
month: 8,
day: 8,
},
DateCase {
year: 687,
month: 2,
day: 21,
},
DateCase {
year: 697,
month: 7,
day: 8,
},
DateCase {
year: 793,
month: 7,
day: 2,
},
DateCase {
year: 839,
month: 7,
day: 7,
},
DateCase {
year: 897,
month: 6,
day: 2,
},
DateCase {
year: 960,
month: 10,
day: 1,
},
DateCase {
year: 967,
month: 5,
day: 28,
},
DateCase {
year: 1058,
month: 5,
day: 19,
},
DateCase {
year: 1091,
month: 6,
day: 3,
},
DateCase {
year: 1128,
month: 8,
day: 5,
},
DateCase {
year: 1182,
month: 2,
day: 4,
},
DateCase {
year: 1234,
month: 10,
day: 11,
},
DateCase {
year: 1255,
month: 1,
day: 12,
},
DateCase {
year: 1321,
month: 1,
day: 22,
},
DateCase {
year: 1348,
month: 3,
day: 20,
},
DateCase {
year: 1360,
month: 9,
day: 9,
},
DateCase {
year: 1362,
month: 4,
day: 14,
},
DateCase {
year: 1362,
month: 10,
day: 8,
},
DateCase {
year: 1412,
month: 9,
day: 14,
},
DateCase {
year: 1416,
month: 10,
day: 6,
},
DateCase {
year: 1460,
month: 10,
day: 13,
},
DateCase {
year: 1518,
month: 3,
day: 6,
},
];
#[test]
fn test_simulated_hijri_from_rd() {
let calendar = Hijri::new_simulated_mecca();
for (case, f_date) in SIMULATED_CASES.iter().zip(TEST_RD.iter()) {
let date = Date::try_new_hijri_with_calendar(case.year, case.month, case.day, calendar)
.unwrap();
let iso = Date::from_rata_die(RataDie::new(*f_date), crate::Iso);
assert_eq!(iso.to_calendar(calendar).inner, date.inner, "{case:?}");
}
}
#[test]
fn test_rd_from_simulated_hijri() {
let calendar = Hijri::new_simulated_mecca();
for (case, f_date) in SIMULATED_CASES.iter().zip(TEST_RD.iter()) {
let date = Date::try_new_hijri_with_calendar(case.year, case.month, case.day, calendar)
.unwrap();
assert_eq!(date.to_rata_die(), RataDie::new(*f_date), "{case:?}");
}
}
#[test]
fn test_rd_from_hijri() {
let calendar = Hijri::new_tabular(
TabularAlgorithmLeapYears::TypeII,
TabularAlgorithmEpoch::Friday,
);
for (case, f_date) in ARITHMETIC_CASES.iter().zip(TEST_RD.iter()) {
let date = Date::try_new_hijri_with_calendar(case.year, case.month, case.day, calendar)
.unwrap();
assert_eq!(date.to_rata_die(), RataDie::new(*f_date), "{case:?}");
}
}
#[test]
fn test_hijri_from_rd() {
let calendar = Hijri::new_tabular(
TabularAlgorithmLeapYears::TypeII,
TabularAlgorithmEpoch::Friday,
);
for (case, f_date) in ARITHMETIC_CASES.iter().zip(TEST_RD.iter()) {
let date = Date::try_new_hijri_with_calendar(case.year, case.month, case.day, calendar)
.unwrap();
let date_rd = Date::from_rata_die(RataDie::new(*f_date), calendar);
assert_eq!(date, date_rd, "{case:?}");
}
}
#[test]
fn test_rd_from_hijri_tbla() {
let calendar = Hijri::new_tabular(
TabularAlgorithmLeapYears::TypeII,
TabularAlgorithmEpoch::Thursday,
);
for (case, f_date) in ASTRONOMICAL_CASES.iter().zip(TEST_RD.iter()) {
let date = Date::try_new_hijri_with_calendar(case.year, case.month, case.day, calendar)
.unwrap();
assert_eq!(date.to_rata_die(), RataDie::new(*f_date), "{case:?}");
}
}
#[test]
fn test_hijri_tbla_from_rd() {
let calendar = Hijri::new_tabular(
TabularAlgorithmLeapYears::TypeII,
TabularAlgorithmEpoch::Thursday,
);
for (case, f_date) in ASTRONOMICAL_CASES.iter().zip(TEST_RD.iter()) {
let date = Date::try_new_hijri_with_calendar(case.year, case.month, case.day, calendar)
.unwrap();
let date_rd = Date::from_rata_die(RataDie::new(*f_date), calendar);
assert_eq!(date, date_rd, "{case:?}");
}
}
#[test]
fn test_saudi_hijri_from_rd() {
let calendar = Hijri::new_umm_al_qura();
for (case, f_date) in UMMALQURA_CASES.iter().zip(TEST_RD.iter()) {
let date = Date::try_new_hijri_with_calendar(case.year, case.month, case.day, calendar)
.unwrap();
let date_rd = Date::from_rata_die(RataDie::new(*f_date), calendar);
assert_eq!(date, date_rd, "{case:?}");
}
}
#[test]
fn test_rd_from_saudi_hijri() {
let calendar = Hijri::new_umm_al_qura();
for (case, f_date) in UMMALQURA_CASES.iter().zip(TEST_RD.iter()) {
let date = Date::try_new_hijri_with_calendar(case.year, case.month, case.day, calendar)
.unwrap();
assert_eq!(date.to_rata_die(), RataDie::new(*f_date), "{case:?}");
}
}
#[ignore] #[test]
fn test_days_in_provided_year_simulated() {
let calendar = Hijri::new_simulated_mecca();
let sum_days_in_year: i64 = (START_YEAR..END_YEAR)
.map(|year| {
Hijri::new_simulated_mecca()
.0
.year_data(year)
.packed
.days_in_year() as i64
})
.sum();
let expected_number_of_days = Date::try_new_hijri_with_calendar(END_YEAR, 1, 1, calendar)
.unwrap()
.to_rata_die()
- Date::try_new_hijri_with_calendar(START_YEAR, 1, 1, calendar)
.unwrap()
.to_rata_die(); let tolerance = 1;
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 = Hijri::new_umm_al_qura();
let sum_days_in_year: i64 = (START_YEAR..END_YEAR)
.map(|year| calendar.0.year_data(year).packed.days_in_year() as i64)
.sum();
let expected_number_of_days = Date::try_new_hijri_with_calendar(END_YEAR, 1, 1, calendar)
.unwrap()
.to_rata_die()
- (Date::try_new_hijri_with_calendar(START_YEAR, 1, 1, calendar).unwrap())
.to_rata_die();
assert_eq!(sum_days_in_year, expected_number_of_days);
}
#[test]
fn test_regression_3868() {
let iso = Date::try_new_iso(2011, 4, 4).unwrap();
let hijri = iso.to_calendar(Hijri::new_umm_al_qura());
assert_eq!(hijri.day_of_month().0, 30);
assert_eq!(hijri.month().ordinal, 4);
assert_eq!(hijri.era_year().year, 1432);
}
#[test]
fn test_regression_4914() {
let dt = Hijri::new_umm_al_qura()
.from_codes(Some("bh"), 6824, MonthCode::new_normal(1).unwrap(), 1)
.unwrap();
assert_eq!(dt.0.day, 1);
assert_eq!(dt.0.month, 1);
assert_eq!(dt.0.year.extended_year, -6823);
}
#[test]
fn test_regression_7056() {
let calendar = Hijri::new_tabular(
TabularAlgorithmLeapYears::TypeII,
TabularAlgorithmEpoch::Friday,
);
let iso = Date::try_new_iso(-62971, 3, 19).unwrap();
let _dt = iso.to_calendar(calendar);
let _dt = iso.to_calendar(Hijri::new_umm_al_qura());
}
#[test]
fn test_regression_5069_uaq() {
let calendar = Hijri::new_umm_al_qura();
let dt = Date::try_new_hijri_with_calendar(1391, 1, 29, calendar).unwrap();
assert_eq!(dt.to_iso().to_calendar(calendar), dt);
}
#[test]
fn test_regression_5069_obs() {
let cal = Hijri::new_simulated_mecca();
let dt = Date::try_new_hijri_with_calendar(1390, 1, 30, cal).unwrap();
assert_eq!(dt.to_iso().to_calendar(cal), dt);
let dt = Date::try_new_iso(2000, 5, 5).unwrap();
assert!(dt.to_calendar(cal).day_of_month().0 > 0);
}
#[test]
fn test_regression_6197() {
let calendar = Hijri::new_umm_al_qura();
let iso = Date::try_new_iso(2025, 2, 26).unwrap();
let date = iso.to_calendar(calendar);
assert_eq!(
(
date.day_of_month().0,
date.month().ordinal,
date.era_year().year
),
(27, 8, 1446)
);
}
#[test]
fn test_hijri_packed_roundtrip() {
fn single_roundtrip(month_lengths: [bool; 12], start_day: RataDie) -> Option<()> {
let packed = PackedHijriYearData::try_new(1600, month_lengths, start_day)?;
for i in 0..12 {
assert_eq!(packed.month_has_30_days(i + 1), month_lengths[i as usize]);
}
assert_eq!(packed.new_year(1600), start_day);
Some(())
}
let l = true;
let s = false;
let all_short = [s; 12];
let all_long = [l; 12];
let mixed1 = [l, s, l, s, l, s, l, s, l, s, l, s];
let mixed2 = [s, s, l, l, l, s, l, s, s, s, l, l];
let start_1600 = PackedHijriYearData::mean_tabular_start_day(1600);
assert_eq!(single_roundtrip(all_short, start_1600), None);
assert_eq!(single_roundtrip(all_long, start_1600), None);
single_roundtrip(mixed1, start_1600).unwrap();
single_roundtrip(mixed2, start_1600).unwrap();
single_roundtrip(mixed1, start_1600 - 7).unwrap();
single_roundtrip(mixed2, start_1600 + 7).unwrap();
single_roundtrip(mixed2, start_1600 + 4).unwrap();
single_roundtrip(mixed2, start_1600 + 1).unwrap();
single_roundtrip(mixed2, start_1600 - 1).unwrap();
single_roundtrip(mixed2, start_1600 - 4).unwrap();
}
}