Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add YearInfo to CalendarArithmetic (but don't use it yet in any calendar) #4407

Merged
merged 8 commits into from
Dec 7, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
113 changes: 95 additions & 18 deletions components/calendar/src/calendar_arithmetic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,34 +3,79 @@
// (online at: https://github.com/unicode-org/icu4x/blob/main/LICENSE ).

use crate::{types, Calendar, CalendarError, DateDuration, DateDurationUnit};
use core::cmp::Ordering;
use core::convert::TryInto;
use core::fmt::Debug;
use core::hash::{Hash, Hasher};
use core::marker::PhantomData;
use tinystr::tinystr;

// Note: The Ord/PartialOrd impls can be derived because the fields are in the correct order.
#[derive(Debug, Hash, Eq, PartialEq, Ord, PartialOrd)]
#[derive(Debug)]
#[allow(clippy::exhaustive_structs)] // this type is stable
pub struct ArithmeticDate<C> {
pub(crate) struct ArithmeticDate<C: CalendarArithmetic> {
pub year: i32,
/// 1-based month of year
pub month: u8,
/// 1-based day of month
pub day: u8,
/// Invariant: MUST be updated to match the info for `year` whenever `year` is updated or set.
pub year_info: C::YearInfo,
marker: PhantomData<C>,
}

impl<C> Copy for ArithmeticDate<C> {}
impl<C> Clone for ArithmeticDate<C> {
// Manual impls since the derive will introduce a C: Trait bound
// and many of these impls can ignore the year_info field
impl<C: CalendarArithmetic> Copy for ArithmeticDate<C> {}
impl<C: CalendarArithmetic> Clone for ArithmeticDate<C> {
fn clone(&self) -> Self {
*self
}
}

impl<C: CalendarArithmetic> PartialEq for ArithmeticDate<C> {
fn eq(&self, other: &Self) -> bool {
self.year == other.year && self.month == other.month && self.day == other.day
}
}

impl<C: CalendarArithmetic> Eq for ArithmeticDate<C> {}

impl<C: CalendarArithmetic> Ord for ArithmeticDate<C> {
fn cmp(&self, other: &Self) -> Ordering {
self.year
.cmp(&other.year)
.then(self.month.cmp(&other.month))
.then(self.day.cmp(&other.day))
}
}

impl<C: CalendarArithmetic> PartialOrd for ArithmeticDate<C> {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}

impl<C: CalendarArithmetic> Hash for ArithmeticDate<C> {
fn hash<H>(&self, state: &mut H)
where
H: Hasher,
{
self.year.hash(state);
self.month.hash(state);
self.day.hash(state);
}
}

/// Maximum number of iterations when iterating through the days of a month; can be increased if necessary
#[allow(dead_code)] // TODO: Remove dead code tag after use
pub(crate) const MAX_ITERS_FOR_DAYS_OF_MONTH: u8 = 33;

pub trait CalendarArithmetic: Calendar {
pub(crate) trait CalendarArithmetic: Calendar {
/// In case we plan to cache per-year data, this stores
/// useful computational information for the current year
/// as a field on ArithmeticDate
type YearInfo: Copy + Debug;
fn month_days(year: i32, month: u8) -> u8;
fn months_for_every_year(year: i32) -> u8;
fn is_leap_year(year: i32) -> bool;
Expand All @@ -52,36 +97,57 @@ pub trait CalendarArithmetic: Calendar {
}
}

pub(crate) trait PrecomputedDataSource<YearInfo> {
/// Given a calendar year, load (or compute) the YearInfo for it
fn load_or_compute_info(&self, year: i32) -> YearInfo;
}

impl PrecomputedDataSource<()> for () {
fn load_or_compute_info(&self, _year: i32) {}
}

impl<C: CalendarArithmetic> ArithmeticDate<C> {
/// Create a new `ArithmeticDate` without checking that `month` and `day` are in bounds.
#[inline]
pub const fn new_unchecked(year: i32, month: u8, day: u8) -> Self {
pub const fn new_unchecked(year: i32, month: u8, day: u8) -> Self
where
C: CalendarArithmetic<YearInfo = ()>,
{
ArithmeticDate {
year,
month,
day,
year_info: (),
marker: PhantomData,
}
}

#[inline]
pub fn min_date() -> Self {
pub fn min_date() -> Self
where
C: CalendarArithmetic<YearInfo = ()>,
{
ArithmeticDate {
year: i32::MIN,
month: 1,
day: 1,
year_info: (),
marker: PhantomData,
}
}

#[inline]
pub fn max_date() -> Self {
pub fn max_date() -> Self
where
C: CalendarArithmetic<YearInfo = ()>,
{
let year = i32::MAX;
let (month, day) = C::last_month_day_in_year(year);
ArithmeticDate {
year: i32::MAX,
month,
day,
year_info: (),
marker: PhantomData,
}
}
Expand Down Expand Up @@ -121,9 +187,14 @@ impl<C: CalendarArithmetic> ArithmeticDate<C> {
}

#[inline]
pub fn offset_date(&mut self, offset: DateDuration<C>) {
pub fn offset_date(
&mut self,
offset: DateDuration<C>,
data: &impl PrecomputedDataSource<C::YearInfo>,
) {
// For offset_date to work with lunar calendars, need to handle an edge case where the original month is not valid in the future year.
self.year += offset.years;
self.year_info = data.load_or_compute_info(self.year);

self.offset_months(offset.months);

Expand All @@ -139,6 +210,8 @@ impl<C: CalendarArithmetic> ArithmeticDate<C> {
_largest_unit: DateDurationUnit,
_smaller_unit: DateDurationUnit,
) -> DateDuration<C> {
// This simple implementation does not need C::PrecomputedDataSource right now, but it
// likely will once we've written a proper implementation
DateDuration::new(
self.year - date2.year,
self.month as i32 - date2.month as i32,
Expand Down Expand Up @@ -172,7 +245,10 @@ impl<C: CalendarArithmetic> ArithmeticDate<C> {
}

#[inline]
pub fn date_from_year_day(year: i32, year_day: u32) -> ArithmeticDate<C> {
pub fn date_from_year_day(year: i32, year_day: u32) -> ArithmeticDate<C>
where
C: CalendarArithmetic<YearInfo = ()>,
{
let mut month = 1;
let mut day = year_day as i32;
while month <= C::months_for_every_year(year) {
Expand All @@ -192,6 +268,7 @@ impl<C: CalendarArithmetic> ArithmeticDate<C> {
year,
month,
day: day.try_into().unwrap_or(0),
year_info: (),
marker: PhantomData,
}
}
Expand Down Expand Up @@ -243,7 +320,10 @@ impl<C: CalendarArithmetic> ArithmeticDate<C> {
year: i32,
month_code: types::MonthCode,
day: u8,
) -> Result<Self, CalendarError> {
) -> Result<Self, CalendarError>
where
C: CalendarArithmetic<YearInfo = ()>,
{
let month = if let Some((ordinal, false)) = month_code.parsed() {
ordinal
} else {
Expand Down Expand Up @@ -274,7 +354,10 @@ impl<C: CalendarArithmetic> ArithmeticDate<C> {
/// Construct a new arithmetic date from a year, month ordinal, and day, bounds checking
/// the month and day
/// Originally (new_from_solar_ordinals) but renamed because it works for some lunar calendars
pub fn new_from_ordinals(year: i32, month: u8, day: u8) -> Result<Self, CalendarError> {
pub fn new_from_ordinals(year: i32, month: u8, day: u8) -> Result<Self, CalendarError>
where
C: CalendarArithmetic<YearInfo = ()>,
{
let max_month = C::months_for_every_year(year);
if month > max_month {
return Err(CalendarError::Overflow {
Expand All @@ -292,12 +375,6 @@ impl<C: CalendarArithmetic> ArithmeticDate<C> {

Ok(Self::new_unchecked(year, month, day))
}

/// This fn currently just calls [`new_from_ordinals`], but exists separately for
/// lunar calendars in case different logic needs to be implemented later.
pub fn new_from_lunar_ordinals(year: i32, month: u8, day: u8) -> Result<Self, CalendarError> {
Self::new_from_ordinals(year, month, day)
}
}

#[cfg(test)]
Expand Down
2 changes: 1 addition & 1 deletion components/calendar/src/chinese.rs
Original file line number Diff line number Diff line change
Expand Up @@ -193,7 +193,7 @@ impl Calendar for Chinese {
#[doc(hidden)] // unstable
fn offset_date(&self, date: &mut Self::DateInner, offset: DateDuration<Self>) {
let year = date.0 .0.year;
date.0 .0.offset_date(offset);
date.0 .0.offset_date(offset, &());
if date.0 .0.year != year {
date.0 .1 = ChineseBasedYearInfo::get_year_info::<Chinese>(year);
}
Expand Down
14 changes: 8 additions & 6 deletions components/calendar/src/chinese_based.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
use crate::{
calendar_arithmetic::{ArithmeticDate, CalendarArithmetic},
types::MonthCode,
CalendarError, Iso,
Calendar, CalendarError, Iso,
};

use calendrical_calculations::chinese_based::{self, ChineseBased, YearBounds};
Expand All @@ -32,7 +32,7 @@ use core::num::NonZeroU8;
/// The trait ChineseBased is used by Chinese-based calendars to perform computations shared by such calendar.
///
/// For an example of how to use this trait, see `impl ChineseBasedWithDataLoading for Chinese` in [`Chinese`].
pub(crate) trait ChineseBasedWithDataLoading: CalendarArithmetic {
pub(crate) trait ChineseBasedWithDataLoading: Calendar {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this had to change, the cyclic dep from ChineseBasedWithDataLoading: CalendarArithmetic and impl<C: ChineseBasedWithDataLoading> CalendarArithmetic for C is brittle and rustc seems to not be willing to resolve certain things there.

type CB: ChineseBased;
/// Get the compiled const data for a ChineseBased calendar; can return `None` if the given year
/// does not correspond to any compiled data.
Expand All @@ -41,14 +41,14 @@ pub(crate) trait ChineseBasedWithDataLoading: CalendarArithmetic {

/// Chinese-based calendars define DateInner as a calendar-specific struct wrapping ChineseBasedDateInner.
#[derive(Debug, Eq, PartialEq, PartialOrd, Ord)]
pub(crate) struct ChineseBasedDateInner<C>(
pub(crate) struct ChineseBasedDateInner<C: CalendarArithmetic>(
pub(crate) ArithmeticDate<C>,
pub(crate) ChineseBasedYearInfo,
);

// we want these impls without the `C: Copy/Clone` bounds
impl<C> Copy for ChineseBasedDateInner<C> {}
impl<C> Clone for ChineseBasedDateInner<C> {
impl<C: CalendarArithmetic> Copy for ChineseBasedDateInner<C> {}
impl<C: CalendarArithmetic> Clone for ChineseBasedDateInner<C> {
fn clone(&self) -> Self {
*self
}
Expand Down Expand Up @@ -240,7 +240,7 @@ impl ChineseBasedCompiledData {
}
}

impl<C: ChineseBasedWithDataLoading> ChineseBasedDateInner<C> {
impl<C: ChineseBasedWithDataLoading + CalendarArithmetic<YearInfo = ()>> ChineseBasedDateInner<C> {
/// Given a 1-indexed chinese extended year, fetch its data from the cache.
///
/// If the actual year data that was fetched is for a different year, update the getter year
Expand Down Expand Up @@ -467,6 +467,8 @@ impl<C: ChineseBasedWithDataLoading> ChineseBasedDateInner<C> {
}

impl<C: ChineseBasedWithDataLoading> CalendarArithmetic for C {
type YearInfo = ();

fn month_days(year: i32, month: u8) -> u8 {
chinese_based::month_days::<C::CB>(year, month)
}
Expand Down
4 changes: 3 additions & 1 deletion components/calendar/src/coptic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,8 @@ pub struct Coptic;
pub struct CopticDateInner(pub(crate) ArithmeticDate<Coptic>);

impl CalendarArithmetic for Coptic {
type YearInfo = ();

fn month_days(year: i32, month: u8) -> u8 {
if (1..=12).contains(&month) {
30
Expand Down Expand Up @@ -157,7 +159,7 @@ impl Calendar for Coptic {
}

fn offset_date(&self, date: &mut Self::DateInner, offset: DateDuration<Self>) {
date.0.offset_date(offset);
date.0.offset_date(offset, &());
}

#[allow(clippy::field_reassign_with_default)]
Expand Down
2 changes: 1 addition & 1 deletion components/calendar/src/dangi.rs
Original file line number Diff line number Diff line change
Expand Up @@ -181,7 +181,7 @@ impl Calendar for Dangi {

fn offset_date(&self, date: &mut Self::DateInner, offset: crate::DateDuration<Self>) {
let year = date.0 .0.year;
date.0 .0.offset_date(offset);
date.0 .0.offset_date(offset, &());
if date.0 .0.year != year {
date.0 .1 = ChineseBasedYearInfo::get_year_info::<Dangi>(year);
}
Expand Down
4 changes: 3 additions & 1 deletion components/calendar/src/ethiopian.rs
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,8 @@ pub struct Ethiopian(pub(crate) bool);
pub struct EthiopianDateInner(ArithmeticDate<Ethiopian>);

impl CalendarArithmetic for Ethiopian {
type YearInfo = ();

fn month_days(year: i32, month: u8) -> u8 {
if (1..=12).contains(&month) {
30
Expand Down Expand Up @@ -179,7 +181,7 @@ impl Calendar for Ethiopian {
}

fn offset_date(&self, date: &mut Self::DateInner, offset: DateDuration<Self>) {
date.0.offset_date(offset);
date.0.offset_date(offset, &());
}

#[allow(clippy::field_reassign_with_default)]
Expand Down
10 changes: 6 additions & 4 deletions components/calendar/src/hebrew.rs
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,8 @@ impl Hebrew {
// HEBREW CALENDAR

impl CalendarArithmetic for Hebrew {
type YearInfo = ();

fn month_days(civil_year: i32, civil_month: u8) -> u8 {
Self::last_day_of_civil_hebrew_month(civil_year, civil_month)
}
Expand Down Expand Up @@ -180,7 +182,7 @@ impl Calendar for Hebrew {
}
};

ArithmeticDate::new_from_lunar_ordinals(year, month_ordinal, day).map(HebrewDateInner)
ArithmeticDate::new_from_ordinals(year, month_ordinal, day).map(HebrewDateInner)
}

fn date_from_iso(&self, iso: Date<Iso>) -> Self::DateInner {
Expand All @@ -206,7 +208,7 @@ impl Calendar for Hebrew {
}

fn offset_date(&self, date: &mut Self::DateInner, offset: DateDuration<Self>) {
date.0.offset_date(offset)
date.0.offset_date(offset, &())
}

fn until(
Expand Down Expand Up @@ -301,7 +303,7 @@ impl Hebrew {
fn biblical_to_civil_date(biblical_date: BookHebrew) -> HebrewDateInner {
let (y, m, d) = biblical_date.to_civil_date();

debug_assert!(ArithmeticDate::<Hebrew>::new_from_lunar_ordinals(y, m, d,).is_ok());
debug_assert!(ArithmeticDate::<Hebrew>::new_from_ordinals(y, m, d,).is_ok());
HebrewDateInner(ArithmeticDate::new_unchecked(y, m, d))
}

Expand Down Expand Up @@ -373,7 +375,7 @@ impl<A: AsCalendar<Calendar = Hebrew>> Date<A> {
day: u8,
calendar: A,
) -> Result<Date<A>, CalendarError> {
ArithmeticDate::new_from_lunar_ordinals(year, month, day)
ArithmeticDate::new_from_ordinals(year, month, day)
.map(HebrewDateInner)
.map(|inner| Date::from_raw(inner, calendar))
}
Expand Down
4 changes: 3 additions & 1 deletion components/calendar/src/indian.rs
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,8 @@ pub struct Indian;
pub struct IndianDateInner(ArithmeticDate<Indian>);

impl CalendarArithmetic for Indian {
type YearInfo = ();

fn month_days(year: i32, month: u8) -> u8 {
if month == 1 {
if Self::is_leap_year(year) {
Expand Down Expand Up @@ -176,7 +178,7 @@ impl Calendar for Indian {
}

fn offset_date(&self, date: &mut Self::DateInner, offset: DateDuration<Self>) {
date.0.offset_date(offset);
date.0.offset_date(offset, &());
}

#[allow(clippy::field_reassign_with_default)]
Expand Down
Loading