diff --git a/components/calendar/src/cal/abstract_gregorian.rs b/components/calendar/src/cal/abstract_gregorian.rs index 0967e4d1776..b88fa1de5fa 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, DateFromFieldsError, EcmaReferenceYearError, UnknownEraError}; 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, @@ -130,8 +106,10 @@ impl Calendar for AbstractGregorian { fn from_rata_die(&self, date: RataDie) -> Self::DateInner { let iso = 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), }; @@ -159,15 +137,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( @@ -194,7 +172,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 { @@ -202,11 +180,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 a96a324e40c..17d3b20c976 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, DateFromFieldsError, EcmaReferenceYearError, MonthCodeError, UnknownEraError, }; @@ -524,7 +522,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) } @@ -534,31 +534,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, @@ -632,13 +607,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); @@ -652,13 +626,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); @@ -672,11 +645,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( @@ -711,7 +684,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`; @@ -724,7 +697,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 @@ -737,7 +710,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 d218bfe08d3..956b6c7ee5d 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, DateFromFieldsError, EcmaReferenceYearError, MonthCodeError, UnknownEraError, @@ -43,12 +43,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 @@ -61,31 +63,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, @@ -166,8 +143,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), }, ) @@ -186,15 +163,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( @@ -216,7 +197,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), @@ -227,7 +208,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 { @@ -235,11 +216,11 @@ 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(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/ethiopian.rs b/components/calendar/src/cal/ethiopian.rs index d012c61c6ac..b24bf4a0442 100644 --- a/components/calendar/src/cal/ethiopian.rs +++ b/components/calendar/src/cal/ethiopian.rs @@ -76,6 +76,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, @@ -187,7 +195,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 c0e7d3e712c..35a5ecb9d4d 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, DateFromFieldsError, EcmaReferenceYearError, MonthCodeError, UnknownEraError, }; @@ -91,7 +89,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) } @@ -104,22 +103,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, @@ -244,7 +227,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 { @@ -338,15 +321,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( @@ -372,7 +355,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"), @@ -383,7 +366,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 { @@ -391,11 +374,11 @@ 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(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 e75e644378e..ad6be80a303 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, DateFromFieldsError, EcmaReferenceYearError, UnknownEraError}; use crate::options::DateFromFieldsOptions; @@ -608,7 +608,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() @@ -691,7 +691,9 @@ impl Ord 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 @@ -704,24 +706,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, @@ -812,15 +796,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( @@ -846,7 +830,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"), @@ -867,7 +851,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 { @@ -875,11 +859,11 @@ 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(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/indian.rs b/components/calendar/src/cal/indian.rs index 9cb55bd496f..7356414beef 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, DateFromFieldsError, EcmaReferenceYearError, UnknownEraError}; use crate::options::DateFromFieldsOptions; @@ -36,10 +36,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) { @@ -53,32 +61,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, @@ -149,20 +131,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 >= @@ -174,19 +175,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( @@ -208,7 +214,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"), @@ -219,7 +225,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 { @@ -227,11 +233,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 74e32ae5909..e2d503abe26 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, DateFromFieldsError, EcmaReferenceYearError, UnknownEraError}; 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, @@ -159,8 +141,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), }, ) @@ -179,15 +161,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( @@ -211,7 +197,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"), @@ -232,7 +218,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` @@ -242,11 +228,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 { @@ -534,10 +525,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 ac9b2ec2193..c367fdc00a2 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, DateFromFieldsError, EcmaReferenceYearError, UnknownEraError}; use crate::options::DateFromFieldsOptions; @@ -39,12 +39,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, } @@ -54,30 +56,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, @@ -132,8 +110,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), }, ) @@ -156,15 +134,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( @@ -186,7 +168,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), @@ -197,7 +179,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 { @@ -205,11 +187,14 @@ 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( + (date.0.month as u16 - 1) * 31 - (date.0.month as u16 - 1).saturating_sub(6) + + date.0.day as u16, + ) } fn debug_name(&self) -> &'static str { @@ -383,15 +368,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]; @@ -404,8 +380,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 + ); } } @@ -413,8 +392,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 ); } } @@ -739,13 +722,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 84ced09ac8b..6fa4c65cd4a 100644 --- a/components/calendar/src/calendar_arithmetic.rs +++ b/components/calendar/src/calendar_arithmetic.rs @@ -8,13 +8,11 @@ use crate::error::{ }; 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; @@ -22,26 +20,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 @@ -49,9 +45,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() @@ -61,13 +57,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, @@ -92,62 +88,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( @@ -213,68 +163,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 @@ -312,9 +204,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 cdbf917b96d..a8cef81a354 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, DateFromFieldsError}; use crate::options::DateFromFieldsOptions; use crate::options::{DateAddOptions, DateDifferenceOptions}; @@ -433,7 +433,8 @@ impl Date { pub fn week_of_year(&self) -> IsoWeekOfYear { let week_of = WeekCalculator::ISO .week_of( - AbstractGregorian::::days_in_provided_year(self.inner.0.year - 1), + 365 + calendrical_calculations::gregorian::is_leap_year(self.inner.0.year - 1) + as u16, self.days_in_year(), self.day_of_year().0, self.day_of_week(),