Skip to content
Merged
Show file tree
Hide file tree
Changes from 24 commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
1d3648e
DateAdd, DateUntil implemented. Stuck in trait land
sffc Oct 1, 2025
2a38bfe
only failing file is abstract_gregorian
sffc Oct 1, 2025
40f70a0
fix abstract_gregorian
robertbastian Oct 1, 2025
d65db3f
Tests compiling; lots of docs tests
sffc Oct 3, 2025
7447707
More tests passing
sffc Oct 3, 2025
547ccac
All tests passing in icu_calendar?
sffc Oct 3, 2025
457b3f1
Merge branch 'main' into dateadduntil
sffc Oct 3, 2025
f683574
Delete commented-out test
sffc Oct 3, 2025
faf38ac
diplomat coverage
sffc Oct 3, 2025
d9ab920
clippy
sffc Oct 3, 2025
860c630
Add another test
sffc Oct 3, 2025
b31cf42
Actually add the file
sffc Oct 3, 2025
b332e05
Hebrew tests
sffc Oct 3, 2025
f8ec962
fmt
sffc Oct 3, 2025
c4059a4
Add more Hebrew tests
sffc Oct 3, 2025
3c43228
clippy
sffc Oct 3, 2025
f1e4dbd
Add PartialEq
Manishearth Oct 6, 2025
300361d
Add tricky Hebrew standalone test case
sffc Oct 7, 2025
a5890fd
Fix leap month arithmetic and re-enable tests
sffc Oct 7, 2025
daa6f27
Renames, docs, comments, fmt
sffc Oct 7, 2025
4b03c6d
ci
sffc Oct 7, 2025
bca8cae
Fix benchmark and set AnyCalendarDifferenceError to hidden
sffc Oct 7, 2025
ad1c54a
feedback
sffc Oct 8, 2025
f999bc7
Add test comparing against RataDie diff
sffc Oct 8, 2025
3ef45f3
Rewrite the macro to simplify code and reduce potential for bugs
sffc Oct 9, 2025
cc339fc
Merge branch 'main' into dateadduntil
sffc Oct 9, 2025
716b194
fmt, clippy
sffc Oct 9, 2025
8262b4a
Add missing docs
sffc Oct 9, 2025
ed48da7
revert controversial any_calendar refactorings
robertbastian Oct 10, 2025
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
4 changes: 4 additions & 0 deletions components/calendar/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -66,5 +66,9 @@ harness = false
name = "convert"
harness = false

[[test]]
name = "arithmetic"
required-features = ["ixdtf"]

[package.metadata.cargo-semver-checks.lints]
workspace = true
25 changes: 17 additions & 8 deletions components/calendar/benches/date.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,18 +20,27 @@ pub struct Test {
use criterion::{
black_box, criterion_group, criterion_main, measurement::WallTime, BenchmarkGroup, Criterion,
};
use icu_calendar::{AsCalendar, Calendar, Date, DateDuration};
use icu_calendar::{
options::{DateAddOptions, Overflow},
types, AsCalendar, Calendar, Date,
};

fn bench_date<A: AsCalendar>(date: &mut Date<A>) {
// black_box used to avoid compiler optimization.
// Arithmetic
date.add(DateDuration {
is_negative: false,
years: black_box(1),
months: black_box(2),
weeks: black_box(3),
days: black_box(4),
});
let mut options = DateAddOptions::default();
options.overflow = Some(Overflow::Constrain);
date.try_add_with_options(
types::DateDuration {
is_negative: false,
years: black_box(1),
months: black_box(2),
weeks: black_box(3),
days: black_box(4),
},
options,
)
.unwrap();

// Retrieving vals
let _ = black_box(date.year());
Expand Down
303 changes: 120 additions & 183 deletions components/calendar/src/any_calendar.rs

Large diffs are not rendered by default.

53 changes: 27 additions & 26 deletions components/calendar/src/cal/abstract_gregorian.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,10 @@ use crate::calendar_arithmetic::{
};
use crate::error::DateError;
use crate::options::DateFromFieldsOptions;
use crate::options::{DateAddOptions, DateDifferenceOptions};
use crate::preferences::CalendarAlgorithm;
use crate::types::EraYear;
use crate::{types, Calendar, DateDuration, DateDurationUnit, RangeError};
use crate::{types, Calendar, RangeError};
use calendrical_calculations::helpers::I32CastError;
use calendrical_calculations::rata_die::RataDie;

Expand Down Expand Up @@ -43,8 +44,6 @@ impl ArithmeticDate<AbstractGregorian<IsoEra>> {
}

impl CalendarArithmetic for AbstractGregorian<IsoEra> {
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 {
Expand Down Expand Up @@ -113,6 +112,7 @@ impl<Y: GregorianYears> crate::cal::scaffold::UnstableSealed for AbstractGregori
impl<Y: GregorianYears> Calendar for AbstractGregorian<Y> {
type DateInner = ArithmeticDate<AbstractGregorian<IsoEra>>;
type Year = types::EraYear;
type DifferenceError = core::convert::Infallible;

fn from_fields(
&self,
Expand Down Expand Up @@ -156,19 +156,22 @@ impl<Y: GregorianYears> Calendar for AbstractGregorian<Y> {
date.days_in_month()
}

fn offset_date(&self, date: &mut Self::DateInner, offset: DateDuration) {
date.offset_date(offset, &());
fn add(
&self,
date: &Self::DateInner,
duration: types::DateDuration,
options: DateAddOptions,
) -> Result<Self::DateInner, DateError> {
date.added(duration, &AbstractGregorian(IsoEra), options)
}

fn until(
&self,
date1: &Self::DateInner,
date2: &Self::DateInner,
_calendar2: &Self,
_largest_unit: DateDurationUnit,
_smallest_unit: DateDurationUnit,
) -> DateDuration {
date1.until(*date2, _largest_unit, _smallest_unit)
options: DateDifferenceOptions,
) -> Result<types::DateDuration, Self::DifferenceError> {
Ok(date1.until(date2, &AbstractGregorian(IsoEra), options))
}

fn year_info(&self, date: &Self::DateInner) -> Self::Year {
Expand All @@ -181,7 +184,7 @@ impl<Y: GregorianYears> Calendar for AbstractGregorian<Y> {
}

fn month(&self, date: &Self::DateInner) -> types::MonthInfo {
date.month()
self.month_code_from_ordinal(&date.year, date.month)
}

fn day_of_month(&self, date: &Self::DateInner) -> types::DayOfMonth {
Expand Down Expand Up @@ -214,6 +217,7 @@ macro_rules! impl_with_abstract_gregorian {
impl crate::Calendar for $cal_ty {
type DateInner = $inner_date_ty;
type Year = types::EraYear;
type DifferenceError = core::convert::Infallible;
fn from_fields(
&self,
fields: crate::types::DateFields,
Expand Down Expand Up @@ -265,30 +269,27 @@ macro_rules! impl_with_abstract_gregorian {
crate::cal::abstract_gregorian::AbstractGregorian($eras_expr).days_in_month(&date.0)
}

fn offset_date(&self, date: &mut Self::DateInner, offset: crate::DateDuration) {
fn add(
&self,
date: &Self::DateInner,
duration: crate::types::DateDuration,
options: crate::options::DateAddOptions,
) -> Result<Self::DateInner, DateError> {
let $self_ident = self;
let mut inner = date.0;
crate::cal::abstract_gregorian::AbstractGregorian($eras_expr)
.offset_date(&mut inner, offset);
date.0 = inner;
.add(&date.0, duration, options)
.map($inner_date_ty)
}

fn until(
&self,
date1: &Self::DateInner,
date2: &Self::DateInner,
_calendar2: &Self,
largest_unit: crate::DateDurationUnit,
smallest_unit: crate::DateDurationUnit,
) -> crate::DateDuration {
options: crate::options::DateDifferenceOptions,
) -> Result<crate::types::DateDuration, Self::DifferenceError> {
let $self_ident = self;
crate::cal::abstract_gregorian::AbstractGregorian($eras_expr).until(
&date1.0,
&date2.0,
&crate::cal::abstract_gregorian::AbstractGregorian($eras_expr),
largest_unit,
smallest_unit,
)
crate::cal::abstract_gregorian::AbstractGregorian($eras_expr)
.until(&date1.0, &date2.0, options)
}

fn year_info(&self, date: &Self::DateInner) -> Self::Year {
Expand Down
47 changes: 27 additions & 20 deletions components/calendar/src/cal/chinese.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,17 @@
// (online at: https://github.com/unicode-org/icu4x/blob/main/LICENSE ).

use crate::cal::iso::{Iso, IsoDateInner};
use crate::calendar_arithmetic::{ArithmeticDate, ArithmeticDateBuilder, CalendarArithmetic};
use crate::calendar_arithmetic::{
ArithmeticDate, ArithmeticDateBuilder, CalendarArithmetic, ToExtendedYear,
};
use crate::calendar_arithmetic::{DateFieldsResolver, PrecomputedDataSource};
use crate::error::DateError;
use crate::options::{DateAddOptions, DateDifferenceOptions};
use crate::options::{DateFromFieldsOptions, Overflow};
use crate::provider::chinese_based::PackedChineseBasedYearInfo;
use crate::types::{MonthCode, MonthInfo};
use crate::AsCalendar;
use crate::{types, Calendar, Date, DateDuration, DateDurationUnit};
use crate::{types, Calendar, Date};
use calendrical_calculations::chinese_based::{
self, ChineseBased, YearBounds, WELL_BEHAVED_ASTRONOMICAL_RANGE,
};
Expand Down Expand Up @@ -493,8 +496,6 @@ impl LunarChinese<China> {
}

impl<R: Rules> CalendarArithmetic for LunarChinese<R> {
type YearInfo = LunarChineseYearData;

fn days_in_provided_month(year: LunarChineseYearData, month: u8) -> u8 {
year.days_in_month(month)
}
Expand Down Expand Up @@ -573,12 +574,21 @@ impl<R: Rules> DateFieldsResolver for LunarChinese<R> {
_ => Err(DateError::UnknownMonthCode(month_code)),
}
}

fn month_code_from_ordinal(
&self,
year: &Self::YearInfo,
ordinal_month: u8,
) -> types::MonthInfo {
year.month(ordinal_month)
}
}

impl<R: Rules> crate::cal::scaffold::UnstableSealed for LunarChinese<R> {}
impl<R: Rules> Calendar for LunarChinese<R> {
type DateInner = ChineseDateInner<R>;
type Year = types::CyclicYear;
type DifferenceError = core::convert::Infallible;

fn from_fields(
&self,
Expand Down Expand Up @@ -641,25 +651,22 @@ impl<R: Rules> Calendar for LunarChinese<R> {
date.0.days_in_month()
}

#[doc(hidden)] // unstable
fn offset_date(&self, date: &mut Self::DateInner, offset: DateDuration) {
date.0.offset_date(offset, self);
fn add(
&self,
date: &Self::DateInner,
duration: types::DateDuration,
options: DateAddOptions,
) -> Result<Self::DateInner, DateError> {
date.0.added(duration, self, options).map(ChineseDateInner)
}

#[doc(hidden)] // unstable
/// Calculate `date2 - date` as a duration
///
/// `calendar2` is the calendar object associated with `date2`. In case the specific calendar objects
/// differ on date, the date for the first calendar is used, and `date2` may be converted if necessary.
fn until(
&self,
date1: &Self::DateInner,
date2: &Self::DateInner,
_calendar2: &Self,
_largest_unit: DateDurationUnit,
_smallest_unit: DateDurationUnit,
) -> DateDuration {
date1.0.until(date2.0, _largest_unit, _smallest_unit)
options: DateDifferenceOptions,
) -> Result<types::DateDuration, Self::DifferenceError> {
Ok(date1.0.until(&date2.0, self, options))
}

/// Obtain a name for the calendar for debug printing
Expand Down Expand Up @@ -756,9 +763,9 @@ pub struct LunarChineseYearData {
pub(crate) related_iso: i32,
}

impl From<LunarChineseYearData> for i32 {
fn from(value: LunarChineseYearData) -> Self {
value.related_iso
impl ToExtendedYear for LunarChineseYearData {
fn to_extended_year(&self) -> i32 {
self.related_iso
}
}

Expand Down
26 changes: 15 additions & 11 deletions components/calendar/src/cal/coptic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ use crate::calendar_arithmetic::{ArithmeticDate, CalendarArithmetic};
use crate::calendar_arithmetic::{ArithmeticDateBuilder, DateFieldsResolver};
use crate::error::DateError;
use crate::options::DateFromFieldsOptions;
use crate::{types, Calendar, Date, DateDuration, DateDurationUnit, RangeError};
use crate::options::{DateAddOptions, DateDifferenceOptions};
use crate::{types, Calendar, Date, RangeError};
use calendrical_calculations::helpers::I32CastError;
use calendrical_calculations::rata_die::RataDie;
use tinystr::tinystr;
Expand Down Expand Up @@ -39,8 +40,6 @@ pub struct Coptic;
pub struct CopticDateInner(pub(crate) ArithmeticDate<Coptic>);

impl CalendarArithmetic for Coptic {
type YearInfo = i32;

fn days_in_provided_month(year: i32, month: u8) -> u8 {
if (1..=12).contains(&month) {
30
Expand Down Expand Up @@ -133,6 +132,8 @@ impl crate::cal::scaffold::UnstableSealed for Coptic {}
impl Calendar for Coptic {
type DateInner = CopticDateInner;
type Year = types::EraYear;
type DifferenceError = core::convert::Infallible;

fn from_fields(
&self,
fields: types::DateFields,
Expand Down Expand Up @@ -178,19 +179,22 @@ impl Calendar for Coptic {
date.0.days_in_month()
}

fn offset_date(&self, date: &mut Self::DateInner, offset: DateDuration) {
date.0.offset_date(offset, &());
fn add(
&self,
date: &Self::DateInner,
duration: types::DateDuration,
options: DateAddOptions,
) -> Result<Self::DateInner, DateError> {
date.0.added(duration, self, options).map(CopticDateInner)
}

fn until(
&self,
date1: &Self::DateInner,
date2: &Self::DateInner,
_calendar2: &Self,
_largest_unit: DateDurationUnit,
_smallest_unit: DateDurationUnit,
) -> DateDuration {
date1.0.until(date2.0, _largest_unit, _smallest_unit)
options: DateDifferenceOptions,
) -> Result<types::DateDuration, Self::DifferenceError> {
Ok(date1.0.until(&date2.0, self, options))
}

fn year_info(&self, date: &Self::DateInner) -> Self::Year {
Expand All @@ -209,7 +213,7 @@ impl Calendar for Coptic {
}

fn month(&self, date: &Self::DateInner) -> types::MonthInfo {
date.0.month()
self.month_code_from_ordinal(&date.0.year, date.0.month)
}

fn day_of_month(&self, date: &Self::DateInner) -> types::DayOfMonth {
Expand Down
24 changes: 16 additions & 8 deletions components/calendar/src/cal/ethiopian.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,9 @@ use crate::cal::Coptic;
use crate::calendar_arithmetic::{ArithmeticDate, ArithmeticDateBuilder, DateFieldsResolver};
use crate::error::DateError;
use crate::options::DateFromFieldsOptions;
use crate::options::{DateAddOptions, DateDifferenceOptions};
use crate::types::DateFields;
use crate::{types, Calendar, Date, DateDuration, DateDurationUnit, RangeError};
use crate::{types, Calendar, Date, RangeError};
use calendrical_calculations::rata_die::RataDie;
use tinystr::tinystr;

Expand Down Expand Up @@ -103,6 +104,8 @@ impl crate::cal::scaffold::UnstableSealed for Ethiopian {}
impl Calendar for Ethiopian {
type DateInner = EthiopianDateInner;
type Year = types::EraYear;
type DifferenceError = core::convert::Infallible;

fn from_fields(
&self,
fields: DateFields,
Expand Down Expand Up @@ -143,19 +146,24 @@ impl Calendar for Ethiopian {
Coptic.days_in_month(&date.0)
}

fn offset_date(&self, date: &mut Self::DateInner, offset: DateDuration) {
Coptic.offset_date(&mut date.0, offset);
fn add(
&self,
date: &Self::DateInner,
duration: types::DateDuration,
options: DateAddOptions,
) -> Result<Self::DateInner, DateError> {
Coptic
.add(&date.0, duration, options)
.map(EthiopianDateInner)
}

fn until(
&self,
date1: &Self::DateInner,
date2: &Self::DateInner,
_calendar2: &Self,
largest_unit: DateDurationUnit,
smallest_unit: DateDurationUnit,
) -> DateDuration {
Coptic.until(&date1.0, &date2.0, &Coptic, largest_unit, smallest_unit)
options: DateDifferenceOptions,
) -> Result<types::DateDuration, Self::DifferenceError> {
Coptic.until(&date1.0, &date2.0, options)
}

fn year_info(&self, date: &Self::DateInner) -> Self::Year {
Expand Down
Loading