Source code

Revision control

Copy as Markdown

Other Tools

// This file is part of ICU4X. For terms of use, please see the file
// called LICENSE at the top level of the ICU4X source tree
//! 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);
//! ```
use crate::calendar_arithmetic::PrecomputedDataSource;
use crate::calendar_arithmetic::{ArithmeticDate, CalendarArithmetic};
use crate::provider::islamic::{
IslamicCacheV1, IslamicObservationalCacheV1Marker, IslamicUmmAlQuraCacheV1Marker,
PackedIslamicYearInfo,
};
use crate::AnyCalendarKind;
use crate::AsCalendar;
use crate::Iso;
use crate::{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)]
pub struct 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
pub struct 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)]
pub struct 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
pub struct 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")]
pub const fn new() -> Self {
Self {
data: Some(DataPayload::from_static_ref(
crate::provider::Baked::SINGLETON_CALENDAR_ISLAMICOBSERVATIONALCACHE_V1,
)),
}
}
icu_provider::gen_any_buffer_data_constructors!(locale: skip, options: skip, error: CalendarError,
#[cfg(skip)]
functions: [
new,
try_new_with_any_provider,
try_new_with_buffer_provider,
try_new_unstable,
Self,
]);
#[doc = icu_provider::gen_any_buffer_unstable_docs!(UNSTABLE, Self::new)]
pub fn try_new_unstable<D: DataProvider<IslamicObservationalCacheV1Marker> + ?Sized>(
provider: &D,
) -> Result<Self, CalendarError> {
Ok(Self {
data: Some(provider.load(Default::default())?.take_payload()?),
})
}
/// Construct a new [`IslamicObservational`] without any precomputed calendrical calculations.
pub fn new_always_calculating() -> Self {
Self { data: None }
}
}
impl IslamicCivil {
/// Construct a new [`IslamicCivil`]
pub fn new() -> Self {
Self
}
/// Construct a new [`IslamicCivil`] (deprecated: we will not add precomputation to this calendar)
#[deprecated = "Precomputation not needed for this calendar"]
pub fn 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")]
pub const fn new() -> Self {
Self {
data: Some(DataPayload::from_static_ref(
crate::provider::Baked::SINGLETON_CALENDAR_ISLAMICUMMALQURACACHE_V1,
)),
}
}
icu_provider::gen_any_buffer_data_constructors!(locale: skip, options: skip, error: CalendarError,
#[cfg(skip)]
functions: [
new,
try_new_with_any_provider,
try_new_with_buffer_provider,
try_new_unstable,
Self,
]);
#[doc = icu_provider::gen_any_buffer_unstable_docs!(UNSTABLE, Self::new)]
pub fn try_new_unstable<D: DataProvider<IslamicUmmAlQuraCacheV1Marker> + ?Sized>(
provider: &D,
) -> Result<Self, CalendarError> {
Ok(Self {
data: Some(provider.load(Default::default())?.take_payload()?),
})
}
/// Construct a new [`IslamicUmmAlQura`] without any precomputed calendrical calculations.
pub fn new_always_calculating() -> Self {
Self { data: None }
}
}
impl IslamicTabular {
/// Construct a new [`IslamicTabular`]
pub fn new() -> Self {
Self
}
/// Construct a new [`IslamicTabular`] (deprecated: we will not add precomputation to this calendar)
#[deprecated = "Precomputation not needed for this calendar"]
pub fn 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:
L353,
}
impl Default for IslamicYearLength {
fn default() -> Self {
Self::L354
}
}
impl IslamicYearLength {
fn try_from_int(value: i64) -> Option<Self> {
match value {
355 => Some(Self::L355),
354 => Some(Self::L354),
353 => Some(Self::L353),
_ => None,
}
}
fn to_int(self) -> u16 {
match self {
Self::L355 => 355,
Self::L354 => 354,
Self::L353 => 353,
}
}
}
#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)]
pub(crate) struct IslamicYearInfo {
packed_data: PackedIslamicYearInfo,
prev_year_length: IslamicYearLength,
}
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()
}
#[inline]
fn days_in_prev_year(self) -> u16 {
self.prev_year_length.to_int()
}
}
/// 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>,
}
impl<'b, IB: IslamicBasedMarker> PrecomputedDataSource<IslamicYearInfo>
for IslamicPrecomputedData<'b, IB>
{
fn load_or_compute_info(&self, extended_year: i32) -> IslamicYearInfo {
self.data
.and_then(|d| d.get_for_extended_year(extended_year))
.unwrap_or_else(|| IslamicYearInfo::compute::<IB>(extended_year))
}
}
/// 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) {
let mut last_day_of_month = info.packed_data.last_day_of_month(possible_month);
let mut 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));
if let 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.
#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq, PartialOrd, Ord)]
pub struct IslamicDateInner(ArithmeticDate<IslamicObservational>);
impl CalendarArithmetic for IslamicObservational {
type YearInfo = IslamicYearInfo;
fn month_days(_year: i32, month: u8, year_info: IslamicYearInfo) -> u8 {
year_info.packed_data.days_in_month(month)
}
fn months_for_every_year(_year: i32, _year_info: IslamicYearInfo) -> u8 {
12
}
fn days_in_provided_year(_year: i32, year_info: IslamicYearInfo) -> u16 {
year_info.packed_data.days_in_year()
}
// 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 IslamicObservational {
type DateInner = IslamicDateInner;
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") || era.0 == tinystr!(16, "ah") {
year
} else {
return Err(CalendarError::UnknownEra(era.0, self.debug_name()));
};
let month = if let Some((ordinal, false)) = month_code.parsed() {
ordinal
} else {
return Err(CalendarError::UnknownMonthCode(
month_code.0,
self.debug_name(),
));
};
ArithmeticDate::new_from_ordinals_with_info(
year,
month,
day,
self.precomputed_data().load_or_compute_info(year),
)
.map(IslamicDateInner)
}
fn date_from_iso(&self, iso: Date<crate::Iso>) -> Self::DateInner {
let fixed_iso = Iso::fixed_from_iso(*iso.inner());
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))
}
fn date_to_iso(&self, date: &Self::DateInner) -> Date<crate::Iso> {
let fixed = date.0.year_info.rd_for::<ObservationalIslamicMarker>(
date.0.year,
date.0.month,
date.0.day,
);
Iso::iso_from_fixed(fixed)
}
fn months_in_year(&self, date: &Self::DateInner) -> u8 {
date.0.months_in_year()
}
fn days_in_year(&self, date: &Self::DateInner) -> u16 {
date.0.days_in_year()
}
fn days_in_month(&self, date: &Self::DateInner) -> u8 {
date.0.days_in_month()
}
fn day_of_week(&self, date: &Self::DateInner) -> types::IsoWeekday {
Iso.day_of_week(self.date_to_iso(date).inner())
}
fn offset_date(&self, date: &mut Self::DateInner, offset: DateDuration<Self>) {
date.0.offset_date(offset, &self.precomputed_data())
}
fn until(
&self,
date1: &Self::DateInner,
date2: &Self::DateInner,
_calendar2: &Self,
_largest_unit: DateDurationUnit,
_smallest_unit: DateDurationUnit,
) -> DateDuration<Self> {
date1.0.until(date2.0, _largest_unit, _smallest_unit)
}
fn debug_name(&self) -> &'static str {
Self::DEBUG_NAME
}
fn year(&self, date: &Self::DateInner) -> types::FormattableYear {
Self::year_as_islamic(date.0.year)
}
fn is_in_leap_year(&self, date: &Self::DateInner) -> bool {
Self::is_leap_year(date.0.year, date.0.year_info)
}
fn month(&self, date: &Self::DateInner) -> types::FormattableMonth {
date.0.month()
}
fn day_of_month(&self, date: &Self::DateInner) -> types::DayOfMonth {
date.0.day_of_month()
}
fn day_of_year_info(&self, date: &Self::DateInner) -> types::DayOfYearInfo {
let prev_year = date.0.year.saturating_sub(1);
let next_year = date.0.year.saturating_add(1);
types::DayOfYearInfo {
day_of_year: date.0.day_of_year(),
days_in_year: date.0.days_in_year(),
prev_year: Self::year_as_islamic(prev_year),
days_in_prev_year: date.0.year_info.days_in_prev_year(),
next_year: Self::year_as_islamic(next_year),
}
}
fn any_calendar_kind(&self) -> Option<AnyCalendarKind> {
Some(AnyCalendarKind::IslamicObservational)
}
}
impl IslamicObservational {
fn precomputed_data(&self) -> IslamicPrecomputedData<ObservationalIslamicMarker> {
IslamicPrecomputedData::new(self.data.as_ref().map(|x| x.get()))
}
fn year_as_islamic(year: i32) -> types::FormattableYear {
types::FormattableYear {
era: types::Era(tinystr!(16, "islamic")),
number: year,
cyclic: None,
related_iso: None,
}
}
pub(crate) const DEBUG_NAME: &'static str = "Islamic (observational)";
}
impl<A: AsCalendar<Calendar = IslamicObservational>> Date<A> {
/// Construct new Islamic Observational Date.
///
/// Has no negative years, only era is the AH.
///
/// ```rust
/// use icu::calendar::islamic::IslamicObservational;
/// use icu::calendar::Date;
///
/// let islamic = IslamicObservational::new_always_calculating();
///
/// let date_islamic =
/// Date::try_new_observational_islamic_date(1392, 4, 25, islamic)
/// .expect("Failed to initialize Islamic Date instance.");
///
/// assert_eq!(date_islamic.year().number, 1392);
/// assert_eq!(date_islamic.month().ordinal, 4);
/// assert_eq!(date_islamic.day_of_month().0, 25);
/// ```
pub fn try_new_observational_islamic_date(
year: i32,
month: u8,
day: u8,
calendar: A,
) -> Result<Date<A>, CalendarError> {
let year_info = calendar
.as_calendar()
.precomputed_data()
.load_or_compute_info(year);
ArithmeticDate::new_from_ordinals_with_info(year, month, day, year_info)
.map(IslamicDateInner)
.map(|inner| Date::from_raw(inner, calendar))
}
}
impl<A: AsCalendar<Calendar = IslamicObservational>> DateTime<A> {
/// Construct a new Islamic Observational datetime from integers.
///
/// ```rust
/// use icu::calendar::islamic::IslamicObservational;
/// use icu::calendar::DateTime;
///
/// let islamic = IslamicObservational::new_always_calculating();
///
/// let datetime_islamic = DateTime::try_new_observational_islamic_datetime(
/// 474, 10, 11, 13, 1, 0, islamic,
/// )
/// .expect("Failed to initialize Islamic DateTime instance.");
///
/// assert_eq!(datetime_islamic.date.year().number, 474);
/// assert_eq!(datetime_islamic.date.month().ordinal, 10);
/// assert_eq!(datetime_islamic.date.day_of_month().0, 11);
/// assert_eq!(datetime_islamic.time.hour.number(), 13);
/// assert_eq!(datetime_islamic.time.minute.number(), 1);
/// assert_eq!(datetime_islamic.time.second.number(), 0);
/// ```
pub fn try_new_observational_islamic_datetime(
year: i32,
month: u8,
day: u8,
hour: u8,
minute: u8,
second: u8,
calendar: A,
) -> Result<DateTime<A>, CalendarError> {
Ok(DateTime {
date: Date::try_new_observational_islamic_date(year, month, day, calendar)?,
time: Time::try_new(hour, minute, second, 0)?,
})
}
}
#[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.
pub struct IslamicUmmAlQuraDateInner(ArithmeticDate<IslamicUmmAlQura>);
impl CalendarArithmetic for IslamicUmmAlQura {
type YearInfo = IslamicYearInfo;
fn month_days(_year: i32, month: u8, year_info: IslamicYearInfo) -> u8 {
year_info.packed_data.days_in_month(month)
}
fn months_for_every_year(_year: i32, _year_info: IslamicYearInfo) -> u8 {
12
}
fn days_in_provided_year(_year: i32, year_info: IslamicYearInfo) -> u16 {
year_info.packed_data.days_in_year()
}
// 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 month = if let Some((ordinal, false)) = month_code.parsed() {
ordinal
} else {
return Err(CalendarError::UnknownMonthCode(
month_code.0,
self.debug_name(),
));
};
ArithmeticDate::new_from_ordinals_with_info(
year,
month,
day,
self.precomputed_data().load_or_compute_info(year),
)
.map(IslamicUmmAlQuraDateInner)
}
fn date_from_iso(&self, iso: Date<Iso>) -> Self::DateInner {
let fixed_iso = Iso::fixed_from_iso(*iso.inner());
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))
}
fn date_to_iso(&self, date: &Self::DateInner) -> Date<Iso> {
let fixed =
date.0
.year_info
.rd_for::<SaudiIslamicMarker>(date.0.year, date.0.month, date.0.day);
Iso::iso_from_fixed(fixed)
}
fn months_in_year(&self, date: &Self::DateInner) -> u8 {
date.0.months_in_year()
}
fn days_in_year(&self, date: &Self::DateInner) -> u16 {
date.0.days_in_year()
}
fn days_in_month(&self, date: &Self::DateInner) -> u8 {
date.0.days_in_month()
}
fn offset_date(&self, date: &mut Self::DateInner, offset: DateDuration<Self>) {
date.0.offset_date(offset, &self.precomputed_data())
}
fn until(
&self,
date1: &Self::DateInner,
date2: &Self::DateInner,
_calendar2: &Self,
_largest_unit: DateDurationUnit,
_smallest_unit: DateDurationUnit,
) -> DateDuration<Self> {
date1.0.until(date2.0, _largest_unit, _smallest_unit)
}
fn debug_name(&self) -> &'static str {
Self::DEBUG_NAME
}
fn year(&self, date: &Self::DateInner) -> types::FormattableYear {
Self::year_as_islamic(date.0.year)
}
fn is_in_leap_year(&self, date: &Self::DateInner) -> bool {
Self::is_leap_year(date.0.year, date.0.year_info)
}
fn month(&self, date: &Self::DateInner) -> types::FormattableMonth {
date.0.month()
}
fn day_of_month(&self, date: &Self::DateInner) -> types::DayOfMonth {
date.0.day_of_month()
}
fn day_of_year_info(&self, date: &Self::DateInner) -> types::DayOfYearInfo {
let prev_year = date.0.year.saturating_sub(1);
let next_year = date.0.year.saturating_add(1);
types::DayOfYearInfo {
day_of_year: date.0.day_of_year(),
days_in_year: date.0.days_in_year(),
prev_year: Self::year_as_islamic(prev_year),
days_in_prev_year: date.0.year_info.days_in_prev_year(),
next_year: Self::year_as_islamic(next_year),
}
}
fn any_calendar_kind(&self) -> Option<AnyCalendarKind> {
Some(AnyCalendarKind::IslamicUmmAlQura)
}
}
impl IslamicUmmAlQura {
fn precomputed_data(&self) -> IslamicPrecomputedData<SaudiIslamicMarker> {
IslamicPrecomputedData::new(self.data.as_ref().map(|x| x.get()))
}
fn year_as_islamic(year: i32) -> types::FormattableYear {
types::FormattableYear {
era: types::Era(tinystr!(16, "islamic")),
number: year,
cyclic: None,
related_iso: None,
}
}
pub(crate) const DEBUG_NAME: &'static str = "Islamic (Umm al-Qura)";
}
impl<A: AsCalendar<Calendar = IslamicUmmAlQura>> Date<A> {
/// Construct new Islamic Umm al-Qura Date.
///
/// Has no negative years, only era is the AH.
///
/// ```rust
/// use icu::calendar::islamic::IslamicUmmAlQura;
/// use icu::calendar::Date;
///
/// let islamic = IslamicUmmAlQura::new_always_calculating();
///
/// let date_islamic = Date::try_new_ummalqura_date(1392, 4, 25, islamic)
/// .expect("Failed to initialize Islamic Date instance.");
///
/// assert_eq!(date_islamic.year().number, 1392);
/// assert_eq!(date_islamic.month().ordinal, 4);
/// assert_eq!(date_islamic.day_of_month().0, 25);
/// ```
pub fn try_new_ummalqura_date(
year: i32,
month: u8,
day: u8,
calendar: A,
) -> Result<Date<A>, CalendarError> {
let year_info = calendar
.as_calendar()
.precomputed_data()
.load_or_compute_info(year);
ArithmeticDate::new_from_ordinals_with_info(year, month, day, year_info)
.map(IslamicUmmAlQuraDateInner)
.map(|inner| Date::from_raw(inner, calendar))
}
}
impl<A: AsCalendar<Calendar = IslamicUmmAlQura>> DateTime<A> {
/// Construct a new Islamic Umm al-Qura datetime from integers.
///
/// ```rust
/// use icu::calendar::islamic::IslamicUmmAlQura;
/// use icu::calendar::DateTime;
///
/// let islamic = IslamicUmmAlQura::new_always_calculating();
///
/// let datetime_islamic =
/// DateTime::try_new_ummalqura_datetime(474, 10, 11, 13, 1, 0, islamic)
/// .expect("Failed to initialize Islamic DateTime instance.");
///
/// assert_eq!(datetime_islamic.date.year().number, 474);
/// assert_eq!(datetime_islamic.date.month().ordinal, 10);
/// assert_eq!(datetime_islamic.date.day_of_month().0, 11);
/// assert_eq!(datetime_islamic.time.hour.number(), 13);
/// assert_eq!(datetime_islamic.time.minute.number(), 1);
/// assert_eq!(datetime_islamic.time.second.number(), 0);
/// ```
pub fn try_new_ummalqura_datetime(
year: i32,
month: u8,
day: u8,
hour: u8,
minute: u8,
second: u8,
calendar: A,
) -> Result<DateTime<A>, CalendarError> {
Ok(DateTime {
date: Date::try_new_ummalqura_date(year, month, day, calendar)?,
time: Time::try_new(hour, minute, second, 0)?,
})
}
}
/// The inner date type used for representing [`Date`]s of [`IslamicCivil`]. See [`Date`] and [`IslamicCivil`] for more details.
#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq, PartialOrd, Ord)]
pub struct IslamicCivilDateInner(ArithmeticDate<IslamicCivil>);
impl CalendarArithmetic for IslamicCivil {
type YearInfo = ();
fn month_days(year: i32, month: u8, _data: ()) -> u8 {
match month {
1 | 3 | 5 | 7 | 9 | 11 => 30,
2 | 4 | 6 | 8 | 10 => 29,
12 if Self::is_leap_year(year, ()) => 30,
12 => 29,
_ => 0,
}
}
fn months_for_every_year(_year: i32, _data: ()) -> u8 {
12
}
fn days_in_provided_year(year: i32, _data: ()) -> u16 {
if Self::is_leap_year(year, ()) {
IslamicYearInfo::LONG_YEAR_LEN
} else {
IslamicYearInfo::SHORT_YEAR_LEN
}
}
fn is_leap_year(year: i32, _data: ()) -> bool {
(14 + 11 * year).rem_euclid(30) < 11
}
fn last_month_day_in_year(year: i32, _data: ()) -> (u8, u8) {
if Self::is_leap_year(year, ()) {
(12, 30)
} else {
(12, 29)
}
}
}
impl Calendar for IslamicCivil {
type DateInner = IslamicCivilDateInner;
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-civil")
|| era.0 == tinystr!(16, "islamicc")
|| era.0 == tinystr!(16, "islamic")
|| era.0 == tinystr!(16, "ah")
{
// TODO: Check name and alias
year
} else {
return Err(CalendarError::UnknownEra(era.0, self.debug_name()));
};
ArithmeticDate::new_from_codes(self, year, month_code, day).map(IslamicCivilDateInner)
}
fn date_from_iso(&self, iso: Date<Iso>) -> Self::DateInner {
let fixed_iso = Iso::fixed_from_iso(*iso.inner());
Self::islamic_from_fixed(fixed_iso).inner
}
fn date_to_iso(&self, date: &Self::DateInner) -> Date<Iso> {
let fixed_islamic = Self::fixed_from_islamic(*date);
Iso::iso_from_fixed(fixed_islamic)
}
fn months_in_year(&self, date: &Self::DateInner) -> u8 {
date.0.months_in_year()
}
fn days_in_year(&self, date: &Self::DateInner) -> u16 {
date.0.days_in_year()
}
fn days_in_month(&self, date: &Self::DateInner) -> u8 {
date.0.days_in_month()
}
fn day_of_week(&self, date: &Self::DateInner) -> types::IsoWeekday {
Iso.day_of_week(self.date_to_iso(date).inner())
}
fn offset_date(&self, date: &mut Self::DateInner, offset: DateDuration<Self>) {
date.0.offset_date(offset, &())
}
fn until(
&self,
date1: &Self::DateInner,
date2: &Self::DateInner,
_calendar2: &Self,
_largest_unit: DateDurationUnit,
_smallest_unit: DateDurationUnit,
) -> DateDuration<Self> {
date1.0.until(date2.0, _largest_unit, _smallest_unit)
}
fn debug_name(&self) -> &'static str {
"Islamic (civil)"
}
fn year(&self, date: &Self::DateInner) -> types::FormattableYear {
Self::year_as_islamic(date.0.year)
}
fn is_in_leap_year(&self, date: &Self::DateInner) -> bool {
Self::is_leap_year(date.0.year, ())
}
fn month(&self, date: &Self::DateInner) -> types::FormattableMonth {
date.0.month()
}
fn day_of_month(&self, date: &Self::DateInner) -> types::DayOfMonth {
date.0.day_of_month()
}
fn day_of_year_info(&self, date: &Self::DateInner) -> types::DayOfYearInfo {
let prev_year = date.0.year.saturating_sub(1);
let next_year = date.0.year.saturating_add(1);
types::DayOfYearInfo {
day_of_year: date.0.day_of_year(),
days_in_year: date.0.days_in_year(),
prev_year: Self::year_as_islamic(prev_year),
days_in_prev_year: Self::days_in_provided_year(prev_year, ()),
next_year: Self::year_as_islamic(next_year),
}
}
fn any_calendar_kind(&self) -> Option<AnyCalendarKind> {
Some(AnyCalendarKind::IslamicCivil)
}
}
impl IslamicCivil {
fn fixed_from_islamic(i_date: IslamicCivilDateInner) -> RataDie {
calendrical_calculations::islamic::fixed_from_islamic_civil(
i_date.0.year,
i_date.0.month,
i_date.0.day,
)
}
fn islamic_from_fixed(date: RataDie) -> Date<IslamicCivil> {
let (y, m, d) = calendrical_calculations::islamic::islamic_civil_from_fixed(date);
debug_assert!(
Date::try_new_islamic_civil_date_with_calendar(y, m, d, IslamicCivil).is_ok()
);
Date::from_raw(
IslamicCivilDateInner(ArithmeticDate::new_unchecked(y, m, d)),
IslamicCivil,
)
}
fn year_as_islamic(year: i32) -> types::FormattableYear {
types::FormattableYear {
era: types::Era(tinystr!(16, "islamic")),
number: year,
cyclic: None,
related_iso: None,
}
}
}
impl<A: AsCalendar<Calendar = IslamicCivil>> Date<A> {
/// Construct new Civil Islamic Date.
///
/// Has no negative years, only era is the AH.
///
/// ```rust
/// use icu::calendar::islamic::IslamicCivil;
/// use icu::calendar::Date;
///
/// let islamic = IslamicCivil::new_always_calculating();
///
/// let date_islamic =
/// Date::try_new_islamic_civil_date_with_calendar(1392, 4, 25, islamic)
/// .expect("Failed to initialize Islamic Date instance.");
///
/// assert_eq!(date_islamic.year().number, 1392);
/// assert_eq!(date_islamic.month().ordinal, 4);
/// assert_eq!(date_islamic.day_of_month().0, 25);
/// ```
pub fn try_new_islamic_civil_date_with_calendar(
year: i32,
month: u8,
day: u8,
calendar: A,
) -> Result<Date<A>, CalendarError> {
ArithmeticDate::new_from_ordinals(year, month, day)
.map(IslamicCivilDateInner)
.map(|inner| Date::from_raw(inner, calendar))
}
}
impl<A: AsCalendar<Calendar = IslamicCivil>> DateTime<A> {
/// Construct a new Civil Islamic datetime from integers.
///
/// ```rust
/// use icu::calendar::islamic::IslamicCivil;
/// use icu::calendar::DateTime;
///
/// let islamic = IslamicCivil::new_always_calculating();
///
/// let datetime_islamic =
/// DateTime::try_new_islamic_civil_datetime_with_calendar(
/// 474, 10, 11, 13, 1, 0, islamic,
/// )
/// .expect("Failed to initialize Islamic DateTime instance.");
///
/// assert_eq!(datetime_islamic.date.year().number, 474);
/// assert_eq!(datetime_islamic.date.month().ordinal, 10);
/// assert_eq!(datetime_islamic.date.day_of_month().0, 11);
/// assert_eq!(datetime_islamic.time.hour.number(), 13);
/// assert_eq!(datetime_islamic.time.minute.number(), 1);
/// assert_eq!(datetime_islamic.time.second.number(), 0);
/// ```
pub fn try_new_islamic_civil_datetime_with_calendar(
year: i32,
month: u8,
day: u8,
hour: u8,
minute: u8,
second: u8,
calendar: A,
) -> Result<DateTime<A>, CalendarError> {
Ok(DateTime {
date: Date::try_new_islamic_civil_date_with_calendar(year, month, day, calendar)?,
time: Time::try_new(hour, minute, second, 0)?,
})
}
}
/// The inner date type used for representing [`Date`]s of [`IslamicTabular`]. See [`Date`] and [`IslamicTabular`] for more details.
#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq, PartialOrd, Ord)]
pub struct IslamicTabularDateInner(ArithmeticDate<IslamicTabular>);
impl CalendarArithmetic for IslamicTabular {
type YearInfo = ();
fn month_days(year: i32, month: u8, _data: ()) -> u8 {
match month {
1 | 3 | 5 | 7 | 9 | 11 => 30,
2 | 4 | 6 | 8 | 10 => 29,
12 if Self::is_leap_year(year, ()) => 30,
12 => 29,
_ => 0,
}
}
fn months_for_every_year(_year: i32, _data: ()) -> u8 {
12
}
fn days_in_provided_year(year: i32, _data: ()) -> u16 {
if Self::is_leap_year(year, ()) {
IslamicYearInfo::LONG_YEAR_LEN
} else {
IslamicYearInfo::SHORT_YEAR_LEN
}
}
fn is_leap_year(year: i32, _data: ()) -> bool {
(14 + 11 * year).rem_euclid(30) < 11
}
fn last_month_day_in_year(year: i32, _data: ()) -> (u8, u8) {
if Self::is_leap_year(year, ()) {
(12, 30)
} else {
(12, 29)
}
}
}
impl Calendar for IslamicTabular {
type DateInner = IslamicTabularDateInner;
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-tbla")
|| era.0 == tinystr!(16, "islamic")
|| era.0 == tinystr!(16, "ah")
{
year
} else {
return Err(CalendarError::UnknownEra(era.0, self.debug_name()));
};
ArithmeticDate::new_from_codes(self, year, month_code, day).map(IslamicTabularDateInner)
}
fn date_from_iso(&self, iso: Date<Iso>) -> Self::DateInner {
let fixed_iso = Iso::fixed_from_iso(*iso.inner());
Self::islamic_from_fixed(fixed_iso).inner
}
fn date_to_iso(&self, date: &Self::DateInner) -> Date<Iso> {
let fixed_islamic = Self::fixed_from_islamic(*date);
Iso::iso_from_fixed(fixed_islamic)
}
fn months_in_year(&self, date: &Self::DateInner) -> u8 {
date.0.months_in_year()
}
fn days_in_year(&self, date: &Self::DateInner) -> u16 {
date.0.days_in_year()
}
fn days_in_month(&self, date: &Self::DateInner) -> u8 {
date.0.days_in_month()
}
fn day_of_week(&self, date: &Self::DateInner) -> types::IsoWeekday {
Iso.day_of_week(self.date_to_iso(date).inner())
}
fn offset_date(&self, date: &mut Self::DateInner, offset: DateDuration<Self>) {
date.0.offset_date(offset, &())
}
fn until(
&self,
date1: &Self::DateInner,
date2: &Self::DateInner,
_calendar2: &Self,
_largest_unit: DateDurationUnit,
_smallest_unit: DateDurationUnit,
) -> DateDuration<Self> {
date1.0.until(date2.0, _largest_unit, _smallest_unit)
}
fn debug_name(&self) -> &'static str {
"Islamic (tabular)"
}
fn year(&self, date: &Self::DateInner) -> types::FormattableYear {
Self::year_as_islamic(date.0.year)
}
fn is_in_leap_year(&self, date: &Self::DateInner) -> bool {
Self::is_leap_year(date.0.year, ())
}
fn month(&self, date: &Self::DateInner) -> types::FormattableMonth {
date.0.month()
}
fn day_of_month(&self, date: &Self::DateInner) -> types::DayOfMonth {
date.0.day_of_month()
}
fn day_of_year_info(&self, date: &Self::DateInner) -> types::DayOfYearInfo {
let prev_year = date.0.year.saturating_sub(1);
let next_year = date.0.year.saturating_add(1);
types::DayOfYearInfo {
day_of_year: date.0.day_of_year(),
days_in_year: date.0.days_in_year(),
prev_year: Self::year_as_islamic(prev_year),
days_in_prev_year: Self::days_in_provided_year(prev_year, ()),
next_year: Self::year_as_islamic(next_year),
}
}
fn any_calendar_kind(&self) -> Option<AnyCalendarKind> {
Some(AnyCalendarKind::IslamicTabular)
}
}
impl IslamicTabular {
fn fixed_from_islamic(i_date: IslamicTabularDateInner) -> RataDie {
calendrical_calculations::islamic::fixed_from_islamic_tabular(
i_date.0.year,
i_date.0.month,
i_date.0.day,
)
}
fn islamic_from_fixed(date: RataDie) -> Date<IslamicTabular> {
let (y, m, d) = calendrical_calculations::islamic::islamic_tabular_from_fixed(date);
debug_assert!(
Date::try_new_islamic_civil_date_with_calendar(y, m, d, IslamicCivil).is_ok()
);
Date::from_raw(
IslamicTabularDateInner(ArithmeticDate::new_unchecked(y, m, d)),
IslamicTabular,
)
}
fn year_as_islamic(year: i32) -> types::FormattableYear {
types::FormattableYear {
era: types::Era(tinystr!(16, "islamic")),
number: year,
cyclic: None,
related_iso: None,
}
}
}
impl<A: AsCalendar<Calendar = IslamicTabular>> Date<A> {
/// Construct new Tabular Islamic Date.
///
/// Has no negative years, only era is the AH.
///
/// ```rust
/// use icu::calendar::islamic::IslamicTabular;
/// use icu::calendar::Date;
///
/// let islamic = IslamicTabular::new_always_calculating();
///
/// let date_islamic =
/// Date::try_new_islamic_tabular_date_with_calendar(1392, 4, 25, islamic)
/// .expect("Failed to initialize Islamic Date instance.");
///
/// assert_eq!(date_islamic.year().number, 1392);
/// assert_eq!(date_islamic.month().ordinal, 4);
/// assert_eq!(date_islamic.day_of_month().0, 25);
/// ```
pub fn try_new_islamic_tabular_date_with_calendar(
year: i32,
month: u8,
day: u8,
calendar: A,
) -> Result<Date<A>, CalendarError> {
ArithmeticDate::new_from_ordinals(year, month, day)
.map(IslamicTabularDateInner)
.map(|inner| Date::from_raw(inner, calendar))
}
}
impl<A: AsCalendar<Calendar = IslamicTabular>> DateTime<A> {
/// Construct a new Tabular Islamic datetime from integers.
///
/// ```rust
/// use icu::calendar::islamic::IslamicTabular;
/// use icu::calendar::DateTime;
///
/// let islamic = IslamicTabular::new_always_calculating();
///
/// let datetime_islamic =
/// DateTime::try_new_islamic_tabular_datetime_with_calendar(
/// 474, 10, 11, 13, 1, 0, islamic,
/// )
/// .expect("Failed to initialize Islamic DateTime instance.");
///
/// assert_eq!(datetime_islamic.date.year().number, 474);
/// assert_eq!(datetime_islamic.date.month().ordinal, 10);
/// assert_eq!(datetime_islamic.date.day_of_month().0, 11);
/// assert_eq!(datetime_islamic.time.hour.number(), 13);
/// assert_eq!(datetime_islamic.time.minute.number(), 1);
/// assert_eq!(datetime_islamic.time.second.number(), 0);
/// ```
pub fn try_new_islamic_tabular_datetime_with_calendar(
year: i32,
month: u8,
day: u8,
hour: u8,
minute: u8,
second: u8,
calendar: A,
) -> Result<DateTime<A>, CalendarError> {
Ok(DateTime {
date: Date::try_new_islamic_tabular_date_with_calendar(year, month, day, calendar)?,
time: Time::try_new(hour, minute, second, 0)?,
})
}
}
#[cfg(test)]
mod test {
use super::*;
use crate::Ref;
const START_YEAR: i32 = -1245;
const END_YEAR: i32 = 1518;
#[derive(Debug)]
struct DateCase {
year: i32,
month: u8,
day: u8,
}
static TEST_FIXED_DATE: [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,
];
// Removed: 601716 and 727274 fixed dates
static TEST_FIXED_DATE_UMMALQURA: [i64; 31] = [
-214193, -61387, 25469, 49217, 171307, 210155, 253427, 369740, 400085, 434355, 452605,
470160, 473837, 507850, 524156, 544676, 567118, 569477, 613424, 626596, 645554, 664224,
671401, 694799, 704424, 708842, 709409, 709580, 728714, 744313, 764652,
];
static UMMALQURA_DATE_EXPECTED: [DateCase; 31] = [
DateCase {
year: -1245,
month: 12,
day: 11,
},
DateCase {
year: -813,
month: 2,
day: 26,
},
DateCase {
year: -568,
month: 4,
day: 3,
},
DateCase {
year: -501,
month: 4,
day: 8,
},
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: 22,
},
DateCase {
year: 697,
month: 7,
day: 8,
},
DateCase {
year: 793,
month: 7,
day: 1,
},
DateCase {
year: 839,
month: 7,
day: 7,
},
DateCase {
year: 897,
month: 6,
day: 3,
},
DateCase {
year: 960,
month: 10,
day: 1,
},
DateCase {
year: 967,
month: 5,
day: 28,
},
DateCase {
year: 1091,
month: 6,
day: 4,
},
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: 11,
},
DateCase {
year: 1321,
month: 1,
day: 21,
},
DateCase {
year: 1348,
month: 3,
day: 20,
},
DateCase {
year: 1360,
month: 9,
day: 8,
},
DateCase {
year: 1362,
month: 4,
day: 14,
},
DateCase {
year: 1362,
month: 10,
day: 8,
},
DateCase {
year: 1416,
month: 10,
day: 6,
},
DateCase {
year: 1460,
month: 10,
day: 13,
},
DateCase {
year: 1518,
month: 3,
day: 6,
},
];
static OBSERVATIONAL_CASES: [DateCase; 33] = [
DateCase {
year: -1245,
month: 12,
day: 11,
},
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: 30,
},
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: 14,
},
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 TABULAR_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_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));
assert_eq!(iso.to_calendar(calendar).inner, date.inner, "{case:?}");
}
}
#[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));
assert_eq!(iso.to_calendar(calendar).inner, date.inner, "{case:?}");
}
}
#[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));
assert_eq!(iso.to_calendar(calendar).inner, date.inner, "{case:?}");
}
}
#[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));
assert_eq!(iso.to_calendar(calendar).inner, date.inner, "{case:?}");
}
}
#[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
assert_eq!(sum_days_in_year, expected_number_of_days);
}
#[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());
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() {
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);
}
}