diff --git a/components/calendar/src/any_calendar.rs b/components/calendar/src/any_calendar.rs index 409226af7f4..ba82b46f677 100644 --- a/components/calendar/src/any_calendar.rs +++ b/components/calendar/src/any_calendar.rs @@ -587,7 +587,7 @@ impl AnyCalendar { AnyCalendarKind::Indian => AnyCalendar::Indian(Indian), AnyCalendarKind::IslamicCivil => AnyCalendar::IslamicCivil(IslamicCivil), AnyCalendarKind::IslamicObservational => { - AnyCalendar::IslamicObservational(IslamicObservational) + AnyCalendar::IslamicObservational(IslamicObservational::new()) } AnyCalendarKind::IslamicTabular => AnyCalendar::IslamicTabular(IslamicTabular), AnyCalendarKind::IslamicUmmAlQura => AnyCalendar::IslamicUmmAlQura(IslamicUmmAlQura), @@ -628,9 +628,9 @@ impl AnyCalendar { AnyCalendarKind::Hebrew => AnyCalendar::Hebrew(Hebrew), AnyCalendarKind::Indian => AnyCalendar::Indian(Indian), AnyCalendarKind::IslamicCivil => AnyCalendar::IslamicCivil(IslamicCivil), - AnyCalendarKind::IslamicObservational => { - AnyCalendar::IslamicObservational(IslamicObservational) - } + AnyCalendarKind::IslamicObservational => AnyCalendar::IslamicObservational( + IslamicObservational::try_new_with_any_provider(provider)?, + ), AnyCalendarKind::IslamicTabular => AnyCalendar::IslamicTabular(IslamicTabular), AnyCalendarKind::IslamicUmmAlQura => AnyCalendar::IslamicUmmAlQura(IslamicUmmAlQura), AnyCalendarKind::Iso => AnyCalendar::Iso(Iso), @@ -673,9 +673,9 @@ impl AnyCalendar { AnyCalendarKind::Hebrew => AnyCalendar::Hebrew(Hebrew), AnyCalendarKind::Indian => AnyCalendar::Indian(Indian), AnyCalendarKind::IslamicCivil => AnyCalendar::IslamicCivil(IslamicCivil), - AnyCalendarKind::IslamicObservational => { - AnyCalendar::IslamicObservational(IslamicObservational) - } + AnyCalendarKind::IslamicObservational => AnyCalendar::IslamicObservational( + IslamicObservational::try_new_with_buffer_provider(provider)?, + ), AnyCalendarKind::IslamicTabular => AnyCalendar::IslamicTabular(IslamicTabular), AnyCalendarKind::IslamicUmmAlQura => AnyCalendar::IslamicUmmAlQura(IslamicUmmAlQura), AnyCalendarKind::Iso => AnyCalendar::Iso(Iso), @@ -697,6 +697,7 @@ impl AnyCalendar { + DataProvider + DataProvider + DataProvider + + DataProvider + ?Sized, { Ok(match kind { @@ -715,7 +716,7 @@ impl AnyCalendar { AnyCalendarKind::Indian => AnyCalendar::Indian(Indian), AnyCalendarKind::IslamicCivil => AnyCalendar::IslamicCivil(IslamicCivil), AnyCalendarKind::IslamicObservational => { - AnyCalendar::IslamicObservational(IslamicObservational) + AnyCalendar::IslamicObservational(IslamicObservational::try_new_unstable(provider)?) } AnyCalendarKind::IslamicTabular => AnyCalendar::IslamicTabular(IslamicTabular), AnyCalendarKind::IslamicUmmAlQura => AnyCalendar::IslamicUmmAlQura(IslamicUmmAlQura), @@ -769,6 +770,7 @@ impl AnyCalendar { + DataProvider + DataProvider + DataProvider + + DataProvider + ?Sized, { let kind = AnyCalendarKind::from_data_locale_with_fallback(locale); @@ -1038,7 +1040,7 @@ impl AnyCalendarKind { AnyCalendarKind::Hebrew => Hebrew.debug_name(), AnyCalendarKind::Indian => Indian.debug_name(), AnyCalendarKind::IslamicCivil => IslamicCivil.debug_name(), - AnyCalendarKind::IslamicObservational => IslamicObservational.debug_name(), + AnyCalendarKind::IslamicObservational => IslamicObservational::DEBUG_NAME, AnyCalendarKind::IslamicTabular => IslamicTabular.debug_name(), AnyCalendarKind::IslamicUmmAlQura => IslamicUmmAlQura.debug_name(), AnyCalendarKind::Iso => Iso.debug_name(), @@ -1231,10 +1233,10 @@ impl IntoAnyCalendar for IslamicCivil { impl IntoAnyCalendar for IslamicObservational { fn to_any(self) -> AnyCalendar { - AnyCalendar::IslamicObservational(IslamicObservational) + AnyCalendar::IslamicObservational(self) } fn to_any_cloned(&self) -> AnyCalendar { - AnyCalendar::IslamicObservational(IslamicObservational) + AnyCalendar::IslamicObservational(self.clone()) } fn date_to_any(&self, d: &Self::DateInner) -> AnyDateInner { AnyDateInner::IslamicObservational(*d) diff --git a/components/calendar/src/date.rs b/components/calendar/src/date.rs index 88353471983..17e3a2a5de1 100644 --- a/components/calendar/src/date.rs +++ b/components/calendar/src/date.rs @@ -322,6 +322,11 @@ impl Date { pub fn calendar_wrapper(&self) -> &A { &self.calendar } + + #[cfg(test)] + pub(crate) fn to_fixed(&self) -> calendrical_calculations::rata_die::RataDie { + Iso::fixed_from_iso(self.to_iso().inner) + } } impl> Date { diff --git a/components/calendar/src/islamic.rs b/components/calendar/src/islamic.rs index f8ef088130d..0be68694b71 100644 --- a/components/calendar/src/islamic.rs +++ b/components/calendar/src/islamic.rs @@ -36,12 +36,19 @@ //! 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, 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}; use calendrical_calculations::rata_die::RataDie; +use core::marker::PhantomData; +use icu_provider::prelude::*; use tinystr::tinystr; /// Islamic Observational Calendar (Default) @@ -54,9 +61,10 @@ use tinystr::tinystr; /// /// This calendar is a pure lunar calendar with no leap months. It uses month codes /// `"M01" - "M12"`. -#[derive(Clone, Debug, Hash, Eq, PartialEq, PartialOrd, Ord)] -#[non_exhaustive] // we'll be adding precompiled data to this -pub struct IslamicObservational; +#[derive(Clone, Debug, Default)] +pub struct IslamicObservational { + data: Option>, +} /// Civil / Arithmetical Islamic Calendar (Used for administrative purposes) /// @@ -101,12 +109,42 @@ pub struct IslamicUmmAlQura; pub struct IslamicTabular; impl IslamicObservational { - /// Construct a new [`IslamicObservational`] without any precomputed calendrical calculations. + /// Creates a new [`IslamicObservational`] with some precomputed calendrical calculations. /// - /// This is the only mode currently possible, but once precomputing is available (#3933) - /// there will be additional constructors that load from data providers. + /// ✨ *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 + ?Sized>( + provider: &D, + ) -> Result { + 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 { - IslamicObservational + Self { data: None } } } @@ -140,35 +178,165 @@ impl IslamicTabular { } } +#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)] +pub(crate) struct IslamicYearInfo { + packed_data: PackedIslamicYearInfo, + /// Is the previous year 355 days (short = 354) + prev_year_long: bool, +} + +impl IslamicYearInfo { + pub(crate) const LONG_YEAR_LEN: u16 = 355; + const SHORT_YEAR_LEN: u16 = 354; + pub(crate) fn new(prev_year_long: bool, packed_data: PackedIslamicYearInfo) -> Self { + Self { + prev_year_long, + packed_data, + } + } + + fn compute(extended_year: i32) -> Self { + let ny = IB::fixed_from_islamic(extended_year, 1, 1); + let packed_data = PackedIslamicYearInfo::compute_with_ny::(extended_year, ny); + let prev_ny = IB::fixed_from_islamic(extended_year - 1, 1, 1); + let diff = u16::try_from(ny - prev_ny).unwrap_or(0); + debug_assert!( + diff == Self::SHORT_YEAR_LEN || diff == Self::LONG_YEAR_LEN, + "Found wrong year length for Islamic year {}: Expected 355 or 354, got {diff}", + extended_year - 1 + ); + Self::new(diff == Self::LONG_YEAR_LEN, packed_data) + } + /// Get the new year R.D. given the extended year that this yearinfo is for + fn new_year(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(self, extended_year: i32, month: u8, day: u8) -> RataDie { + let ny = self.new_year::(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 { + if self.prev_year_long { + Self::LONG_YEAR_LEN + } else { + Self::SHORT_YEAR_LEN + } + } +} + +/// 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, +} + +impl<'b, IB: IslamicBasedMarker> PrecomputedDataSource + 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::(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::(fixed)); + if let Some((cached, year)) = cached { + let ny = cached.packed_data.ny::(year); + let day_of_year = (fixed - ny) as u16 + 1; + debug_assert!(day_of_year < 360); + let possible_month = u8::try_from(1 + (day_of_year / 29)).unwrap_or(1); // + 1 because 1-indexed + 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::(y); + let ny = info.packed_data.ny::(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); impl CalendarArithmetic for IslamicObservational { - type YearInfo = (); + type YearInfo = IslamicYearInfo; - fn month_days(year: i32, month: u8, _data: ()) -> u8 { - calendrical_calculations::islamic::observational_islamic_month_days(year, month) + 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, _data: ()) -> u8 { + fn months_for_every_year(_year: i32, _year_info: IslamicYearInfo) -> u8 { 12 } - fn days_in_provided_year(year: i32, _data: ()) -> u16 { - (1..=12) - .map(|month| IslamicObservational::month_days(year, month, ()) as u16) - .sum() + fn days_in_provided_year(_year: i32, year_info: IslamicYearInfo) -> u16 { + year_info.packed_data.days_in_year() } - // As an observational-lunar calendar, it does not have leap years. - fn is_leap_year(_year: i32, _data: ()) -> bool { + // As an true lunar calendar, it does not have leap years. + fn is_leap_year(_year: i32, _year_info: IslamicYearInfo) -> bool { false } - fn last_month_day_in_year(year: i32, _data: ()) -> (u8, u8) { - let days = Self::month_days(year, 12, ()); + fn last_month_day_in_year(year: i32, year_info: IslamicYearInfo) -> (u8, u8) { + let days = Self::month_days(year, 12, year_info); (12, days) } @@ -188,18 +356,39 @@ impl Calendar for IslamicObservational { } else { return Err(CalendarError::UnknownEra(era.0, self.debug_name())); }; - - ArithmeticDate::new_from_codes(self, year, month_code, day).map(IslamicDateInner) + 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) -> Self::DateInner { let fixed_iso = Iso::fixed_from_iso(*iso.inner()); - Self::islamic_from_fixed(fixed_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 { - let fixed_islamic = Self::fixed_from_islamic(*date); - Iso::iso_from_fixed(fixed_islamic) + let fixed = date.0.year_info.rd_for::( + date.0.year, + date.0.month, + date.0.day, + ); + Iso::iso_from_fixed(fixed) } fn months_in_year(&self, date: &Self::DateInner) -> u8 { @@ -219,7 +408,7 @@ impl Calendar for IslamicObservational { } fn offset_date(&self, date: &mut Self::DateInner, offset: DateDuration) { - date.0.offset_date(offset, &()) + date.0.offset_date(offset, &self.precomputed_data()) } fn until( @@ -234,7 +423,7 @@ impl Calendar for IslamicObservational { } fn debug_name(&self) -> &'static str { - "Islamic (observational)" + Self::DEBUG_NAME } fn year(&self, date: &Self::DateInner) -> types::FormattableYear { @@ -242,7 +431,7 @@ impl Calendar for IslamicObservational { } fn is_in_leap_year(&self, date: &Self::DateInner) -> bool { - Self::is_leap_year(date.0.year, ()) + Self::is_leap_year(date.0.year, date.0.year_info) } fn month(&self, date: &Self::DateInner) -> types::FormattableMonth { @@ -260,7 +449,7 @@ impl Calendar for IslamicObservational { 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, ()), + days_in_prev_year: date.0.year_info.days_in_prev_year(), next_year: Self::year_as_islamic(next_year), } } @@ -271,28 +460,8 @@ impl Calendar for IslamicObservational { } impl IslamicObservational { - fn fixed_from_islamic(i_date: IslamicDateInner) -> RataDie { - calendrical_calculations::islamic::fixed_from_islamic_observational( - i_date.0.year, - i_date.0.month, - i_date.0.day, - ) - } - - fn islamic_from_fixed(date: RataDie) -> Date { - let (y, m, d) = calendrical_calculations::islamic::observational_islamic_from_fixed(date); - - debug_assert!(Date::try_new_observational_islamic_date( - y, - m, - d, - IslamicObservational::new_always_calculating() - ) - .is_ok()); - Date::from_raw( - IslamicDateInner(ArithmeticDate::new_unchecked(y, m, d)), - IslamicObservational, - ) + fn precomputed_data(&self) -> IslamicPrecomputedData { + IslamicPrecomputedData::new(self.data.as_ref().map(|x| x.get())) } fn year_as_islamic(year: i32) -> types::FormattableYear { @@ -303,6 +472,7 @@ impl IslamicObservational { related_iso: None, } } + pub(crate) const DEBUG_NAME: &'static str = "Islamic (observational)"; } impl> Date { @@ -330,7 +500,11 @@ impl> Date { day: u8, calendar: A, ) -> Result, CalendarError> { - ArithmeticDate::new_from_ordinals(year, month, day) + 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)) } @@ -1099,6 +1273,7 @@ impl> DateTime { #[cfg(test)] mod test { use super::*; + use crate::Ref; const START_YEAR: i32 = -1245; const END_YEAR: i32 = 1518; @@ -1786,33 +1961,27 @@ mod test { #[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, - IslamicObservational::new_always_calculating(), - ) - .unwrap(); - assert_eq!( - IslamicObservational::islamic_from_fixed(RataDie::new(*f_date)), - date, - "{case:?}" - ); + 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 = IslamicDateInner(ArithmeticDate::new_unchecked( - case.year, case.month, case.day, - )); - assert_eq!( - IslamicObservational::fixed_from_islamic(date), - RataDie::new(*f_date), - "{case:?}" - ); + 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:?}"); } } @@ -1921,16 +2090,25 @@ mod test { #[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, ()) as i64) + .map(|year| { + IslamicObservational::days_in_provided_year( + year, + IslamicYearInfo::compute::(year), + ) as i64 + }) .sum(); - let expected_number_of_days = IslamicObservational::fixed_from_islamic(IslamicDateInner( - ArithmeticDate::new_from_ordinals(END_YEAR, 1, 1).unwrap(), - )) - IslamicObservational::fixed_from_islamic( - IslamicDateInner(ArithmeticDate::new_from_ordinals(START_YEAR, 1, 1).unwrap()), - ); // The number of days between Islamic years -1245 and 1518 + 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!( diff --git a/components/calendar/src/provider.rs b/components/calendar/src/provider.rs index 95df06e2337..a9a7381b9db 100644 --- a/components/calendar/src/provider.rs +++ b/components/calendar/src/provider.rs @@ -16,7 +16,9 @@ #![allow(clippy::exhaustive_structs, clippy::exhaustive_enums)] pub mod chinese_based; +pub mod islamic; pub use chinese_based::{ChineseCacheV1Marker, DangiCacheV1Marker}; +pub use islamic::IslamicObservationalCacheV1Marker; use crate::types::IsoWeekday; use core::str::FromStr; @@ -45,6 +47,7 @@ const _: () = { icu_calendar_data::make_provider!(Baked); icu_calendar_data::impl_calendar_chinesecache_v1!(Baked); icu_calendar_data::impl_calendar_dangicache_v1!(Baked); + icu_calendar_data::impl_calendar_islamicobservationalcache_v1!(Baked); icu_calendar_data::impl_calendar_japanese_v1!(Baked); icu_calendar_data::impl_calendar_japanext_v1!(Baked); icu_calendar_data::impl_datetime_week_data_v1!(Baked); @@ -56,6 +59,7 @@ const _: () = { pub const KEYS: &[DataKey] = &[ ChineseCacheV1Marker::KEY, DangiCacheV1Marker::KEY, + IslamicObservationalCacheV1Marker::KEY, JapaneseErasV1Marker::KEY, JapaneseExtendedErasV1Marker::KEY, WeekDataV2Marker::KEY, diff --git a/components/calendar/src/provider/islamic.rs b/components/calendar/src/provider/islamic.rs new file mode 100644 index 00000000000..4d56ee57e85 --- /dev/null +++ b/components/calendar/src/provider/islamic.rs @@ -0,0 +1,296 @@ +// 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 +// (online at: https://github.com/unicode-org/icu4x/blob/main/LICENSE ). + +//! 🚧 \[Unstable\] Data provider struct definitions for chinese-based calendars. +//! +//!
+//! 🚧 This code is considered unstable; it may change at any time, in breaking or non-breaking ways, +//! including in SemVer minor releases. While the serde representation of data structs is guaranteed +//! to be stable, their Rust representation might not be. Use with caution. +//!
+//! +//! Read more about data providers: [`icu_provider`] + +use crate::islamic::IslamicYearInfo; +use calendrical_calculations::islamic::IslamicBasedMarker; +use calendrical_calculations::rata_die::RataDie; +use icu_provider::prelude::*; +use zerovec::ule::{AsULE, ULE}; +use zerovec::ZeroVec; + +/// Cached/precompiled data for a certain range of years for a chinese-based +/// calendar. Avoids the need to perform lunar calendar arithmetic for most calendrical +/// operations. +#[icu_provider::data_struct(marker( + IslamicObservationalCacheV1Marker, + "calendar/islamicobservationalcache@1", + singleton +))] +#[derive(Debug, PartialEq, Clone, Default)] +#[cfg_attr( + feature = "datagen", + derive(serde::Serialize, databake::Bake), + databake(path = icu_calendar::provider::islamic), +)] +#[cfg_attr(feature = "serde", derive(serde::Deserialize))] +pub struct IslamicCacheV1<'data> { + /// The extended year corresponding to the first data entry for this year + pub first_extended_year: i32, + /// A list of precomputed data for each year beginning with first_extended_year + #[cfg_attr(feature = "serde", serde(borrow))] + pub data: ZeroVec<'data, PackedIslamicYearInfo>, +} + +impl<'data> IslamicCacheV1<'data> { + /// Compute this data for a range of years + #[cfg(feature = "datagen")] + pub fn compute_for(extended_years: core::ops::Range) -> Self { + let data = extended_years + .clone() + .map(|year| PackedIslamicYearInfo::compute::(year)) + .collect(); + IslamicCacheV1 { + first_extended_year: extended_years.start, + data, + } + } + + /// Get the cached data for a given extended year + pub(crate) fn get_for_extended_year(&self, extended_year: i32) -> Option { + let delta = extended_year - self.first_extended_year; + let delta = usize::try_from(delta).ok()?; + + if delta == 0 { + return None; + } + + let (Some(this_packed), Some(prev_packed)) = + (self.data.get(delta), self.data.get(delta - 1)) + else { + return None; + }; + + let days_in_prev_year = prev_packed.days_in_year(); + + Some(IslamicYearInfo::new( + days_in_prev_year == IslamicYearInfo::LONG_YEAR_LEN, + this_packed, + )) + } + /// Get the cached data for the Islamic Year corresponding to a given day. + /// + /// Also returns the corresponding extended year. + pub(crate) fn get_for_fixed( + &self, + fixed: RataDie, + ) -> Option<(IslamicYearInfo, i32)> { + let extended_year = IB::approximate_islamic_from_fixed(fixed); + + let delta = extended_year - self.first_extended_year; + let delta = usize::try_from(delta).ok()?; + + if delta <= 1 { + return None; + } + + let this_packed = self.data.get(delta)?; + let prev_packed = self.data.get(delta + 1)?; + + let this_ny = this_packed.ny::(extended_year); + + if fixed < this_ny { + let prev2_packed = self.data.get(delta - 2)?; + return Some(( + IslamicYearInfo::new( + prev2_packed.days_in_year() == IslamicYearInfo::LONG_YEAR_LEN, + prev_packed, + ), + extended_year - 1, + )); + } + let next_packed = self.data.get(delta + 1)?; + let next_ny = next_packed.ny::(extended_year + 1); + + if fixed >= next_ny { + Some(( + IslamicYearInfo::new( + this_packed.days_in_year() == IslamicYearInfo::LONG_YEAR_LEN, + next_packed, + ), + extended_year + 1, + )) + } else { + Some(( + IslamicYearInfo::new( + prev_packed.days_in_year() == IslamicYearInfo::LONG_YEAR_LEN, + this_packed, + ), + extended_year, + )) + } + } +} + +/// The struct containing compiled Islamic YearInfo +/// +/// Bit structure (little endian: note that shifts go in the opposite direction!) +/// +/// ```text +/// Bit: 0 1 2 3 4 5 6 7 +/// Byte 0: [ month lengths ............. +/// Byte 1: .. months ] | [ ny offset ] +/// ``` +/// +/// Where the New Year Offset is a signed offset from `epoch + MEAN_SYNODIC_MONTH * year * 12` for the given +/// calendar. This number does not appear to be less than 2, however we use all remaining bits for it in case of drift +/// in the math. +/// The month lengths are stored as 1 = 30, 0 = 29 for each month including the leap month. +/// +///
+/// 🚧 This code is considered unstable; it may change at any time, in breaking or non-breaking ways, +/// including in SemVer minor releases. While the serde representation of data structs is guaranteed +/// to be stable, their Rust representation might not be. Use with caution. +///
+#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq, PartialOrd, Ord, ULE)] +#[cfg_attr( + feature = "datagen", + derive(serde::Serialize, databake::Bake), + databake(path = icu_calendar::provider), +)] +#[cfg_attr(feature = "serde", derive(serde::Deserialize))] +#[repr(packed)] +pub struct PackedIslamicYearInfo(pub u8, pub u8); + +impl PackedIslamicYearInfo { + pub(crate) fn new(month_lengths: [bool; 12], ny_offset: i8) -> Self { + debug_assert!( + -8 < ny_offset && ny_offset < 8, + "Year offset too big to store" + ); + + let mut all = 0u16; // last byte unused + + for (month, length_30) in month_lengths.iter().enumerate() { + #[allow(clippy::indexing_slicing)] + if *length_30 { + all |= 1 << month as u16; + } + } + + if ny_offset < 0 { + all |= 1 << 12; + } + all |= u16::from(ny_offset.unsigned_abs()) << 13; + let le = all.to_le_bytes(); + Self(le[0], le[1]) + } + + // Get the new year offset from the mean synodic new year + pub(crate) fn ny_offset(self) -> i8 { + let masked = (self.1 >> 5) as i8; + if (self.1 & 0b10000) != 0 { + -masked + } else { + masked + } + } + // Get the new year offset from the mean synodic new year + pub(crate) fn ny(self, extended_year: i32) -> RataDie { + let mean_synodic_ny = IB::mean_synodic_ny(extended_year); + mean_synodic_ny + i64::from(self.ny_offset()) + } + + // Whether a particular month has 30 days (month is 1-indexed) + pub(crate) fn month_has_30_days(self, month: u8) -> bool { + let months = u16::from_le_bytes([self.0, self.1]); + months & (1 << (month - 1) as u16) != 0 + } + + /// The number of days in a given 1-indexed month + pub(crate) fn days_in_month(self, month: u8) -> u8 { + if self.month_has_30_days(month) { + 30 + } else { + 29 + } + } + + // Which day of year is the last day of a month (month is 1-indexed) + pub(crate) fn last_day_of_month(self, month: u8) -> u16 { + let months = u16::from_le_bytes([self.0, self.1]); + // month is 1-indexed, so `29 * month` includes the current month + let mut prev_month_lengths = 29 * month as u16; + // month is 1-indexed, so `1 << month` is a mask with all zeroes except + // for a 1 at the bit index at the next month. Subtracting 1 from it gets us + // a bitmask for all months up to now + let long_month_bits = months & ((1 << month as u16) - 1); + prev_month_lengths += long_month_bits.count_ones().try_into().unwrap_or(0); + prev_month_lengths + } + + pub(crate) fn days_in_year(self) -> u16 { + self.last_day_of_month(12) + } + + pub(crate) fn compute_with_ny(extended_year: i32, ny: RataDie) -> Self { + let month_lengths = IB::month_lengths_for_year(extended_year, ny); + let ny_offset = ny - IB::mean_synodic_ny(extended_year); + let ny_offset = if !(-7..=7).contains(&ny_offset) { + 0 + } else { + ny_offset as i8 + }; + Self::new(month_lengths, ny_offset) + } + #[cfg(feature = "datagen")] + pub(crate) fn compute(extended_year: i32) -> Self { + let ny = IB::fixed_from_islamic(extended_year, 1, 1); + Self::compute_with_ny::(extended_year, ny) + } +} + +impl AsULE for PackedIslamicYearInfo { + type ULE = Self; + fn to_unaligned(self) -> Self { + self + } + fn from_unaligned(other: Self) -> Self { + other + } +} + +#[cfg(test)] +mod tests { + use super::*; + + fn single_roundtrip(month_lengths: [bool; 12], ny_offset: i8) { + let packed = PackedIslamicYearInfo::new(month_lengths, ny_offset); + for i in 0..12 { + assert_eq!(packed.month_has_30_days(i + 1), month_lengths[i as usize], "Month lengths must match for testcase {month_lengths:?} / {ny_offset}, with packed repr: {packed:?}"); + } + assert_eq!(packed.ny_offset(), ny_offset, "Month lengths must match for testcase {month_lengths:?} / {ny_offset}, with packed repr: {packed:?}"); + } + const ALL_FALSE: [bool; 12] = [false; 12]; + const ALL_TRUE: [bool; 12] = [true; 12]; + const MIXED1: [bool; 12] = [ + true, false, true, false, true, false, true, false, true, false, true, false, + ]; + const MIXED2: [bool; 12] = [ + false, false, true, true, true, false, true, false, false, false, true, true, + ]; + #[test] + fn test_islamic_packed_roundtrip() { + single_roundtrip(ALL_FALSE, 0); + single_roundtrip(ALL_TRUE, 0); + single_roundtrip(MIXED1, 0); + single_roundtrip(MIXED2, 0); + + single_roundtrip(MIXED1, -7); + single_roundtrip(MIXED2, 7); + single_roundtrip(MIXED2, 4); + single_roundtrip(MIXED2, 1); + single_roundtrip(MIXED2, -1); + single_roundtrip(MIXED2, -4); + } +} diff --git a/components/datetime/src/any/date.rs b/components/datetime/src/any/date.rs index 5410f96a3e6..080e89f1c87 100644 --- a/components/datetime/src/any/date.rs +++ b/components/datetime/src/any/date.rs @@ -9,8 +9,8 @@ use crate::{input::DateInput, DateTimeError, FormattedDateTime}; use alloc::string::String; use icu_calendar::any_calendar::AnyCalendar; use icu_calendar::provider::{ - ChineseCacheV1Marker, DangiCacheV1Marker, JapaneseErasV1Marker, JapaneseExtendedErasV1Marker, - WeekDataV1Marker, + ChineseCacheV1Marker, DangiCacheV1Marker, IslamicObservationalCacheV1Marker, + JapaneseErasV1Marker, JapaneseExtendedErasV1Marker, WeekDataV1Marker, }; use icu_decimal::provider::DecimalSymbolsV1Marker; use icu_plurals::provider::OrdinalV1Marker; @@ -187,6 +187,7 @@ impl DateFormatter { + DataProvider + DataProvider + DataProvider + + DataProvider + DataProvider + DataProvider + DataProvider diff --git a/components/datetime/src/any/datetime.rs b/components/datetime/src/any/datetime.rs index 523fb73ce7f..56871f0611f 100644 --- a/components/datetime/src/any/datetime.rs +++ b/components/datetime/src/any/datetime.rs @@ -9,8 +9,8 @@ use crate::{input::DateTimeInput, DateTimeError, FormattedDateTime}; use alloc::string::String; use icu_calendar::any_calendar::AnyCalendar; use icu_calendar::provider::{ - ChineseCacheV1Marker, DangiCacheV1Marker, JapaneseErasV1Marker, JapaneseExtendedErasV1Marker, - WeekDataV1Marker, + ChineseCacheV1Marker, DangiCacheV1Marker, IslamicObservationalCacheV1Marker, + JapaneseErasV1Marker, JapaneseExtendedErasV1Marker, WeekDataV1Marker, }; use icu_decimal::provider::DecimalSymbolsV1Marker; use icu_plurals::provider::OrdinalV1Marker; @@ -241,6 +241,7 @@ impl DateTimeFormatter { + DataProvider + DataProvider + DataProvider + + DataProvider + DataProvider + DataProvider + DataProvider @@ -388,6 +389,7 @@ impl DateTimeFormatter { + DataProvider + DataProvider + DataProvider + + DataProvider + DataProvider + DataProvider + DataProvider diff --git a/components/datetime/src/any/zoned_datetime.rs b/components/datetime/src/any/zoned_datetime.rs index 9cd81782e72..00639a83972 100644 --- a/components/datetime/src/any/zoned_datetime.rs +++ b/components/datetime/src/any/zoned_datetime.rs @@ -14,8 +14,8 @@ use crate::time_zone::TimeZoneFormatterOptions; use crate::{DateTimeError, FormattedZonedDateTime}; use icu_calendar::any_calendar::{AnyCalendar, AnyCalendarKind}; use icu_calendar::provider::{ - ChineseCacheV1Marker, DangiCacheV1Marker, JapaneseErasV1Marker, JapaneseExtendedErasV1Marker, - WeekDataV1Marker, + ChineseCacheV1Marker, DangiCacheV1Marker, IslamicObservationalCacheV1Marker, + JapaneseErasV1Marker, JapaneseExtendedErasV1Marker, WeekDataV1Marker, }; use icu_calendar::{DateTime, Time}; use icu_decimal::provider::DecimalSymbolsV1Marker; @@ -257,6 +257,7 @@ impl ZonedDateTimeFormatter { + DataProvider + DataProvider + DataProvider + + DataProvider + DataProvider + DataProvider + DataProvider @@ -411,6 +412,7 @@ impl ZonedDateTimeFormatter { + DataProvider + DataProvider + DataProvider + + DataProvider + DataProvider + DataProvider + DataProvider diff --git a/components/datetime/src/external_loaders.rs b/components/datetime/src/external_loaders.rs index 24538cd2332..8678b67b212 100644 --- a/components/datetime/src/external_loaders.rs +++ b/components/datetime/src/external_loaders.rs @@ -207,6 +207,7 @@ where + DataProvider + DataProvider + DataProvider + + DataProvider + ?Sized, { #[inline] diff --git a/components/datetime/src/neo.rs b/components/datetime/src/neo.rs index b764f42ae8c..6d0b6c19881 100644 --- a/components/datetime/src/neo.rs +++ b/components/datetime/src/neo.rs @@ -18,8 +18,8 @@ use crate::Error; use core::fmt; use core::marker::PhantomData; use icu_calendar::provider::{ - ChineseCacheV1Marker, DangiCacheV1Marker, JapaneseErasV1Marker, JapaneseExtendedErasV1Marker, - WeekDataV2Marker, + ChineseCacheV1Marker, DangiCacheV1Marker, IslamicObservationalCacheV1Marker, + JapaneseErasV1Marker, JapaneseExtendedErasV1Marker, WeekDataV2Marker, }; use icu_calendar::AnyCalendar; use icu_decimal::provider::DecimalSymbolsV1Marker; @@ -351,6 +351,7 @@ impl NeoDateFormatter { // AnyCalendar constructor keys + DataProvider + DataProvider + + DataProvider + DataProvider + DataProvider // FixedDecimalFormatter keys @@ -1206,6 +1207,7 @@ impl NeoDateTimeFormatter { + DataProvider + DataProvider + DataProvider + + DataProvider + DataProvider + DataProvider + DataProvider @@ -1378,6 +1380,7 @@ impl NeoDateTimeFormatter { // AnyCalendar constructor keys + DataProvider + DataProvider + + DataProvider + DataProvider + DataProvider // FixedDecimalFormatter keys @@ -1505,6 +1508,7 @@ impl NeoDateTimeFormatter { + DataProvider + DataProvider + DataProvider + + DataProvider + DataProvider + DataProvider + DataProvider diff --git a/provider/baked/calendar/data/macros.rs b/provider/baked/calendar/data/macros.rs index f698f60f3ca..6f8a9fb0e9b 100644 --- a/provider/baked/calendar/data/macros.rs +++ b/provider/baked/calendar/data/macros.rs @@ -36,6 +36,11 @@ mod calendar_dangicache_v1; #[doc(inline)] pub use __impl_calendar_dangicache_v1 as impl_calendar_dangicache_v1; #[macro_use] +#[path = "macros/calendar_islamicobservationalcache_v1.rs.data"] +mod calendar_islamicobservationalcache_v1; +#[doc(inline)] +pub use __impl_calendar_islamicobservationalcache_v1 as impl_calendar_islamicobservationalcache_v1; +#[macro_use] #[path = "macros/calendar_japanese_v1.rs.data"] mod calendar_japanese_v1; #[doc(inline)] diff --git a/provider/baked/calendar/data/macros/calendar_islamicobservationalcache_v1.rs.data b/provider/baked/calendar/data/macros/calendar_islamicobservationalcache_v1.rs.data new file mode 100644 index 00000000000..50a1ef053d8 --- /dev/null +++ b/provider/baked/calendar/data/macros/calendar_islamicobservationalcache_v1.rs.data @@ -0,0 +1,27 @@ +// @generated +/// Implement `DataProvider` on the given struct using the data +/// hardcoded in this file. This allows the struct to be used with +/// `icu`'s `_unstable` constructors. +#[doc(hidden)] +#[macro_export] +macro_rules! __impl_calendar_islamicobservationalcache_v1 { + ($ provider : ty) => { + #[clippy::msrv = "1.67"] + const _: () = <$provider>::MUST_USE_MAKE_PROVIDER_MACRO; + #[clippy::msrv = "1.67"] + impl $provider { + #[doc(hidden)] + pub const SINGLETON_CALENDAR_ISLAMICOBSERVATIONALCACHE_V1: &'static ::Yokeable = &icu::calendar::provider::islamic::IslamicCacheV1 { first_extended_year: 1317i32, data: unsafe { zerovec::ZeroVec::from_bytes_unchecked(b"/9\xAE\x02\xAD\x05\xAA\r\x92-%-\x8D\n-\x05m\x05j\x0BT'I\x0F\x92.&+V\x05\xAE\n\xAC#i\x07T/\xA4.L-\x9C*\\\x05\xBA\x06\xB4%\xA9\rT-\xAA*6\tv\x02u5\xDA\n\xB4&\x99\x06+\x05;\n\x9E\x04^\n\\\x05Y\x0B*+U\n+\n[\x02;5\xD9\n\xD2&\xA5\x0EJ.\x96\x0C-\x05m\nj\rX'I/\x92.)-U*\xB5\x0C\xB4%\xB1\r\xA4+I+\x95\n\xB5\x04m\t\xEC\x02\xE9\x06\xD2\x0Ed-\xAA,V\tv\x04\xF5\x04\xEA\n\xD4*i*S\tk\x04\xBB8v\x02u\t\xAA\x05S\r&-N\n\xAE\x04m\t\xEA\x02\xD5\n\xA5\x06K\x06\xA5\x0C+\t\xAB\x02k\x05\xA5\x0BQ+\xA3\nG\x05\xA7\x02W5\xE6\n\xD4%\xC9\r\x92-\xA5*U\x05\xAD\x01\xEB2\xE9\t\xD2%\xA5\x05\xCB\x02W\t\xB6\x02v\t\xF4\x02i\x0BJ+\x96\n.\x05^\x02\xDD4\xDA\n\xD4%\xA5\x05K\x05\x8F:\xAE\x04^\tZ\x03M\x0B&'\x8B\x06\x17\x05O\n\xAE\x04\xAD\x06\xAA\r\x94-I-\x95\n-\x05\xAD\nj\x03U\x07J\x0F\x94+&+V\x05\xAE\nt#j\x07T')/T-\xAC*\\\x05\xBA\x02\xB9\x05\xB2\rT-*+V\t\xAE\x02]\x05\xBA\n\xB4&\x99\x06+\x06[\x0C\x1E\t^\x02]\x05Y\r2-\x15\r+\n\x9B\x04[9\xDA\n\xD4&\xA9\x0EJ.\x95,-\x05\xAD\nj\rh'Q/\xA2.I-U*\xD5\x04\xB5\t\xB2\r\xA8+Q+\xA5\nU\x05m\n\xEC\x04\xE9\x06\xD2-d-\xAA,V\nv\x04\xF5\x08\xEA\x02\xE5\n\xAA*U\t\xAB\x04W\x01\xB74\xB5\t\xAA\x05\xA5\r*-V\n\xAE\x08k\t\xEA\x04\xD3\n\xA5\x06S\x0E#-K\n\xAB\x02\xAB\x05\xA5\x0B\xA2'E'\x8B\x06\xAB\x04k9\xEA\x02\xD5\x05\xC9\r\xA2-\xC5*U\t\xAD\x01\xED\x02\xEA\t\xD4#\xA9\x03K\x03") } }; + } + #[clippy::msrv = "1.67"] + impl icu_provider::DataProvider for $provider { + fn load(&self, req: icu_provider::DataRequest) -> Result, icu_provider::DataError> { + if req.locale.is_empty() { + Ok(icu_provider::DataResponse { payload: Some(icu_provider::DataPayload::from_static_ref(Self::SINGLETON_CALENDAR_ISLAMICOBSERVATIONALCACHE_V1)), metadata: Default::default() }) + } else { + Err(icu_provider::DataErrorKind::ExtraneousLocale.with_req(::KEY, req)) + } + } + } + }; +} diff --git a/provider/datagen/src/registry.rs b/provider/datagen/src/registry.rs index 7fa0dd34745..423634f9618 100644 --- a/provider/datagen/src/registry.rs +++ b/provider/datagen/src/registry.rs @@ -141,6 +141,8 @@ registry!( icu_calendar::provider::ChineseCacheV1Marker = "calendar/chinesecache@1", icu_calendar::provider::DangiCacheV1Marker = "calendar/dangicache@1", icu_calendar::provider::JapaneseErasV1Marker = "calendar/japanese@1", + icu_calendar::provider::IslamicObservationalCacheV1Marker = + "calendar/islamicobservationalcache@1", icu_calendar::provider::JapaneseExtendedErasV1Marker = "calendar/japanext@1", icu_calendar::provider::WeekDataV1Marker = "datetime/week_data@1", icu_calendar::provider::WeekDataV2Marker = "datetime/week_data@2", diff --git a/provider/datagen/src/transform/cldr/calendar/islamic.rs b/provider/datagen/src/transform/cldr/calendar/islamic.rs new file mode 100644 index 00000000000..2cb825490aa --- /dev/null +++ b/provider/datagen/src/transform/cldr/calendar/islamic.rs @@ -0,0 +1,38 @@ +// 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 +// (online at: https://github.com/unicode-org/icu4x/blob/main/LICENSE ). + +use calendrical_calculations::islamic::{IslamicBasedMarker, ObservationalIslamicMarker}; +use calendrical_calculations::iso; +use icu_calendar::provider::islamic::*; +use icu_provider::datagen::IterableDataProvider; +use icu_provider::prelude::*; + +const YEARS: i32 = 250; +const ISO_START: i32 = 1900; + +fn load() -> IslamicCacheV1<'static> { + let extended_start = IB::approximate_islamic_from_fixed(iso::fixed_from_iso(ISO_START, 1, 1)); + let extended_end = extended_start + YEARS; + IslamicCacheV1::compute_for::(extended_start..extended_end) +} + +impl DataProvider for crate::DatagenProvider { + fn load( + &self, + req: DataRequest, + ) -> Result, DataError> { + self.check_req::(req)?; + let cache = load::(); + Ok(DataResponse { + metadata: DataResponseMetadata::default(), + payload: Some(DataPayload::from_owned(cache)), + }) + } +} + +impl IterableDataProvider for crate::DatagenProvider { + fn supported_locales(&self) -> Result, DataError> { + Ok(vec![Default::default()]) + } +} diff --git a/provider/datagen/src/transform/cldr/calendar/mod.rs b/provider/datagen/src/transform/cldr/calendar/mod.rs index 2a6a632762f..6c06d350057 100644 --- a/provider/datagen/src/transform/cldr/calendar/mod.rs +++ b/provider/datagen/src/transform/cldr/calendar/mod.rs @@ -7,3 +7,6 @@ pub mod japanese; /// Cached data for chinese-based calendars pub mod chinese_based; + +/// Cached data for islamic calendars +pub mod islamic; diff --git a/provider/datagen/tests/data/baked/macros.rs b/provider/datagen/tests/data/baked/macros.rs index 080920f0766..3aa3135d5f9 100644 --- a/provider/datagen/tests/data/baked/macros.rs +++ b/provider/datagen/tests/data/baked/macros.rs @@ -36,6 +36,11 @@ mod calendar_dangicache_v1; #[doc(inline)] pub use __impl_calendar_dangicache_v1 as impl_calendar_dangicache_v1; #[macro_use] +#[path = "macros/calendar_islamicobservationalcache_v1.rs.data"] +mod calendar_islamicobservationalcache_v1; +#[doc(inline)] +pub use __impl_calendar_islamicobservationalcache_v1 as impl_calendar_islamicobservationalcache_v1; +#[macro_use] #[path = "macros/calendar_japanese_v1.rs.data"] mod calendar_japanese_v1; #[doc(inline)] diff --git a/provider/datagen/tests/data/baked/macros/calendar_islamicobservationalcache_v1.rs.data b/provider/datagen/tests/data/baked/macros/calendar_islamicobservationalcache_v1.rs.data new file mode 100644 index 00000000000..50a1ef053d8 --- /dev/null +++ b/provider/datagen/tests/data/baked/macros/calendar_islamicobservationalcache_v1.rs.data @@ -0,0 +1,27 @@ +// @generated +/// Implement `DataProvider` on the given struct using the data +/// hardcoded in this file. This allows the struct to be used with +/// `icu`'s `_unstable` constructors. +#[doc(hidden)] +#[macro_export] +macro_rules! __impl_calendar_islamicobservationalcache_v1 { + ($ provider : ty) => { + #[clippy::msrv = "1.67"] + const _: () = <$provider>::MUST_USE_MAKE_PROVIDER_MACRO; + #[clippy::msrv = "1.67"] + impl $provider { + #[doc(hidden)] + pub const SINGLETON_CALENDAR_ISLAMICOBSERVATIONALCACHE_V1: &'static ::Yokeable = &icu::calendar::provider::islamic::IslamicCacheV1 { first_extended_year: 1317i32, data: unsafe { zerovec::ZeroVec::from_bytes_unchecked(b"/9\xAE\x02\xAD\x05\xAA\r\x92-%-\x8D\n-\x05m\x05j\x0BT'I\x0F\x92.&+V\x05\xAE\n\xAC#i\x07T/\xA4.L-\x9C*\\\x05\xBA\x06\xB4%\xA9\rT-\xAA*6\tv\x02u5\xDA\n\xB4&\x99\x06+\x05;\n\x9E\x04^\n\\\x05Y\x0B*+U\n+\n[\x02;5\xD9\n\xD2&\xA5\x0EJ.\x96\x0C-\x05m\nj\rX'I/\x92.)-U*\xB5\x0C\xB4%\xB1\r\xA4+I+\x95\n\xB5\x04m\t\xEC\x02\xE9\x06\xD2\x0Ed-\xAA,V\tv\x04\xF5\x04\xEA\n\xD4*i*S\tk\x04\xBB8v\x02u\t\xAA\x05S\r&-N\n\xAE\x04m\t\xEA\x02\xD5\n\xA5\x06K\x06\xA5\x0C+\t\xAB\x02k\x05\xA5\x0BQ+\xA3\nG\x05\xA7\x02W5\xE6\n\xD4%\xC9\r\x92-\xA5*U\x05\xAD\x01\xEB2\xE9\t\xD2%\xA5\x05\xCB\x02W\t\xB6\x02v\t\xF4\x02i\x0BJ+\x96\n.\x05^\x02\xDD4\xDA\n\xD4%\xA5\x05K\x05\x8F:\xAE\x04^\tZ\x03M\x0B&'\x8B\x06\x17\x05O\n\xAE\x04\xAD\x06\xAA\r\x94-I-\x95\n-\x05\xAD\nj\x03U\x07J\x0F\x94+&+V\x05\xAE\nt#j\x07T')/T-\xAC*\\\x05\xBA\x02\xB9\x05\xB2\rT-*+V\t\xAE\x02]\x05\xBA\n\xB4&\x99\x06+\x06[\x0C\x1E\t^\x02]\x05Y\r2-\x15\r+\n\x9B\x04[9\xDA\n\xD4&\xA9\x0EJ.\x95,-\x05\xAD\nj\rh'Q/\xA2.I-U*\xD5\x04\xB5\t\xB2\r\xA8+Q+\xA5\nU\x05m\n\xEC\x04\xE9\x06\xD2-d-\xAA,V\nv\x04\xF5\x08\xEA\x02\xE5\n\xAA*U\t\xAB\x04W\x01\xB74\xB5\t\xAA\x05\xA5\r*-V\n\xAE\x08k\t\xEA\x04\xD3\n\xA5\x06S\x0E#-K\n\xAB\x02\xAB\x05\xA5\x0B\xA2'E'\x8B\x06\xAB\x04k9\xEA\x02\xD5\x05\xC9\r\xA2-\xC5*U\t\xAD\x01\xED\x02\xEA\t\xD4#\xA9\x03K\x03") } }; + } + #[clippy::msrv = "1.67"] + impl icu_provider::DataProvider for $provider { + fn load(&self, req: icu_provider::DataRequest) -> Result, icu_provider::DataError> { + if req.locale.is_empty() { + Ok(icu_provider::DataResponse { payload: Some(icu_provider::DataPayload::from_static_ref(Self::SINGLETON_CALENDAR_ISLAMICOBSERVATIONALCACHE_V1)), metadata: Default::default() }) + } else { + Err(icu_provider::DataErrorKind::ExtraneousLocale.with_req(::KEY, req)) + } + } + } + }; +} diff --git a/provider/datagen/tests/data/baked/mod.rs b/provider/datagen/tests/data/baked/mod.rs index be0986cc29d..2d02d8e7a15 100644 --- a/provider/datagen/tests/data/baked/mod.rs +++ b/provider/datagen/tests/data/baked/mod.rs @@ -5,6 +5,7 @@ macro_rules! impl_data_provider { make_provider!($provider); impl_calendar_chinesecache_v1!($provider); impl_calendar_dangicache_v1!($provider); + impl_calendar_islamicobservationalcache_v1!($provider); impl_calendar_japanese_v1!($provider); impl_calendar_japanext_v1!($provider); impl_collator_data_v1!($provider); @@ -294,6 +295,7 @@ macro_rules! impl_any_provider { match key.hashed() { h if h == ::KEY.hashed() => icu_provider::DataProvider::::load(self, req).map(icu_provider::DataResponse::wrap_into_any_response), h if h == ::KEY.hashed() => icu_provider::DataProvider::::load(self, req).map(icu_provider::DataResponse::wrap_into_any_response), + h if h == ::KEY.hashed() => icu_provider::DataProvider::::load(self, req).map(icu_provider::DataResponse::wrap_into_any_response), h if h == ::KEY.hashed() => icu_provider::DataProvider::::load(self, req).map(icu_provider::DataResponse::wrap_into_any_response), h if h == ::KEY.hashed() => icu_provider::DataProvider::::load(self, req).map(icu_provider::DataResponse::wrap_into_any_response), h if h == ::KEY.hashed() => icu_provider::DataProvider::::load(self, req).map(icu_provider::DataResponse::wrap_into_any_response), diff --git a/provider/datagen/tests/data/json/calendar/islamicobservationalcache@1/und.json b/provider/datagen/tests/data/json/calendar/islamicobservationalcache@1/und.json new file mode 100644 index 00000000000..111d8e815a3 --- /dev/null +++ b/provider/datagen/tests/data/json/calendar/islamicobservationalcache@1/und.json @@ -0,0 +1,1005 @@ +{ + "first_extended_year": 1317, + "data": [ + [ + 47, + 57 + ], + [ + 174, + 2 + ], + [ + 173, + 5 + ], + [ + 170, + 13 + ], + [ + 146, + 45 + ], + [ + 37, + 45 + ], + [ + 141, + 10 + ], + [ + 45, + 5 + ], + [ + 109, + 5 + ], + [ + 106, + 11 + ], + [ + 84, + 39 + ], + [ + 73, + 15 + ], + [ + 146, + 46 + ], + [ + 38, + 43 + ], + [ + 86, + 5 + ], + [ + 174, + 10 + ], + [ + 172, + 35 + ], + [ + 105, + 7 + ], + [ + 84, + 47 + ], + [ + 164, + 46 + ], + [ + 76, + 45 + ], + [ + 156, + 42 + ], + [ + 92, + 5 + ], + [ + 186, + 6 + ], + [ + 180, + 37 + ], + [ + 169, + 13 + ], + [ + 84, + 45 + ], + [ + 170, + 42 + ], + [ + 54, + 9 + ], + [ + 118, + 2 + ], + [ + 117, + 53 + ], + [ + 218, + 10 + ], + [ + 180, + 38 + ], + [ + 153, + 6 + ], + [ + 43, + 5 + ], + [ + 59, + 10 + ], + [ + 158, + 4 + ], + [ + 94, + 10 + ], + [ + 92, + 5 + ], + [ + 89, + 11 + ], + [ + 42, + 43 + ], + [ + 85, + 10 + ], + [ + 43, + 10 + ], + [ + 91, + 2 + ], + [ + 59, + 53 + ], + [ + 217, + 10 + ], + [ + 210, + 38 + ], + [ + 165, + 14 + ], + [ + 74, + 46 + ], + [ + 150, + 12 + ], + [ + 45, + 5 + ], + [ + 109, + 10 + ], + [ + 106, + 13 + ], + [ + 88, + 39 + ], + [ + 73, + 47 + ], + [ + 146, + 46 + ], + [ + 41, + 45 + ], + [ + 85, + 42 + ], + [ + 181, + 12 + ], + [ + 180, + 37 + ], + [ + 177, + 13 + ], + [ + 164, + 43 + ], + [ + 73, + 43 + ], + [ + 149, + 10 + ], + [ + 181, + 4 + ], + [ + 109, + 9 + ], + [ + 236, + 2 + ], + [ + 233, + 6 + ], + [ + 210, + 14 + ], + [ + 100, + 45 + ], + [ + 170, + 44 + ], + [ + 86, + 9 + ], + [ + 118, + 4 + ], + [ + 245, + 4 + ], + [ + 234, + 10 + ], + [ + 212, + 42 + ], + [ + 105, + 42 + ], + [ + 83, + 9 + ], + [ + 107, + 4 + ], + [ + 187, + 56 + ], + [ + 118, + 2 + ], + [ + 117, + 9 + ], + [ + 170, + 5 + ], + [ + 83, + 13 + ], + [ + 38, + 45 + ], + [ + 78, + 10 + ], + [ + 174, + 4 + ], + [ + 109, + 9 + ], + [ + 234, + 2 + ], + [ + 213, + 10 + ], + [ + 165, + 6 + ], + [ + 75, + 6 + ], + [ + 165, + 12 + ], + [ + 43, + 9 + ], + [ + 171, + 2 + ], + [ + 107, + 5 + ], + [ + 165, + 11 + ], + [ + 81, + 43 + ], + [ + 163, + 10 + ], + [ + 71, + 5 + ], + [ + 167, + 2 + ], + [ + 87, + 53 + ], + [ + 230, + 10 + ], + [ + 212, + 37 + ], + [ + 201, + 13 + ], + [ + 146, + 45 + ], + [ + 165, + 42 + ], + [ + 85, + 5 + ], + [ + 173, + 1 + ], + [ + 235, + 50 + ], + [ + 233, + 9 + ], + [ + 210, + 37 + ], + [ + 165, + 5 + ], + [ + 203, + 2 + ], + [ + 87, + 9 + ], + [ + 182, + 2 + ], + [ + 118, + 9 + ], + [ + 244, + 2 + ], + [ + 105, + 11 + ], + [ + 74, + 43 + ], + [ + 150, + 10 + ], + [ + 46, + 5 + ], + [ + 94, + 2 + ], + [ + 221, + 52 + ], + [ + 218, + 10 + ], + [ + 212, + 37 + ], + [ + 165, + 5 + ], + [ + 75, + 5 + ], + [ + 143, + 58 + ], + [ + 174, + 4 + ], + [ + 94, + 9 + ], + [ + 90, + 3 + ], + [ + 77, + 11 + ], + [ + 38, + 39 + ], + [ + 139, + 6 + ], + [ + 23, + 5 + ], + [ + 79, + 10 + ], + [ + 174, + 4 + ], + [ + 173, + 6 + ], + [ + 170, + 13 + ], + [ + 148, + 45 + ], + [ + 73, + 45 + ], + [ + 149, + 10 + ], + [ + 45, + 5 + ], + [ + 173, + 10 + ], + [ + 106, + 3 + ], + [ + 85, + 7 + ], + [ + 74, + 15 + ], + [ + 148, + 43 + ], + [ + 38, + 43 + ], + [ + 86, + 5 + ], + [ + 174, + 10 + ], + [ + 116, + 35 + ], + [ + 106, + 7 + ], + [ + 84, + 39 + ], + [ + 41, + 47 + ], + [ + 84, + 45 + ], + [ + 172, + 42 + ], + [ + 92, + 5 + ], + [ + 186, + 2 + ], + [ + 185, + 5 + ], + [ + 178, + 13 + ], + [ + 84, + 45 + ], + [ + 42, + 43 + ], + [ + 86, + 9 + ], + [ + 174, + 2 + ], + [ + 93, + 5 + ], + [ + 186, + 10 + ], + [ + 180, + 38 + ], + [ + 153, + 6 + ], + [ + 43, + 6 + ], + [ + 91, + 12 + ], + [ + 30, + 9 + ], + [ + 94, + 2 + ], + [ + 93, + 5 + ], + [ + 89, + 13 + ], + [ + 50, + 45 + ], + [ + 21, + 13 + ], + [ + 43, + 10 + ], + [ + 155, + 4 + ], + [ + 91, + 57 + ], + [ + 218, + 10 + ], + [ + 212, + 38 + ], + [ + 169, + 14 + ], + [ + 74, + 46 + ], + [ + 149, + 44 + ], + [ + 45, + 5 + ], + [ + 173, + 10 + ], + [ + 106, + 13 + ], + [ + 104, + 39 + ], + [ + 81, + 47 + ], + [ + 162, + 46 + ], + [ + 73, + 45 + ], + [ + 85, + 42 + ], + [ + 213, + 4 + ], + [ + 181, + 9 + ], + [ + 178, + 13 + ], + [ + 168, + 43 + ], + [ + 81, + 43 + ], + [ + 165, + 10 + ], + [ + 85, + 5 + ], + [ + 109, + 10 + ], + [ + 236, + 4 + ], + [ + 233, + 6 + ], + [ + 210, + 45 + ], + [ + 100, + 45 + ], + [ + 170, + 44 + ], + [ + 86, + 10 + ], + [ + 118, + 4 + ], + [ + 245, + 8 + ], + [ + 234, + 2 + ], + [ + 229, + 10 + ], + [ + 170, + 42 + ], + [ + 85, + 9 + ], + [ + 171, + 4 + ], + [ + 87, + 1 + ], + [ + 183, + 52 + ], + [ + 181, + 9 + ], + [ + 170, + 5 + ], + [ + 165, + 13 + ], + [ + 42, + 45 + ], + [ + 86, + 10 + ], + [ + 174, + 8 + ], + [ + 107, + 9 + ], + [ + 234, + 4 + ], + [ + 211, + 10 + ], + [ + 165, + 6 + ], + [ + 83, + 14 + ], + [ + 35, + 45 + ], + [ + 75, + 10 + ], + [ + 171, + 2 + ], + [ + 171, + 5 + ], + [ + 165, + 11 + ], + [ + 162, + 39 + ], + [ + 69, + 39 + ], + [ + 139, + 6 + ], + [ + 171, + 4 + ], + [ + 107, + 57 + ], + [ + 234, + 2 + ], + [ + 213, + 5 + ], + [ + 201, + 13 + ], + [ + 162, + 45 + ], + [ + 197, + 42 + ], + [ + 85, + 9 + ], + [ + 173, + 1 + ], + [ + 237, + 2 + ], + [ + 234, + 9 + ], + [ + 212, + 35 + ], + [ + 169, + 3 + ], + [ + 75, + 3 + ] + ] +} diff --git a/provider/datagen/tests/data/postcard/fingerprints.csv b/provider/datagen/tests/data/postcard/fingerprints.csv index a94b2d522f5..6dfcdddba69 100644 --- a/provider/datagen/tests/data/postcard/fingerprints.csv +++ b/provider/datagen/tests/data/postcard/fingerprints.csv @@ -1,5 +1,6 @@ calendar/chinesecache@1, und, 754B, c116ab2a7479b26d calendar/dangicache@1, und, 754B, d7565838cc8c6aa6 +calendar/islamicobservationalcache@1, und, 504B, 31c8968bfd922ede calendar/japanese@1, und, 111B, b31e52deaf52706f calendar/japanext@1, und, 5216B, 6c20e216c8cd6e41 collator/data@1, ar, 8267B, fce742b37324adbe diff --git a/utils/calendrical_calculations/src/astronomy.rs b/utils/calendrical_calculations/src/astronomy.rs index 4852f43f892..828dbcbcc28 100644 --- a/utils/calendrical_calculations/src/astronomy.rs +++ b/utils/calendrical_calculations/src/astronomy.rs @@ -38,10 +38,14 @@ fn div_euclid_f64(n: f64, d: f64) -> f64 { /// elevation in meters, and zone as a UTC offset in fractional days (ex. UTC+1 would have zone = 1.0 / 24.0) #[allow(clippy::exhaustive_structs)] // This is all that is needed by the book algorithms pub struct Location { - pub latitude: f64, // latitude from -90 to 90 - pub longitude: f64, // longitude from -180 to 180 - pub elevation: f64, // elevation in meters - pub zone: f64, // UTC timezone offset in fractional days (1 hr = 1.0 / 24.0 day) + /// latitude from -90 to 90 + pub latitude: f64, + /// longitude from -180 to 180 + pub longitude: f64, + /// elevation in meters + pub elevation: f64, + /// UTC timezone offset in fractional days (1 hr = 1.0 / 24.0 day) + pub zone: f64, } /// The location of Mecca; used for Islamic calendar calculations. @@ -1186,7 +1190,7 @@ impl Astronomical { lunar_phase: Option, ) -> RataDie { let lunar_phase = - lunar_phase.unwrap_or_else(|| Self::calculate_lunar_phase_at_or_before(date)); + lunar_phase.unwrap_or_else(|| Self::calculate_new_moon_at_or_before(date)); let age = date.to_f64_date() - lunar_phase; let tau = if age <= 4.0 || Self::visible_crescent((date - 1).as_moment(), location) { lunar_phase + 29.0 // Next new moon @@ -1207,7 +1211,7 @@ impl Astronomical { lunar_phase: Option, ) -> RataDie { let lunar_phase = - lunar_phase.unwrap_or_else(|| Self::calculate_lunar_phase_at_or_before(date)); + lunar_phase.unwrap_or_else(|| Self::calculate_new_moon_at_or_before(date)); let age = date.to_f64_date() - lunar_phase; let tau = if age <= 3.0 && !Self::visible_crescent((date).as_moment(), location) { lunar_phase - 30.0 // Previous new moon @@ -1217,7 +1221,8 @@ impl Astronomical { next_moment(Moment::new(tau), location, Self::visible_crescent) } - pub fn calculate_lunar_phase_at_or_before(date: RataDie) -> f64 { + /// Calculate the day that the new moon occurred on or before the given date. + pub fn calculate_new_moon_at_or_before(date: RataDie) -> f64 { Self::lunar_phase_at_or_before(0.0, date.as_moment()) .inner() .floor() diff --git a/utils/calendrical_calculations/src/islamic.rs b/utils/calendrical_calculations/src/islamic.rs index ec17353170d..332d6b68299 100644 --- a/utils/calendrical_calculations/src/islamic.rs +++ b/utils/calendrical_calculations/src/islamic.rs @@ -17,6 +17,120 @@ const CAIRO: Location = Location { zone: (1_f64 / 12_f64), }; +/// Common abstraction over islamic-style calendars +pub trait IslamicBasedMarker { + /// The epoch of the calendar. Different calendars use a different epoch (Thu or Fri) due to disagreement on the exact date of Mohammed's migration to Mecca. + const EPOCH: RataDie; + /// Given the extended year, calculate the approximate new year using the mean synodic month + fn mean_synodic_ny(extended_year: i32) -> RataDie { + Self::EPOCH + (f64::from((extended_year - 1) * 12) * MEAN_SYNODIC_MONTH).floor() as i64 + } + /// Given an iso date, calculate the *approximate* islamic year it corresponds to (for quick cache lookup) + fn approximate_islamic_from_fixed(date: RataDie) -> i32 { + let diff = date - Self::EPOCH; + let months = diff as f64 / MEAN_SYNODIC_MONTH; + let years = months / 12.; + (years + 1.).floor() as i32 + } + /// Convert an islamic date in this calendar to a R.D. + fn fixed_from_islamic(year: i32, month: u8, day: u8) -> RataDie; + /// Convert an R.D. To an islamic date in this calendar + fn islamic_from_fixed(date: RataDie) -> (i32, u8, u8); + + /// Given an extended year, calculate whether each month is 29 or 30 days long + fn month_lengths_for_year(extended_year: i32, ny: RataDie) -> [bool; 12] { + let mut prev_rd = ny; + let mut lengths = [false; 12]; + + for month_idx in 0..11 { + let new_rd = Self::fixed_from_islamic(extended_year, month_idx + 2, 1); + let diff = new_rd - prev_rd; + debug_assert!( + // TODO: year 1409 has a 31-length month 1 + diff == 29 || diff == 30 || diff == 31, + "Found extended year {extended_year} with month length {diff} for month {}", + month_idx + 1 + ); + if new_rd - prev_rd >= 30 { + if let Some(l) = lengths.get_mut(usize::from(month_idx)) { + *l = true; + } + } + prev_rd = new_rd + } + let new_rd = Self::fixed_from_islamic(extended_year + 1, 1, 1); + let diff = new_rd - prev_rd; + debug_assert!( + diff == 29 || diff == 30, + "Found extended year {extended_year} with month length {diff} for month 12" + ); + if new_rd - prev_rd == 30 { + lengths[11] = true; + } + lengths + } +} + +/// Marker type for observational islamic calendar, for use with [`IslamicBasedMarker`] +#[derive(Clone, Copy, Debug, Hash, Eq, PartialEq)] +#[allow(clippy::exhaustive_structs)] // marker +pub struct ObservationalIslamicMarker; + +/// Marker type for Saudi islamic calendar, for use with [`IslamicBasedMarker`] +#[derive(Clone, Copy, Debug, Hash, Eq, PartialEq)] +#[allow(clippy::exhaustive_structs)] // marker +pub struct SaudiIslamicMarker; + +/// Marker type for civil islamic calendar, for use with [`IslamicBasedMarker`] +#[derive(Clone, Copy, Debug, Hash, Eq, PartialEq)] +#[allow(clippy::exhaustive_structs)] // marker +pub struct CivilIslamicMarker; + +/// Marker type for observational islamic calendar, for use with [`IslamicBasedMarker`] +#[derive(Clone, Copy, Debug, Hash, Eq, PartialEq)] +#[allow(clippy::exhaustive_structs)] // marker +pub struct TabularIslamicMarker; + +impl IslamicBasedMarker for ObservationalIslamicMarker { + const EPOCH: RataDie = FIXED_ISLAMIC_EPOCH_FRIDAY; + fn fixed_from_islamic(year: i32, month: u8, day: u8) -> RataDie { + fixed_from_islamic_observational(year, month, day) + } + fn islamic_from_fixed(date: RataDie) -> (i32, u8, u8) { + observational_islamic_from_fixed(date) + } +} + +impl IslamicBasedMarker for SaudiIslamicMarker { + const EPOCH: RataDie = FIXED_ISLAMIC_EPOCH_FRIDAY; + fn fixed_from_islamic(year: i32, month: u8, day: u8) -> RataDie { + fixed_from_saudi_islamic(year, month, day) + } + fn islamic_from_fixed(date: RataDie) -> (i32, u8, u8) { + saudi_islamic_from_fixed(date) + } +} + +impl IslamicBasedMarker for CivilIslamicMarker { + const EPOCH: RataDie = FIXED_ISLAMIC_EPOCH_FRIDAY; + fn fixed_from_islamic(year: i32, month: u8, day: u8) -> RataDie { + fixed_from_islamic_civil(year, month, day) + } + fn islamic_from_fixed(date: RataDie) -> (i32, u8, u8) { + islamic_civil_from_fixed(date) + } +} + +impl IslamicBasedMarker for TabularIslamicMarker { + const EPOCH: RataDie = FIXED_ISLAMIC_EPOCH_THURSDAY; + fn fixed_from_islamic(year: i32, month: u8, day: u8) -> RataDie { + fixed_from_islamic_tabular(year, month, day) + } + fn islamic_from_fixed(date: RataDie) -> (i32, u8, u8) { + islamic_tabular_from_fixed(date) + } +} + /// Lisp code reference: pub fn fixed_from_islamic_observational(year: i32, month: u8, day: u8) -> RataDie { let year = i64::from(year); @@ -24,15 +138,14 @@ pub fn fixed_from_islamic_observational(year: i32, month: u8, day: u8) -> RataDi let day = i64::from(day); let midmonth = FIXED_ISLAMIC_EPOCH_FRIDAY.to_f64_date() + (((year - 1) as f64) * 12.0 + month as f64 - 0.5) * MEAN_SYNODIC_MONTH; - let lunar_phase = - Astronomical::calculate_lunar_phase_at_or_before(RataDie::new(midmonth as i64)); + let lunar_phase = Astronomical::calculate_new_moon_at_or_before(RataDie::new(midmonth as i64)); Astronomical::phasis_on_or_before(RataDie::new(midmonth as i64), CAIRO, Some(lunar_phase)) + day - 1 } /// Lisp code reference: pub fn observational_islamic_from_fixed(date: RataDie) -> (i32, u8, u8) { - let lunar_phase = Astronomical::calculate_lunar_phase_at_or_before(date); + let lunar_phase = Astronomical::calculate_new_moon_at_or_before(date); let crescent = Astronomical::phasis_on_or_before(date, CAIRO, Some(lunar_phase)); let elapsed_months = ((crescent - FIXED_ISLAMIC_EPOCH_FRIDAY) as f64 / MEAN_SYNODIC_MONTH).round() as i32; @@ -172,7 +285,7 @@ pub fn observational_islamic_month_days(year: i32, month: u8) -> u8 { + (((year - 1) as f64) * 12.0 + month as f64 - 0.5) * MEAN_SYNODIC_MONTH; let lunar_phase: f64 = - Astronomical::calculate_lunar_phase_at_or_before(RataDie::new(midmonth as i64)); + Astronomical::calculate_new_moon_at_or_before(RataDie::new(midmonth as i64)); let f_date = Astronomical::phasis_on_or_before(RataDie::new(midmonth as i64), CAIRO, Some(lunar_phase));