Skip to content

Commit

Permalink
Thread through caching in chinese based calendars (#4411)
Browse files Browse the repository at this point in the history
  • Loading branch information
Manishearth authored Dec 13, 2023
1 parent 160cecf commit df7a7ef
Show file tree
Hide file tree
Showing 16 changed files with 464 additions and 451 deletions.
92 changes: 58 additions & 34 deletions components/calendar/src/calendar_arithmetic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -76,29 +76,35 @@ pub(crate) trait CalendarArithmetic: Calendar {
/// 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;
fn last_month_day_in_year(year: i32) -> (u8, u8);

// 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 month_days(year: i32, month: u8, year_info: Self::YearInfo) -> u8;
fn months_for_every_year(year: i32, year_info: Self::YearInfo) -> u8;
fn is_leap_year(year: i32, year_info: Self::YearInfo) -> bool;
fn last_month_day_in_year(year: i32, year_info: Self::YearInfo) -> (u8, u8);

/// 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: i32) -> u16 {
let months_in_year = Self::months_for_every_year(year);
fn days_in_provided_year(year: i32, year_info: Self::YearInfo) -> u16 {
let months_in_year = Self::months_for_every_year(year, year_info);
let mut days: u16 = 0;
for month in 1..=months_in_year {
days += Self::month_days(year, month) as u16;
days += Self::month_days(year, month, year_info) as u16;
}
days
}
}

pub(crate) trait PrecomputedDataSource<YearInfo> {
/// Given a calendar year, load (or compute) the YearInfo for it
///
/// In the future we may pass in an optional previous YearInfo alongside the year
/// it matches to allow code to take shortcuts.
fn load_or_compute_info(&self, year: i32) -> YearInfo;
}

Expand All @@ -113,11 +119,21 @@ impl<C: CalendarArithmetic> ArithmeticDate<C> {
where
C: CalendarArithmetic<YearInfo = ()>,
{
Self::new_unchecked_with_info(year, month, day, ())
}
/// Create a new `ArithmeticDate` without checking that `month` and `day` are in bounds.
#[inline]
pub const fn new_unchecked_with_info(
year: i32,
month: u8,
day: u8,
year_info: C::YearInfo,
) -> Self {
ArithmeticDate {
year,
month,
day,
year_info: (),
year_info,
marker: PhantomData,
}
}
Expand All @@ -142,7 +158,7 @@ impl<C: CalendarArithmetic> ArithmeticDate<C> {
C: CalendarArithmetic<YearInfo = ()>,
{
let year = i32::MAX;
let (month, day) = C::last_month_day_in_year(year);
let (month, day) = C::last_month_day_in_year(year, ());
ArithmeticDate {
year: i32::MAX,
month,
Expand All @@ -153,15 +169,15 @@ impl<C: CalendarArithmetic> ArithmeticDate<C> {
}

#[inline]
fn offset_days(&mut self, mut day_offset: i32) {
fn offset_days(&mut self, mut day_offset: i32, data: &impl PrecomputedDataSource<C::YearInfo>) {
while day_offset != 0 {
let month_days = C::month_days(self.year, self.month);
let month_days = C::month_days(self.year, self.month, self.year_info);
if self.day as i32 + day_offset > month_days as i32 {
self.offset_months(1);
self.offset_months(1, data);
day_offset -= month_days as i32;
} else if self.day as i32 + day_offset < 1 {
self.offset_months(-1);
day_offset += C::month_days(self.year, self.month) as i32;
self.offset_months(-1, data);
day_offset += C::month_days(self.year, self.month, self.year_info) as i32;
} else {
self.day = (self.day as i32 + day_offset) as u8;
day_offset = 0;
Expand All @@ -170,15 +186,21 @@ impl<C: CalendarArithmetic> ArithmeticDate<C> {
}

#[inline]
fn offset_months(&mut self, mut month_offset: i32) {
fn offset_months(
&mut self,
mut month_offset: i32,
data: &impl PrecomputedDataSource<C::YearInfo>,
) {
while month_offset != 0 {
let year_months = C::months_for_every_year(self.year);
let year_months = C::months_for_every_year(self.year, self.year_info);
if self.month as i32 + month_offset > year_months as i32 {
self.year += 1;
self.year_info = data.load_or_compute_info(self.year);
month_offset -= year_months as i32;
} else if self.month as i32 + month_offset < 1 {
self.year -= 1;
month_offset += C::months_for_every_year(self.year) as i32;
self.year_info = data.load_or_compute_info(self.year);
month_offset += C::months_for_every_year(self.year, self.year_info) as i32;
} else {
self.month = (self.month as i32 + month_offset) as u8;
month_offset = 0
Expand All @@ -192,15 +214,17 @@ impl<C: CalendarArithmetic> ArithmeticDate<C> {
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);
if offset.years != 0 {
// 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);
self.offset_months(offset.months, data);

let day_offset = offset.days + offset.weeks * 7 + self.day as i32 - 1;
self.day = 1;
self.offset_days(day_offset);
self.offset_days(day_offset, data);
}

#[inline]
Expand All @@ -222,24 +246,24 @@ impl<C: CalendarArithmetic> ArithmeticDate<C> {

#[inline]
pub fn days_in_year(&self) -> u16 {
C::days_in_provided_year(self.year)
C::days_in_provided_year(self.year, self.year_info)
}

#[inline]
pub fn months_in_year(&self) -> u8 {
C::months_for_every_year(self.year)
C::months_for_every_year(self.year, self.year_info)
}

#[inline]
pub fn days_in_month(&self) -> u8 {
C::month_days(self.year, self.month)
C::month_days(self.year, self.month, self.year_info)
}

#[inline]
pub fn day_of_year(&self) -> u16 {
let mut day_of_year = 0;
for month in 1..self.month {
day_of_year += C::month_days(self.year, month) as u16;
day_of_year += C::month_days(self.year, month, self.year_info) as u16;
}
day_of_year + (self.day as u16)
}
Expand All @@ -251,8 +275,8 @@ impl<C: CalendarArithmetic> ArithmeticDate<C> {
{
let mut month = 1;
let mut day = year_day as i32;
while month <= C::months_for_every_year(year) {
let month_days = C::month_days(year, month) as i32;
while month <= C::months_for_every_year(year, ()) {
let month_days = C::month_days(year, month, ()) as i32;
if day <= month_days {
break;
} else {
Expand All @@ -261,7 +285,7 @@ impl<C: CalendarArithmetic> ArithmeticDate<C> {
}
}

debug_assert!(day <= C::month_days(year, month) as i32);
debug_assert!(day <= C::month_days(year, month, ()) as i32);
#[allow(clippy::unwrap_used)]
// The day is expected to be within the range of month_days of C
ArithmeticDate {
Expand All @@ -288,7 +312,7 @@ impl<C: CalendarArithmetic> ArithmeticDate<C> {
#[inline]
pub fn month(&self) -> types::FormattableMonth {
let code = match self.month {
a if a > C::months_for_every_year(self.year) => tinystr!(4, "und"),
a if a > C::months_for_every_year(self.year, self.year_info) => tinystr!(4, "und"),
1 => tinystr!(4, "M01"),
2 => tinystr!(4, "M02"),
3 => tinystr!(4, "M03"),
Expand Down Expand Up @@ -333,14 +357,14 @@ impl<C: CalendarArithmetic> ArithmeticDate<C> {
));
};

if month > C::months_for_every_year(year) {
if month > C::months_for_every_year(year, ()) {
return Err(CalendarError::UnknownMonthCode(
month_code.0,
cal.debug_name(),
));
}

let max_day = C::month_days(year, month);
let max_day = C::month_days(year, month, ());
if day > max_day {
return Err(CalendarError::Overflow {
field: "day",
Expand All @@ -358,14 +382,14 @@ impl<C: CalendarArithmetic> ArithmeticDate<C> {
where
C: CalendarArithmetic<YearInfo = ()>,
{
let max_month = C::months_for_every_year(year);
let max_month = C::months_for_every_year(year, ());
if month > max_month {
return Err(CalendarError::Overflow {
field: "month",
max: max_month as usize,
});
}
let max_day = C::month_days(year, month);
let max_day = C::month_days(year, month, ());
if day > max_day {
return Err(CalendarError::Overflow {
field: "day",
Expand Down
Loading

0 comments on commit df7a7ef

Please sign in to comment.