Skip to content

Conversation

@robertbastian
Copy link
Member

@robertbastian robertbastian commented Nov 7, 2025

Changed the range from applying to era years to applying to extended years. We cannot apply it to era years, as Japanese has data-driven eras higher than any era from any calendar, so a new Japanese era would need a bigger RD range.

#7076

/// Construct a date from a [`RataDie`] and some calendar representation
/// The lowest [`RataDie`] that will round-trip through [`Date::from_rata_die`]
/// and [`Date::to_rata_die`].
pub const LOWEST_RATA_DIE: RataDie = *VALID_RD_RANGE.start();
Copy link
Member

Choose a reason for hiding this comment

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

note: we should make sure this new public API is where we want it

I think it is.

Copy link
Member

Choose a reason for hiding this comment

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

Alternative:

Date::new_max().to_rata_die()

new_max returns Date<Iso>

Copy link
Member

Choose a reason for hiding this comment

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

I think to_rata_die() can't be called in const

Copy link
Member Author

Choose a reason for hiding this comment

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

I want to stop treating ISO as a special calendar, RD is the calendar-neutral date representation. Also I don't want to expose this as the max date, only as the max input for the from_rata_die function.

Copy link
Member

Choose a reason for hiding this comment

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

I think that's reasonable.

Copy link
Member Author

Choose a reason for hiding this comment

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

hmm it's not ideal, because Date is generic and callers have to pick a calendar to access the const, even though it's calendar-agnostic

Copy link
Member

Choose a reason for hiding this comment

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

If from_rata_die saturates, people can get the max via

Date::from_rata_die(RataDie::MAX_VALUE).to_rata_die()

Copy link
Member Author

Choose a reason for hiding this comment

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

RataDie::MAX_VALUE does not exist though

Copy link
Member

Choose a reason for hiding this comment

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

And we probably shouldn't add one since this is an icu_calendar constraint, not a calendrical_calculations constraint.

I'm not overly bothered by this value requiring you to specify a calendar. I don't expect this to be used a lot.

Copy link
Member

Choose a reason for hiding this comment

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

I had intended to observe that if we were to add RataDie::MAX_VALUE returning i64::MAX in calendrical_calculations, icu_calendar still results in it being constrained.

/// Construct a new Buddhist [`Date`].
///
/// Years are specified as BE years.
/// Years are arithmetic, meaning there is a year 0 preceded by negative years, with a
Copy link
Member

Choose a reason for hiding this comment

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

Nit: This sentence doesn't read correctly for me. The word "arithmetic" is a noun by default, so "years are arithmetic" doesn't make sense. If using "arithmetic" as an adjective, please don't use it in a position in the sentence where it could be interpreted as a noun.

Copy link
Member Author

Choose a reason for hiding this comment

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

I didn't write this

/// Years are arithmetic, meaning there is a year 0. Zero and negative years are in BC, with year 0 = 1 BC

(Some(era), Some(era_year)) => {
let era_year_as_year_info = calendar
.year_info_from_era(era, range_check(era_year, "year", VALID_YEAR_RANGE)?)?;
let year = calendar.year_info_from_era(era, era_year)?;
Copy link
Member

Choose a reason for hiding this comment

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

Issue: The era_year shouldn't get plumbed into the calendar impl until we've checked that it is in some reasonable range, maybe +/- 1e7, to allow calendars like simplified chinese to make assumptions that years aren't too big.

Copy link
Member

Choose a reason for hiding this comment

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

I don't think this is fixed. It looks like you fixed it for calendar-specific constructors, but not for from-codes or from-fields.

/// Construct a date from a [`RataDie`] and some calendar representation
/// The lowest [`RataDie`] that will round-trip through [`Date::from_rata_die`]
/// and [`Date::to_rata_die`].
pub const LOWEST_RATA_DIE: RataDie = *VALID_RD_RANGE.start();
Copy link
Member

Choose a reason for hiding this comment

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

Alternative:

Date::new_max().to_rata_die()

new_max returns Date<Iso>

Comment on lines 67 to 68
/// [`Date::try_from_fields`](crate::Date::try_from_fields) accepts years that correspond to
/// `extended_year`s in the range `-1,000,000..=1,000,000`.
Copy link
Member

Choose a reason for hiding this comment

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

Suggestion/Issue: I think this should range-check the era year to +/- 1e6.

The range check should either be on the input or on the output. If it is on the input, it should check the era year. If it is on the output, it should check the rata die. It doesn't make sense to range check the extended year, since it is the output of a calculation, which is valid so long as the Date is in range of its invariant.

Copy link
Member Author

Choose a reason for hiding this comment

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

as explained in the PR description, that doesn't work because anyone can add a Japanese era that starts in the year 2e6.

@Manishearth
Copy link
Member

Can we special case Japanese so that it checks in eraYear range and then also checks RD ranges?

@sffc
Copy link
Member

sffc commented Nov 7, 2025

My concern is that someone could give era: "reiwa", era_year: i32::MAX which would cause an arithmetic overflow unless we constrain era_year first.

@sffc
Copy link
Member

sffc commented Nov 7, 2025

I'm okay constraining both era year and extended year. The behavior would be, like,

Input Range Error Field Min Max Note
era: "reiwa", era_year: i32::MAX era_year -1e6 1e6
extended_year: i32::MAX extended_year ^ ^
era: "reiwa", era_year: 1e6 extended_year ^ ^ Valid era year but not extended year

@Manishearth
Copy link
Member

Yeah, that's my concern too, and that's the blocking part of my comment there.

@Manishearth
Copy link
Member

Hmm, doing a double check everywhere is fine, I guess. I was hoping we could just check on the inputted year and specifically in the Japanese case constrain extra.

But this is fine.

Copy link
Member

@sffc sffc left a comment

Choose a reason for hiding this comment

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

Would really love to see tests for the limits across the constructors.

(Some(era), Some(era_year)) => {
let era_year_as_year_info = calendar
.year_info_from_era(era, range_check(era_year, "year", VALID_YEAR_RANGE)?)?;
let year = calendar.year_info_from_era(era, era_year)?;
Copy link
Member

Choose a reason for hiding this comment

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

I don't think this is fixed. It looks like you fixed it for calendar-specific constructors, but not for from-codes or from-fields.

Copy link
Member

@Manishearth Manishearth left a comment

Choose a reason for hiding this comment

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

I think this seems about right!

If you would like you can remove the guards in the fuzz tests but don't worry about it, I can do that myself later.

@sffc
Copy link
Member

sffc commented Nov 10, 2025

(I do consider tests to be a requirement for shipping this new "feature", but they can be in a follow-up if necessary)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants