From 6a8dd11ae9e7fb8542970feabeb1819331f140b8 Mon Sep 17 00:00:00 2001 From: Robert Bastian Date: Tue, 14 Oct 2025 12:06:10 +0200 Subject: [PATCH 1/2] nuke calendar arithmetic --- .../calendar/src/cal/abstract_gregorian.rs | 63 +++----- components/calendar/src/cal/chinese.rs | 53 ++----- components/calendar/src/cal/coptic.rs | 60 +++----- components/calendar/src/cal/ethiopian.rs | 10 +- components/calendar/src/cal/hebrew.rs | 44 ++---- components/calendar/src/cal/hijri.rs | 45 +++--- components/calendar/src/cal/indian.rs | 93 +++++++----- components/calendar/src/cal/julian.rs | 62 ++++---- components/calendar/src/cal/persian.rs | 95 +++++------- .../calendar/src/calendar_arithmetic.rs | 140 ++---------------- components/calendar/src/date.rs | 8 +- 11 files changed, 237 insertions(+), 436 deletions(-) diff --git a/components/calendar/src/cal/abstract_gregorian.rs b/components/calendar/src/cal/abstract_gregorian.rs index 164b6e2ec20..3576db1c1c5 100644 --- a/components/calendar/src/cal/abstract_gregorian.rs +++ b/components/calendar/src/cal/abstract_gregorian.rs @@ -3,9 +3,7 @@ // (online at: https://github.com/unicode-org/icu4x/blob/main/LICENSE ). use crate::cal::iso::{IsoDateInner, IsoEra}; -use crate::calendar_arithmetic::{ - ArithmeticDate, ArithmeticDateBuilder, CalendarArithmetic, DateFieldsResolver, -}; +use crate::calendar_arithmetic::{ArithmeticDate, ArithmeticDateBuilder, DateFieldsResolver}; use crate::error::DateError; use crate::options::DateFromFieldsOptions; use crate::options::{DateAddOptions, DateDifferenceOptions}; @@ -43,7 +41,15 @@ impl ArithmeticDate> { } } -impl CalendarArithmetic for AbstractGregorian { +pub(crate) const REFERENCE_YEAR: i32 = 1972; +#[cfg(test)] +pub(crate) const LAST_DAY_OF_REFERENCE_YEAR: RataDie = + calendrical_calculations::gregorian::day_before_year(REFERENCE_YEAR + 1); + +impl DateFieldsResolver for AbstractGregorian { + // Gregorian year + type YearInfo = i32; + fn days_in_provided_month(year: i32, month: u8) -> u8 { // https://www.youtube.com/watch?v=J9KijLyP-yg&t=1394s if month == 2 { @@ -57,36 +63,6 @@ impl CalendarArithmetic for AbstractGregorian { 12 } - fn provided_year_is_leap(year: i32) -> bool { - calendrical_calculations::gregorian::is_leap_year(year) - } - - fn last_month_day_in_provided_year(_year: i32) -> (u8, u8) { - (12, 31) - } - - fn days_in_provided_year(year: i32) -> u16 { - 365 + calendrical_calculations::gregorian::is_leap_year(year) as u16 - } - - fn day_of_provided_year(year: Self::YearInfo, month: u8, day: u8) -> u16 { - calendrical_calculations::gregorian::days_before_month(year, month) + day as u16 - } - - fn date_from_provided_year_day(year: Self::YearInfo, year_day: u16) -> (u8, u8) { - calendrical_calculations::gregorian::year_day(year, year_day) - } -} - -pub(crate) const REFERENCE_YEAR: i32 = 1972; -#[cfg(test)] -pub(crate) const LAST_DAY_OF_REFERENCE_YEAR: RataDie = - calendrical_calculations::gregorian::day_before_year(REFERENCE_YEAR + 1); - -impl DateFieldsResolver for AbstractGregorian { - // Gregorian year - type YearInfo = i32; - #[inline] fn year_info_from_era(&self, era: &str, era_year: i32) -> Result { Ok(self.0.extended_from_era_year(Some(era), era_year)? + Y::EXTENDED_YEAR_OFFSET) @@ -126,8 +102,8 @@ impl Calendar for AbstractGregorian { fn from_rata_die(&self, date: RataDie) -> Self::DateInner { match calendrical_calculations::gregorian::gregorian_from_fixed(date) { - Err(I32CastError::BelowMin) => ArithmeticDate::min_date(), - Err(I32CastError::AboveMax) => ArithmeticDate::max_date(), + Err(I32CastError::BelowMin) => ArithmeticDate::new_unchecked(i32::MIN, 1, 1), + Err(I32CastError::AboveMax) => ArithmeticDate::new_unchecked(i32::MAX, 12, 31), Ok((year, month, day)) => ArithmeticDate::new_unchecked(year, month, day), } } @@ -145,15 +121,15 @@ impl Calendar for AbstractGregorian { } fn months_in_year(&self, date: &Self::DateInner) -> u8 { - date.months_in_year() + AbstractGregorian::::months_in_provided_year(date.year) } fn days_in_year(&self, date: &Self::DateInner) -> u16 { - date.days_in_year() + 365 + calendrical_calculations::gregorian::is_leap_year(date.year) as u16 } fn days_in_month(&self, date: &Self::DateInner) -> u8 { - date.days_in_month() + AbstractGregorian::::days_in_provided_month(date.year, date.month) } fn add( @@ -180,7 +156,7 @@ impl Calendar for AbstractGregorian { } fn is_in_leap_year(&self, date: &Self::DateInner) -> bool { - AbstractGregorian::::provided_year_is_leap(date.year) + calendrical_calculations::gregorian::is_leap_year(date.year) } fn month(&self, date: &Self::DateInner) -> types::MonthInfo { @@ -188,11 +164,14 @@ impl Calendar for AbstractGregorian { } fn day_of_month(&self, date: &Self::DateInner) -> types::DayOfMonth { - date.day_of_month() + types::DayOfMonth(date.day) } fn day_of_year(&self, date: &Self::DateInner) -> types::DayOfYear { - date.day_of_year() + types::DayOfYear( + calendrical_calculations::gregorian::days_before_month(date.year, date.month) + + date.day as u16, + ) } fn debug_name(&self) -> &'static str { diff --git a/components/calendar/src/cal/chinese.rs b/components/calendar/src/cal/chinese.rs index 2a4b78ce98f..1d791e51370 100644 --- a/components/calendar/src/cal/chinese.rs +++ b/components/calendar/src/cal/chinese.rs @@ -4,9 +4,7 @@ use crate::cal::iso::{Iso, IsoDateInner}; use crate::calendar_arithmetic::DateFieldsResolver; -use crate::calendar_arithmetic::{ - ArithmeticDate, ArithmeticDateBuilder, CalendarArithmetic, ToExtendedYear, -}; +use crate::calendar_arithmetic::{ArithmeticDate, ArithmeticDateBuilder, ToExtendedYear}; use crate::error::DateError; use crate::options::{DateAddOptions, DateDifferenceOptions}; use crate::options::{DateFromFieldsOptions, Overflow}; @@ -499,7 +497,9 @@ impl LunarChinese { } } -impl CalendarArithmetic for LunarChinese { +impl DateFieldsResolver for LunarChinese { + type YearInfo = LunarChineseYearData; + fn days_in_provided_month(year: LunarChineseYearData, month: u8) -> u8 { year.days_in_month(month) } @@ -509,31 +509,6 @@ impl CalendarArithmetic for LunarChinese { year.months_in_year() } - /// Returns true if the given year is a leap year, and false if not. - fn provided_year_is_leap(year: LunarChineseYearData) -> bool { - year.leap_month().is_some() - } - - /// Returns the (month, day) of the last day in a Chinese year (the day before Chinese New Year). - /// The last month in a year will always be 12 in a common year or 13 in a leap year. The day is - /// determined by finding the day immediately before the next new year and calculating the number - /// of days since the last new moon (beginning of the last month in the year). - fn last_month_day_in_provided_year(year: LunarChineseYearData) -> (u8, u8) { - if year.leap_month().is_some() { - (13, year.days_in_month(13)) - } else { - (12, year.days_in_month(12)) - } - } - - fn days_in_provided_year(year: LunarChineseYearData) -> u16 { - year.days_in_year() - } -} - -impl DateFieldsResolver for LunarChinese { - type YearInfo = LunarChineseYearData; - #[inline] fn year_info_from_era(&self, _era: &str, _era_year: i32) -> Result { // This calendar has no era codes @@ -602,13 +577,12 @@ impl Calendar for LunarChinese { fn from_rata_die(&self, rd: RataDie) -> Self::DateInner { let iso = Iso.from_rata_die(rd); let y = { - let actual_iso = iso.0.extended_year(); - let candidate = self.0.year_data(actual_iso); + let candidate = self.0.year_data(iso.0.year); if rd >= candidate.new_year() { candidate } else { - self.0.year_data(actual_iso - 1) + self.0.year_data(iso.0.year - 1) } }; let (m, d) = y.md_from_rd(rd); @@ -622,13 +596,12 @@ impl Calendar for LunarChinese { fn from_iso(&self, iso: IsoDateInner) -> Self::DateInner { let rd = Iso.to_rata_die(&iso); let y = { - let actual_iso = iso.0.extended_year(); - let candidate = self.0.year_data(actual_iso); + let candidate = self.0.year_data(iso.0.year); if rd >= candidate.new_year() { candidate } else { - self.0.year_data(actual_iso - 1) + self.0.year_data(iso.0.year - 1) } }; let (m, d) = y.md_from_rd(rd); @@ -642,11 +615,11 @@ impl Calendar for LunarChinese { // Count the number of months in a given year, specified by providing a date // from that year fn days_in_year(&self, date: &Self::DateInner) -> u16 { - date.0.days_in_year() + date.0.year.days_in_year() } fn days_in_month(&self, date: &Self::DateInner) -> u8 { - date.0.days_in_month() + Self::days_in_provided_month(date.0.year, date.0.month) } fn add( @@ -681,7 +654,7 @@ impl Calendar for LunarChinese { } fn is_in_leap_year(&self, date: &Self::DateInner) -> bool { - Self::provided_year_is_leap(date.0.year) + date.0.year.leap_month().is_some() } /// The calendar-specific month code represented by `date`; @@ -694,7 +667,7 @@ impl Calendar for LunarChinese { /// The calendar-specific day-of-month represented by `date` fn day_of_month(&self, date: &Self::DateInner) -> types::DayOfMonth { - date.0.day_of_month() + types::DayOfMonth(date.0.day) } /// Information of the day of the year @@ -707,7 +680,7 @@ impl Calendar for LunarChinese { } fn months_in_year(&self, date: &Self::DateInner) -> u8 { - date.0.months_in_year() + Self::months_in_provided_year(date.0.year) } } diff --git a/components/calendar/src/cal/coptic.rs b/components/calendar/src/cal/coptic.rs index 8d17f836001..899abf52caa 100644 --- a/components/calendar/src/cal/coptic.rs +++ b/components/calendar/src/cal/coptic.rs @@ -3,7 +3,7 @@ // (online at: https://github.com/unicode-org/icu4x/blob/main/LICENSE ). use crate::cal::iso::{Iso, IsoDateInner}; -use crate::calendar_arithmetic::{ArithmeticDate, CalendarArithmetic}; +use crate::calendar_arithmetic::ArithmeticDate; use crate::calendar_arithmetic::{ArithmeticDateBuilder, DateFieldsResolver}; use crate::error::DateError; use crate::options::DateFromFieldsOptions; @@ -39,12 +39,14 @@ pub struct Coptic; #[derive(Copy, Clone, Debug, Hash, Eq, PartialEq, PartialOrd, Ord)] pub struct CopticDateInner(pub(crate) ArithmeticDate); -impl CalendarArithmetic for Coptic { +impl DateFieldsResolver for Coptic { + type YearInfo = i32; + fn days_in_provided_month(year: i32, month: u8) -> u8 { if (1..=12).contains(&month) { 30 } else if month == 13 { - if Self::provided_year_is_leap(year) { + if year.rem_euclid(4) == 3 { 6 } else { 5 @@ -57,31 +59,6 @@ impl CalendarArithmetic for Coptic { fn months_in_provided_year(_: i32) -> u8 { 13 } - - fn provided_year_is_leap(year: i32) -> bool { - year.rem_euclid(4) == 3 - } - - fn last_month_day_in_provided_year(year: i32) -> (u8, u8) { - if Self::provided_year_is_leap(year) { - (13, 6) - } else { - (13, 5) - } - } - - fn days_in_provided_year(year: i32) -> u16 { - if Self::provided_year_is_leap(year) { - 366 - } else { - 365 - } - } -} - -impl DateFieldsResolver for Coptic { - type YearInfo = i32; - #[inline] fn year_info_from_era(&self, era: &str, era_year: i32) -> Result { match era { @@ -161,8 +138,8 @@ impl Calendar for Coptic { fn from_rata_die(&self, rd: RataDie) -> Self::DateInner { CopticDateInner( match calendrical_calculations::coptic::coptic_from_fixed(rd) { - Err(I32CastError::BelowMin) => ArithmeticDate::min_date(), - Err(I32CastError::AboveMax) => ArithmeticDate::max_date(), + Err(I32CastError::BelowMin) => ArithmeticDate::new_unchecked(i32::MIN, 1, 1), + Err(I32CastError::AboveMax) => ArithmeticDate::new_unchecked(i32::MAX, 13, 6), Ok((year, month, day)) => ArithmeticDate::new_unchecked(year, month, day), }, ) @@ -181,15 +158,19 @@ impl Calendar for Coptic { } fn months_in_year(&self, date: &Self::DateInner) -> u8 { - date.0.months_in_year() + Self::months_in_provided_year(date.0.year) } fn days_in_year(&self, date: &Self::DateInner) -> u16 { - date.0.days_in_year() + if self.is_in_leap_year(date) { + 366 + } else { + 365 + } } fn days_in_month(&self, date: &Self::DateInner) -> u8 { - date.0.days_in_month() + Self::days_in_provided_month(date.0.year, date.0.month) } fn add( @@ -211,7 +192,7 @@ impl Calendar for Coptic { } fn year_info(&self, date: &Self::DateInner) -> Self::Year { - let year = date.0.extended_year(); + let year = date.0.year; types::EraYear { era: tinystr!(16, "am"), era_index: Some(0), @@ -222,7 +203,7 @@ impl Calendar for Coptic { } fn is_in_leap_year(&self, date: &Self::DateInner) -> bool { - Self::provided_year_is_leap(date.0.year) + date.0.year.rem_euclid(4) == 3 } fn month(&self, date: &Self::DateInner) -> types::MonthInfo { @@ -230,11 +211,16 @@ impl Calendar for Coptic { } fn day_of_month(&self, date: &Self::DateInner) -> types::DayOfMonth { - date.0.day_of_month() + types::DayOfMonth(date.0.day) } fn day_of_year(&self, date: &Self::DateInner) -> types::DayOfYear { - date.0.day_of_year() + types::DayOfYear( + (1..date.0.month) + .map(|m| Self::days_in_provided_month(date.0.year, m) as u16) + .sum::() + + date.0.day as u16, + ) } fn debug_name(&self) -> &'static str { diff --git a/components/calendar/src/cal/ethiopian.rs b/components/calendar/src/cal/ethiopian.rs index 12c1baf22e9..79f7d51a30a 100644 --- a/components/calendar/src/cal/ethiopian.rs +++ b/components/calendar/src/cal/ethiopian.rs @@ -71,6 +71,14 @@ impl DateFieldsResolver for Ethiopian { // Coptic year type YearInfo = i32; + fn days_in_provided_month(year: Self::YearInfo, month: u8) -> u8 { + Coptic::days_in_provided_month(year, month) + } + + fn months_in_provided_year(year: Self::YearInfo) -> u8 { + Coptic::months_in_provided_year(year) + } + #[inline] fn year_info_from_era(&self, era: &str, era_year: i32) -> Result { match (self.era_style(), era) { @@ -180,7 +188,7 @@ impl Calendar for Ethiopian { } fn year_info(&self, date: &Self::DateInner) -> Self::Year { - let coptic_year = date.0 .0.extended_year(); + let coptic_year = date.0 .0.year; let extended_year = if self.0 == EthiopianEraStyle::AmeteAlem { coptic_year + AMETE_ALEM_OFFSET } else { diff --git a/components/calendar/src/cal/hebrew.rs b/components/calendar/src/cal/hebrew.rs index 4de085e73c6..93f79729f57 100644 --- a/components/calendar/src/cal/hebrew.rs +++ b/components/calendar/src/cal/hebrew.rs @@ -4,9 +4,7 @@ use crate::cal::iso::{Iso, IsoDateInner}; use crate::calendar_arithmetic::ArithmeticDateBuilder; -use crate::calendar_arithmetic::{ - ArithmeticDate, CalendarArithmetic, DateFieldsResolver, ToExtendedYear, -}; +use crate::calendar_arithmetic::{ArithmeticDate, DateFieldsResolver, ToExtendedYear}; use crate::error::DateError; use crate::options::{DateAddOptions, DateDifferenceOptions}; use crate::options::{DateFromFieldsOptions, Overflow}; @@ -87,7 +85,8 @@ impl HebrewYearInfo { } // HEBREW CALENDAR -impl CalendarArithmetic for Hebrew { +impl DateFieldsResolver for Hebrew { + type YearInfo = HebrewYearInfo; fn days_in_provided_month(info: HebrewYearInfo, ordinal_month: u8) -> u8 { info.keviyah.month_len(ordinal_month) } @@ -100,22 +99,6 @@ impl CalendarArithmetic for Hebrew { } } - fn days_in_provided_year(info: HebrewYearInfo) -> u16 { - info.keviyah.year_length() - } - - fn provided_year_is_leap(info: HebrewYearInfo) -> bool { - info.keviyah.is_leap() - } - - fn last_month_day_in_provided_year(info: HebrewYearInfo) -> (u8, u8) { - info.keviyah.last_month_day_in_year() - } -} - -impl DateFieldsResolver for Hebrew { - type YearInfo = HebrewYearInfo; - #[inline] fn year_info_from_era(&self, era: &str, era_year: i32) -> Result { match era { @@ -228,7 +211,7 @@ impl DateFieldsResolver for Hebrew { ordinal_month: u8, ) -> types::MonthInfo { let mut ordinal = ordinal_month; - let is_leap_year = Self::provided_year_is_leap(*year); + let is_leap_year = year.keviyah.is_leap(); if is_leap_year { if ordinal == 6 { @@ -322,15 +305,15 @@ impl Calendar for Hebrew { } fn months_in_year(&self, date: &Self::DateInner) -> u8 { - date.0.months_in_year() + Self::months_in_provided_year(date.0.year) } fn days_in_year(&self, date: &Self::DateInner) -> u16 { - date.0.days_in_year() + date.0.year.keviyah.year_length() } fn days_in_month(&self, date: &Self::DateInner) -> u8 { - date.0.days_in_month() + Self::days_in_provided_month(date.0.year, date.0.month) } fn add( @@ -356,7 +339,7 @@ impl Calendar for Hebrew { } fn year_info(&self, date: &Self::DateInner) -> Self::Year { - let extended_year = date.0.extended_year(); + let extended_year = date.0.year.value; types::EraYear { era_index: Some(0), era: tinystr!(16, "am"), @@ -367,7 +350,7 @@ impl Calendar for Hebrew { } fn is_in_leap_year(&self, date: &Self::DateInner) -> bool { - Self::provided_year_is_leap(date.0.year) + date.0.year.keviyah.is_leap() } fn month(&self, date: &Self::DateInner) -> MonthInfo { @@ -375,11 +358,16 @@ impl Calendar for Hebrew { } fn day_of_month(&self, date: &Self::DateInner) -> types::DayOfMonth { - date.0.day_of_month() + types::DayOfMonth(date.0.day) } fn day_of_year(&self, date: &Self::DateInner) -> types::DayOfYear { - date.0.day_of_year() + types::DayOfYear( + (1..date.0.month) + .map(|m| Self::days_in_provided_month(date.0.year, m) as u16) + .sum::() + + date.0.day as u16, + ) } fn calendar_algorithm(&self) -> Option { diff --git a/components/calendar/src/cal/hijri.rs b/components/calendar/src/cal/hijri.rs index 20de57e04a9..31f2e1da7e6 100644 --- a/components/calendar/src/cal/hijri.rs +++ b/components/calendar/src/cal/hijri.rs @@ -3,8 +3,8 @@ // (online at: https://github.com/unicode-org/icu4x/blob/main/LICENSE ). use crate::cal::iso::{Iso, IsoDateInner}; +use crate::calendar_arithmetic::ArithmeticDate; use crate::calendar_arithmetic::ToExtendedYear; -use crate::calendar_arithmetic::{ArithmeticDate, CalendarArithmetic}; use crate::calendar_arithmetic::{ArithmeticDateBuilder, DateFieldsResolver}; use crate::error::DateError; use crate::options::DateFromFieldsOptions; @@ -585,7 +585,7 @@ fn computer_reference_years() { year_info_from_extended: impl Fn(i32) -> C::YearInfo, ) -> Result where - C: CalendarArithmetic, + C: DateFieldsResolver, { let (ordinal_month, _is_leap) = month_code .parsed() @@ -658,7 +658,9 @@ impl PartialEq for HijriDateInner { } impl Eq for HijriDateInner {} -impl CalendarArithmetic for Hijri { +impl DateFieldsResolver for Hijri { + type YearInfo = HijriYearData; + fn days_in_provided_month(year: Self::YearInfo, month: u8) -> u8 { if year.packed.month_has_30_days(month) { 30 @@ -671,24 +673,6 @@ impl CalendarArithmetic for Hijri { 12 } - fn days_in_provided_year(year: Self::YearInfo) -> u16 { - year.last_day_of_month(12) - } - - fn provided_year_is_leap(year: Self::YearInfo) -> bool { - year.packed.is_leap() - } - - fn last_month_day_in_provided_year(year: Self::YearInfo) -> (u8, u8) { - let days = Self::days_in_provided_month(year, 12); - - (12, days) - } -} - -impl DateFieldsResolver for Hijri { - type YearInfo = HijriYearData; - #[inline] fn year_info_from_era(&self, era: &str, era_year: i32) -> Result { let extended_year = match era { @@ -776,15 +760,15 @@ impl Calendar for Hijri { } fn months_in_year(&self, date: &Self::DateInner) -> u8 { - date.0.months_in_year() + Self::months_in_provided_year(date.0.year) } fn days_in_year(&self, date: &Self::DateInner) -> u16 { - date.0.days_in_year() + date.0.year.last_day_of_month(12) } fn days_in_month(&self, date: &Self::DateInner) -> u8 { - date.0.days_in_month() + Self::days_in_provided_month(date.0.year, date.0.month) } fn add( @@ -810,7 +794,7 @@ impl Calendar for Hijri { } fn year_info(&self, date: &Self::DateInner) -> Self::Year { - let extended_year = date.0.extended_year(); + let extended_year = date.0.year.extended_year; if extended_year > 0 { types::EraYear { era: tinystr!(16, "ah"), @@ -831,7 +815,7 @@ impl Calendar for Hijri { } fn is_in_leap_year(&self, date: &Self::DateInner) -> bool { - Self::provided_year_is_leap(date.0.year) + date.0.year.packed.is_leap() } fn month(&self, date: &Self::DateInner) -> types::MonthInfo { @@ -839,11 +823,16 @@ impl Calendar for Hijri { } fn day_of_month(&self, date: &Self::DateInner) -> types::DayOfMonth { - date.0.day_of_month() + types::DayOfMonth(date.0.day) } fn day_of_year(&self, date: &Self::DateInner) -> types::DayOfYear { - date.0.day_of_year() + types::DayOfYear( + (1..date.0.month) + .map(|m| Self::days_in_provided_month(date.0.year, m) as u16) + .sum::() + + date.0.day as u16, + ) } fn calendar_algorithm(&self) -> Option { diff --git a/components/calendar/src/cal/indian.rs b/components/calendar/src/cal/indian.rs index 3ff9912eb41..8d6f27f2b30 100644 --- a/components/calendar/src/cal/indian.rs +++ b/components/calendar/src/cal/indian.rs @@ -3,7 +3,7 @@ // (online at: https://github.com/unicode-org/icu4x/blob/main/LICENSE ). use crate::cal::iso::{Iso, IsoDateInner}; -use crate::calendar_arithmetic::{ArithmeticDate, CalendarArithmetic}; +use crate::calendar_arithmetic::ArithmeticDate; use crate::calendar_arithmetic::{ArithmeticDateBuilder, DateFieldsResolver}; use crate::error::DateError; use crate::options::DateFromFieldsOptions; @@ -34,10 +34,18 @@ pub struct Indian; #[derive(Copy, Clone, Debug, Hash, Eq, PartialEq, PartialOrd, Ord)] pub struct IndianDateInner(ArithmeticDate); -impl CalendarArithmetic for Indian { +/// The Śaka era starts on the 81st day of the Gregorian year (March 22 or 21) +/// which is an 80 day offset. This number should be subtracted from Gregorian dates +const DAY_OFFSET: u16 = 80; +/// The Śaka era is 78 years behind Gregorian. This number should be added to Gregorian dates +const YEAR_OFFSET: i32 = 78; + +impl DateFieldsResolver for Indian { + type YearInfo = i32; + fn days_in_provided_month(year: i32, month: u8) -> u8 { if month == 1 { - 30 + Self::provided_year_is_leap(year) as u8 + 30 + calendrical_calculations::gregorian::is_leap_year(year + YEAR_OFFSET) as u8 } else if (2..=6).contains(&month) { 31 } else if (7..=12).contains(&month) { @@ -51,32 +59,6 @@ impl CalendarArithmetic for Indian { 12 } - fn provided_year_is_leap(year: i32) -> bool { - calendrical_calculations::gregorian::is_leap_year(year + YEAR_OFFSET) - } - - fn last_month_day_in_provided_year(_year: i32) -> (u8, u8) { - (12, 30) - } - - fn days_in_provided_year(year: i32) -> u16 { - if Self::provided_year_is_leap(year) { - 366 - } else { - 365 - } - } -} - -/// The Śaka era starts on the 81st day of the Gregorian year (March 22 or 21) -/// which is an 80 day offset. This number should be subtracted from Gregorian dates -const DAY_OFFSET: u16 = 80; -/// The Śaka era is 78 years behind Gregorian. This number should be added to Gregorian dates -const YEAR_OFFSET: i32 = 78; - -impl DateFieldsResolver for Indian { - type YearInfo = i32; - #[inline] fn year_info_from_era(&self, era: &str, era_year: i32) -> Result { match era { @@ -146,20 +128,39 @@ impl Calendar for Indian { // This is in the previous Indian year let day_of_year_indian = if day_of_year_iso <= DAY_OFFSET { year -= 1; - let n_days = Self::days_in_provided_year(year); + let n_days = if calendrical_calculations::gregorian::is_leap_year(year + YEAR_OFFSET) { + 366 + } else { + 365 + }; // calculate day of year in previous year n_days + day_of_year_iso - DAY_OFFSET } else { day_of_year_iso - DAY_OFFSET }; - IndianDateInner(ArithmeticDate::date_from_year_day(year, day_of_year_indian)) + let mut month = 1; + let mut day = day_of_year_indian as i32; + while month <= 12 { + let month_days = Self::days_in_provided_month(year, month) as i32; + if day <= month_days { + break; + } else { + day -= month_days; + month += 1; + } + } + + debug_assert!(day <= Self::days_in_provided_month(year, month) as i32); + let day = day.try_into().unwrap_or(1); + + IndianDateInner(ArithmeticDate::new_unchecked(year, month, day)) } // Algorithms directly implemented in icu_calendar since they're not from the book fn to_iso(&self, date: &Self::DateInner) -> IsoDateInner { - let day_of_year_indian = date.0.day_of_year().0; // 1-indexed - let days_in_year = date.0.days_in_year(); + let day_of_year_indian = self.day_of_year(date).0; // 1-indexed + let days_in_year = self.days_in_year(date); let mut year = date.0.year + YEAR_OFFSET; // days_in_year is a valid day of the year, so we check > not >= @@ -171,19 +172,24 @@ impl Calendar for Indian { day_of_year_indian + DAY_OFFSET }; - IsoDateInner(ArithmeticDate::date_from_year_day(year, day_of_year_iso)) + let (month, day) = calendrical_calculations::gregorian::year_day(year, day_of_year_iso); + IsoDateInner(ArithmeticDate::new_unchecked(year, month, day)) } fn months_in_year(&self, date: &Self::DateInner) -> u8 { - date.0.months_in_year() + Self::months_in_provided_year(date.0.year) } fn days_in_year(&self, date: &Self::DateInner) -> u16 { - date.0.days_in_year() + if self.is_in_leap_year(date) { + 366 + } else { + 365 + } } fn days_in_month(&self, date: &Self::DateInner) -> u8 { - date.0.days_in_month() + Self::days_in_provided_month(date.0.year, date.0.month) } fn add( @@ -205,7 +211,7 @@ impl Calendar for Indian { } fn year_info(&self, date: &Self::DateInner) -> Self::Year { - let extended_year = date.0.extended_year(); + let extended_year = date.0.year; types::EraYear { era_index: Some(0), era: tinystr!(16, "shaka"), @@ -216,7 +222,7 @@ impl Calendar for Indian { } fn is_in_leap_year(&self, date: &Self::DateInner) -> bool { - Self::provided_year_is_leap(date.0.year) + calendrical_calculations::gregorian::is_leap_year(date.0.year + YEAR_OFFSET) } fn month(&self, date: &Self::DateInner) -> types::MonthInfo { @@ -224,11 +230,16 @@ impl Calendar for Indian { } fn day_of_month(&self, date: &Self::DateInner) -> types::DayOfMonth { - date.0.day_of_month() + types::DayOfMonth(date.0.day) } fn day_of_year(&self, date: &Self::DateInner) -> types::DayOfYear { - date.0.day_of_year() + types::DayOfYear( + (1..date.0.month) + .map(|m| Self::days_in_provided_month(date.0.year, m) as u16) + .sum::() + + date.0.day as u16, + ) } fn debug_name(&self) -> &'static str { diff --git a/components/calendar/src/cal/julian.rs b/components/calendar/src/cal/julian.rs index 4093ab27e47..f898e8d5dd8 100644 --- a/components/calendar/src/cal/julian.rs +++ b/components/calendar/src/cal/julian.rs @@ -3,7 +3,7 @@ // (online at: https://github.com/unicode-org/icu4x/blob/main/LICENSE ). use crate::cal::iso::{Iso, IsoDateInner}; -use crate::calendar_arithmetic::{ArithmeticDate, CalendarArithmetic}; +use crate::calendar_arithmetic::ArithmeticDate; use crate::calendar_arithmetic::{ArithmeticDateBuilder, DateFieldsResolver}; use crate::error::DateError; use crate::options::DateFromFieldsOptions; @@ -69,11 +69,13 @@ pub struct Julian; // The inner date type used for representing Date pub struct JulianDateInner(pub(crate) ArithmeticDate); -impl CalendarArithmetic for Julian { +impl DateFieldsResolver for Julian { + type YearInfo = i32; + fn days_in_provided_month(year: i32, month: u8) -> u8 { match month { 4 | 6 | 9 | 11 => 30, - 2 if Self::provided_year_is_leap(year) => 29, + 2 if calendrical_calculations::julian::is_leap_year(year) => 29, 2 => 28, 1 | 3 | 5 | 7 | 8 | 10 | 12 => 31, _ => 0, @@ -84,26 +86,6 @@ impl CalendarArithmetic for Julian { 12 } - fn provided_year_is_leap(year: i32) -> bool { - calendrical_calculations::julian::is_leap_year(year) - } - - fn last_month_day_in_provided_year(_year: i32) -> (u8, u8) { - (12, 31) - } - - fn days_in_provided_year(year: i32) -> u16 { - if Self::provided_year_is_leap(year) { - 366 - } else { - 365 - } - } -} - -impl DateFieldsResolver for Julian { - type YearInfo = i32; - #[inline] fn year_info_from_era(&self, era: &str, era_year: i32) -> Result { match era { @@ -158,8 +140,8 @@ impl Calendar for Julian { fn from_rata_die(&self, rd: RataDie) -> Self::DateInner { JulianDateInner( match calendrical_calculations::julian::julian_from_fixed(rd) { - Err(I32CastError::BelowMin) => ArithmeticDate::min_date(), - Err(I32CastError::AboveMax) => ArithmeticDate::max_date(), + Err(I32CastError::BelowMin) => ArithmeticDate::new_unchecked(i32::MIN, 1, 1), + Err(I32CastError::AboveMax) => ArithmeticDate::new_unchecked(i32::MAX, 12, 31), Ok((year, month, day)) => ArithmeticDate::new_unchecked(year, month, day), }, ) @@ -178,15 +160,19 @@ impl Calendar for Julian { } fn months_in_year(&self, date: &Self::DateInner) -> u8 { - date.0.months_in_year() + Self::months_in_provided_year(date.0.year) } fn days_in_year(&self, date: &Self::DateInner) -> u16 { - date.0.days_in_year() + if self.is_in_leap_year(date) { + 366 + } else { + 365 + } } fn days_in_month(&self, date: &Self::DateInner) -> u8 { - date.0.days_in_month() + Self::days_in_provided_month(date.0.year, date.0.month) } fn add( @@ -210,7 +196,7 @@ impl Calendar for Julian { /// The calendar-specific year represented by `date` /// Julian has the same era scheme as Gregorian fn year_info(&self, date: &Self::DateInner) -> Self::Year { - let extended_year = date.0.extended_year(); + let extended_year = date.0.year; if extended_year > 0 { types::EraYear { era: tinystr!(16, "ce"), @@ -231,7 +217,7 @@ impl Calendar for Julian { } fn is_in_leap_year(&self, date: &Self::DateInner) -> bool { - Self::provided_year_is_leap(date.0.year) + calendrical_calculations::julian::is_leap_year(date.0.year) } /// The calendar-specific month represented by `date` @@ -241,11 +227,16 @@ impl Calendar for Julian { /// The calendar-specific day-of-month represented by `date` fn day_of_month(&self, date: &Self::DateInner) -> types::DayOfMonth { - date.0.day_of_month() + types::DayOfMonth(date.0.day) } fn day_of_year(&self, date: &Self::DateInner) -> types::DayOfYear { - date.0.day_of_year() + types::DayOfYear( + (1..date.0.month) + .map(|m| Self::days_in_provided_month(date.0.year, m) as u16) + .sum::() + + date.0.day as u16, + ) } fn debug_name(&self) -> &'static str { @@ -541,10 +532,9 @@ mod test { #[test] fn test_julian_leap_years() { - assert!(Julian::provided_year_is_leap(4)); - assert!(Julian::provided_year_is_leap(0)); - assert!(Julian::provided_year_is_leap(-4)); - + Date::try_new_julian(4, 2, 29).unwrap(); + Date::try_new_julian(0, 2, 29).unwrap(); + Date::try_new_julian(-4, 2, 29).unwrap(); Date::try_new_julian(2020, 2, 29).unwrap(); } } diff --git a/components/calendar/src/cal/persian.rs b/components/calendar/src/cal/persian.rs index c67aa9d566a..02997abcdf7 100644 --- a/components/calendar/src/cal/persian.rs +++ b/components/calendar/src/cal/persian.rs @@ -3,7 +3,7 @@ // (online at: https://github.com/unicode-org/icu4x/blob/main/LICENSE ). use crate::cal::iso::{Iso, IsoDateInner}; -use crate::calendar_arithmetic::{ArithmeticDate, CalendarArithmetic}; +use crate::calendar_arithmetic::ArithmeticDate; use crate::calendar_arithmetic::{ArithmeticDateBuilder, DateFieldsResolver}; use crate::error::DateError; use crate::options::DateFromFieldsOptions; @@ -37,12 +37,14 @@ pub struct Persian; /// The inner date type used for representing [`Date`]s of [`Persian`]. See [`Date`] and [`Persian`] for more details. pub struct PersianDateInner(ArithmeticDate); -impl CalendarArithmetic for Persian { +impl DateFieldsResolver for Persian { + type YearInfo = i32; + fn days_in_provided_month(year: i32, month: u8) -> u8 { match month { 1..=6 => 31, 7..=11 => 30, - 12 if Self::provided_year_is_leap(year) => 30, + 12 if calendrical_calculations::persian::is_leap_year(year) => 30, 12 => 29, _ => 0, } @@ -52,30 +54,6 @@ impl CalendarArithmetic for Persian { 12 } - fn provided_year_is_leap(p_year: i32) -> bool { - calendrical_calculations::persian::is_leap_year(p_year) - } - - fn days_in_provided_year(year: i32) -> u16 { - if Self::provided_year_is_leap(year) { - 366 - } else { - 365 - } - } - - fn last_month_day_in_provided_year(year: i32) -> (u8, u8) { - if Self::provided_year_is_leap(year) { - (12, 30) - } else { - (12, 29) - } - } -} - -impl DateFieldsResolver for Persian { - type YearInfo = i32; - #[inline] fn year_info_from_era(&self, era: &str, era_year: i32) -> Result { match era { @@ -129,8 +107,8 @@ impl Calendar for Persian { fn from_rata_die(&self, rd: RataDie) -> Self::DateInner { PersianDateInner( match calendrical_calculations::persian::fast_persian_from_fixed(rd) { - Err(I32CastError::BelowMin) => ArithmeticDate::min_date(), - Err(I32CastError::AboveMax) => ArithmeticDate::max_date(), + Err(I32CastError::BelowMin) => ArithmeticDate::new_unchecked(i32::MIN, 1, 1), + Err(I32CastError::AboveMax) => ArithmeticDate::new_unchecked(i32::MAX, 12, 29), Ok((year, month, day)) => ArithmeticDate::new_unchecked(year, month, day), }, ) @@ -153,15 +131,19 @@ impl Calendar for Persian { } fn months_in_year(&self, date: &Self::DateInner) -> u8 { - date.0.months_in_year() + Self::months_in_provided_year(date.0.year) } fn days_in_year(&self, date: &Self::DateInner) -> u16 { - date.0.days_in_year() + if self.is_in_leap_year(date) { + 366 + } else { + 365 + } } fn days_in_month(&self, date: &Self::DateInner) -> u8 { - date.0.days_in_month() + Self::days_in_provided_month(date.0.year, date.0.month) } fn add( @@ -183,7 +165,7 @@ impl Calendar for Persian { } fn year_info(&self, date: &Self::DateInner) -> Self::Year { - let extended_year = date.0.extended_year(); + let extended_year = date.0.year; types::EraYear { era: tinystr!(16, "ap"), era_index: Some(0), @@ -194,7 +176,7 @@ impl Calendar for Persian { } fn is_in_leap_year(&self, date: &Self::DateInner) -> bool { - Self::provided_year_is_leap(date.0.year) + calendrical_calculations::persian::is_leap_year(date.0.year) } fn month(&self, date: &Self::DateInner) -> types::MonthInfo { @@ -202,11 +184,16 @@ impl Calendar for Persian { } fn day_of_month(&self, date: &Self::DateInner) -> types::DayOfMonth { - date.0.day_of_month() + types::DayOfMonth(date.0.day) } fn day_of_year(&self, date: &Self::DateInner) -> types::DayOfYear { - date.0.day_of_year() + types::DayOfYear( + (1..date.0.month) + .map(|m| Self::days_in_provided_month(date.0.year, m) as u16) + .sum::() + + date.0.day as u16, + ) } fn debug_name(&self) -> &'static str { @@ -380,15 +367,6 @@ mod tests { }, ]; - fn days_in_provided_year_core(year: i32) -> u16 { - let ny = - calendrical_calculations::persian::fixed_from_fast_persian(year, 1, 1).to_i64_date(); - let next_ny = calendrical_calculations::persian::fixed_from_fast_persian(year + 1, 1, 1) - .to_i64_date(); - - (next_ny - ny) as u16 - } - #[test] fn test_persian_leap_year() { let mut leap_years: [i32; 21] = [0; 21]; @@ -401,8 +379,11 @@ mod tests { for (index, case) in CASES.iter().enumerate() { leap_years[index] = case.year; } - for (year, bool) in leap_years.iter().zip(expected_values.iter()) { - assert_eq!(Persian::provided_year_is_leap(*year), *bool); + for (&year, &is_leap) in leap_years.iter().zip(expected_values.iter()) { + assert_eq!( + Date::try_new_persian(year, 1, 1).unwrap().is_in_leap_year(), + is_leap + ); } } @@ -410,8 +391,12 @@ mod tests { fn days_in_provided_year_test() { for case in CASES.iter() { assert_eq!( - days_in_provided_year_core(case.year), - Persian::days_in_provided_year(case.year) + Date::try_new_persian(case.year, 1, 1) + .unwrap() + .days_in_year(), + (calendrical_calculations::persian::fixed_from_fast_persian(case.year + 1, 1, 1) + - calendrical_calculations::persian::fixed_from_fast_persian(case.year, 1, 1)) + as u16 ); } } @@ -736,13 +721,13 @@ mod tests { #[test] fn test_calendar_ut_ac_ir_data() { - for (p_year, leap, iso_year, iso_month, iso_day) in CALENDAR_UT_AC_IR_TEST_DATA.iter() { - assert_eq!(Persian::provided_year_is_leap(*p_year), *leap); - let persian_date = Date::try_new_persian(*p_year, 1, 1).unwrap(); + for &(p_year, leap, iso_year, iso_month, iso_day) in CALENDAR_UT_AC_IR_TEST_DATA.iter() { + let persian_date = Date::try_new_persian(p_year, 1, 1).unwrap(); + assert_eq!(persian_date.is_in_leap_year(), leap); let iso_date = persian_date.to_calendar(Iso); - assert_eq!(iso_date.era_year().year, *iso_year); - assert_eq!(iso_date.month().ordinal, *iso_month); - assert_eq!(iso_date.day_of_month().0, *iso_day); + assert_eq!(iso_date.era_year().year, iso_year); + assert_eq!(iso_date.month().ordinal, iso_month); + assert_eq!(iso_date.day_of_month().0, iso_day); } } } diff --git a/components/calendar/src/calendar_arithmetic.rs b/components/calendar/src/calendar_arithmetic.rs index 9f7e699e69f..907d9de1f4c 100644 --- a/components/calendar/src/calendar_arithmetic.rs +++ b/components/calendar/src/calendar_arithmetic.rs @@ -5,13 +5,11 @@ use crate::error::{range_check, range_check_with_overflow}; use crate::options::{DateAddOptions, DateDifferenceOptions}; use crate::options::{DateFromFieldsOptions, MissingFieldsStrategy, Overflow}; -use crate::types::{DateDuration, DateDurationUnit, DateFields, DayOfYear, MonthCode}; +use crate::types::{DateDuration, DateDurationUnit, DateFields, MonthCode}; use crate::{types, Calendar, DateError, RangeError}; use core::cmp::Ordering; -use core::convert::TryInto; use core::fmt::Debug; use core::hash::{Hash, Hasher}; -use core::marker::PhantomData; use core::ops::RangeInclusive; use tinystr::tinystr; @@ -19,26 +17,24 @@ use tinystr::tinystr; const VALID_YEAR_RANGE: RangeInclusive = (i32::MIN / 16)..=-(i32::MIN / 16); #[derive(Debug)] -#[allow(clippy::exhaustive_structs)] // this type is stable -pub(crate) struct ArithmeticDate { +pub(crate) struct ArithmeticDate { pub year: C::YearInfo, /// 1-based month of year pub month: u8, /// 1-based day of month pub day: u8, - marker: PhantomData, } // Manual impls since the derive will introduce a C: Trait bound // and only the year value should be compared -impl Copy for ArithmeticDate {} -impl Clone for ArithmeticDate { +impl Copy for ArithmeticDate {} +impl Clone for ArithmeticDate { fn clone(&self) -> Self { *self } } -impl PartialEq for ArithmeticDate { +impl PartialEq for ArithmeticDate { fn eq(&self, other: &Self) -> bool { self.year.to_extended_year() == other.year.to_extended_year() && self.month == other.month @@ -46,9 +42,9 @@ impl PartialEq for ArithmeticDate { } } -impl Eq for ArithmeticDate {} +impl Eq for ArithmeticDate {} -impl Ord for ArithmeticDate { +impl Ord for ArithmeticDate { fn cmp(&self, other: &Self) -> Ordering { self.year .to_extended_year() @@ -58,13 +54,13 @@ impl Ord for ArithmeticDate { } } -impl PartialOrd for ArithmeticDate { +impl PartialOrd for ArithmeticDate { fn partial_cmp(&self, other: &Self) -> Option { Some(self.cmp(other)) } } -impl Hash for ArithmeticDate { +impl Hash for ArithmeticDate { fn hash(&self, state: &mut H) where H: Hasher, @@ -89,62 +85,16 @@ impl ToExtendedYear for i32 { } } -pub(crate) trait CalendarArithmetic: Calendar + DateFieldsResolver { - // TODO(#3933): potentially make these methods take &self instead, and absorb certain y/m parameters - // based on usage patterns (e.g month_days is only ever called with self.year) - fn days_in_provided_month(year: Self::YearInfo, month: u8) -> u8; - fn months_in_provided_year(year: Self::YearInfo) -> u8; - fn provided_year_is_leap(year: Self::YearInfo) -> bool; - fn last_month_day_in_provided_year(year: Self::YearInfo) -> (u8, u8); - - fn day_of_provided_year(year: Self::YearInfo, month: u8, day: u8) -> u16 { - let mut day_of_year = 0; - for month in 1..month { - day_of_year += Self::days_in_provided_month(year, month) as u16; - } - day_of_year + day as u16 - } - - /// Calculate the days in a given year - /// Can be overridden with simpler implementations for solar calendars - /// (typically, 366 in leap, 365 otherwise) Leave this as the default - /// for lunar calendars - /// - /// The name has `provided` in it to avoid clashes with Calendar - fn days_in_provided_year(year: Self::YearInfo) -> u16 { - let months_in_year = Self::months_in_provided_year(year); - let mut days: u16 = 0; - for month in 1..=months_in_year { - days += Self::days_in_provided_month(year, month) as u16; - } - days - } - - fn date_from_provided_year_day(year: Self::YearInfo, year_day: u16) -> (u8, u8) { - let mut month = 1; - let mut day = year_day as i32; - while month <= Self::months_in_provided_year(year) { - let month_days = Self::days_in_provided_month(year, month) as i32; - if day <= month_days { - break; - } else { - day -= month_days; - month += 1; - } - } - - debug_assert!(day <= Self::days_in_provided_month(year, month) as i32); - - (month, day.try_into().unwrap_or(1)) - } -} - /// Trait for converting from era codes, month codes, and other fields to year/month/day ordinals. pub(crate) trait DateFieldsResolver: Calendar { /// This stores the year as either an i32, or a type containing more /// useful computational information. type YearInfo: Copy + Debug + PartialEq + ToExtendedYear; + fn days_in_provided_month(year: Self::YearInfo, month: u8) -> u8; + + fn months_in_provided_year(year: Self::YearInfo) -> u8; + /// Converts the era and era year to a YearInfo. If the calendar does not have eras, /// this should always return an Err result. fn year_info_from_era(&self, era: &str, era_year: i32) -> Result; @@ -206,68 +156,10 @@ pub(crate) trait DateFieldsResolver: Calendar { } } -impl ArithmeticDate { +impl ArithmeticDate { #[inline] pub(crate) const fn new_unchecked(year: C::YearInfo, month: u8, day: u8) -> Self { - ArithmeticDate { - year, - month, - day, - marker: PhantomData, - } - } - - #[inline] - pub fn min_date() -> Self - where - C: CalendarArithmetic, - { - ArithmeticDate::new_unchecked(i32::MIN, 1, 1) - } - - #[inline] - pub fn max_date() -> Self - where - C: CalendarArithmetic, - { - let year = i32::MAX; - let (month, day) = C::last_month_day_in_provided_year(year); - ArithmeticDate::new_unchecked(year, month, day) - } - - #[inline] - pub fn days_in_year(&self) -> u16 { - C::days_in_provided_year(self.year) - } - - #[inline] - pub fn months_in_year(&self) -> u8 { - C::months_in_provided_year(self.year) - } - - #[inline] - pub fn days_in_month(&self) -> u8 { - C::days_in_provided_month(self.year, self.month) - } - - #[inline] - pub fn date_from_year_day(year: C::YearInfo, year_day: u16) -> ArithmeticDate { - let (month, day) = C::date_from_provided_year_day(year, year_day); - ArithmeticDate::new_unchecked(year, month, day) - } - - #[inline] - pub fn day_of_month(&self) -> types::DayOfMonth { - types::DayOfMonth(self.day) - } - - #[inline] - pub fn day_of_year(&self) -> DayOfYear { - DayOfYear(C::day_of_provided_year(self.year, self.month, self.day)) - } - - pub fn extended_year(&self) -> i32 { - self.year.to_extended_year() + ArithmeticDate { year, month, day } } /// Construct a new arithmetic date from a year, month ordinal, and day, bounds checking @@ -305,9 +197,7 @@ impl ArithmeticDate { }, ) } -} -impl ArithmeticDate { /// Implements the Temporal abstract operation BalanceNonISODate. /// /// This takes a year, month, and day, where the month and day might be out of range, then diff --git a/components/calendar/src/date.rs b/components/calendar/src/date.rs index 692c0e29887..ec165c7e40a 100644 --- a/components/calendar/src/date.rs +++ b/components/calendar/src/date.rs @@ -4,7 +4,7 @@ use crate::any_calendar::{AnyCalendar, IntoAnyCalendar}; use crate::cal::{abstract_gregorian::AbstractGregorian, iso::IsoEra}; -use crate::calendar_arithmetic::CalendarArithmetic; +use crate::calendar_arithmetic::ArithmeticDate; use crate::error::DateError; use crate::options::DateFromFieldsOptions; use crate::options::{DateAddOptions, DateDifferenceOptions}; @@ -434,9 +434,11 @@ impl Date { pub fn week_of_year(&self) -> IsoWeekOfYear { let week_of = WeekCalculator::ISO .week_of( - AbstractGregorian::::days_in_provided_year( + AbstractGregorian(IsoEra).days_in_year(&ArithmeticDate::new_unchecked( self.inner.0.year.saturating_sub(1), - ), + 1, + 1, + )), self.days_in_year(), self.day_of_year().0, self.day_of_week(), From 28c53ff1251d198125b1d5f510593db1a8240d28 Mon Sep 17 00:00:00 2001 From: Robert Bastian Date: Tue, 14 Oct 2025 11:59:30 +0200 Subject: [PATCH 2/2] improve day_of_year code --- components/calendar/src/cal/coptic.rs | 7 +------ components/calendar/src/cal/hebrew.rs | 7 +------ components/calendar/src/cal/hijri.rs | 7 +------ components/calendar/src/cal/persian.rs | 4 +--- 4 files changed, 4 insertions(+), 21 deletions(-) diff --git a/components/calendar/src/cal/coptic.rs b/components/calendar/src/cal/coptic.rs index 899abf52caa..146cf5b1f44 100644 --- a/components/calendar/src/cal/coptic.rs +++ b/components/calendar/src/cal/coptic.rs @@ -215,12 +215,7 @@ impl Calendar for Coptic { } fn day_of_year(&self, date: &Self::DateInner) -> types::DayOfYear { - types::DayOfYear( - (1..date.0.month) - .map(|m| Self::days_in_provided_month(date.0.year, m) as u16) - .sum::() - + date.0.day as u16, - ) + types::DayOfYear(30 * (date.0.month as u16 - 1) + date.0.day as u16) } fn debug_name(&self) -> &'static str { diff --git a/components/calendar/src/cal/hebrew.rs b/components/calendar/src/cal/hebrew.rs index 93f79729f57..414871d6bf9 100644 --- a/components/calendar/src/cal/hebrew.rs +++ b/components/calendar/src/cal/hebrew.rs @@ -362,12 +362,7 @@ impl Calendar for Hebrew { } fn day_of_year(&self, date: &Self::DateInner) -> types::DayOfYear { - types::DayOfYear( - (1..date.0.month) - .map(|m| Self::days_in_provided_month(date.0.year, m) as u16) - .sum::() - + date.0.day as u16, - ) + types::DayOfYear(date.0.year.keviyah.days_preceding(date.0.month) + date.0.day as u16) } fn calendar_algorithm(&self) -> Option { diff --git a/components/calendar/src/cal/hijri.rs b/components/calendar/src/cal/hijri.rs index 31f2e1da7e6..5c5661085d4 100644 --- a/components/calendar/src/cal/hijri.rs +++ b/components/calendar/src/cal/hijri.rs @@ -827,12 +827,7 @@ impl Calendar for Hijri { } fn day_of_year(&self, date: &Self::DateInner) -> types::DayOfYear { - types::DayOfYear( - (1..date.0.month) - .map(|m| Self::days_in_provided_month(date.0.year, m) as u16) - .sum::() - + date.0.day as u16, - ) + types::DayOfYear(date.0.year.last_day_of_month(date.0.month - 1) + date.0.day as u16) } fn calendar_algorithm(&self) -> Option { diff --git a/components/calendar/src/cal/persian.rs b/components/calendar/src/cal/persian.rs index 02997abcdf7..0c34ee23d60 100644 --- a/components/calendar/src/cal/persian.rs +++ b/components/calendar/src/cal/persian.rs @@ -189,9 +189,7 @@ impl Calendar for Persian { fn day_of_year(&self, date: &Self::DateInner) -> types::DayOfYear { types::DayOfYear( - (1..date.0.month) - .map(|m| Self::days_in_provided_month(date.0.year, m) as u16) - .sum::() + (date.0.month as u16 - 1) * 31 - (date.0.month as u16 - 1).saturating_sub(6) + date.0.day as u16, ) }