Skip to content

Use more specific calendar-dependent operations in Until, Add, and ToISO #69

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

Open
wants to merge 6 commits into
base: main
Choose a base branch
from

Conversation

sffc
Copy link
Collaborator

@sffc sffc commented Jul 26, 2025

Fixes #55. More steps toward #57.

This PR is based on #66, and it also depends on #68 and tc39/proposal-temporal#3138.

I rewrote NonISODateAdd, NonISODateUntil, and NonISOCalendarDateToISO to more precisely specify their algorithms. They are still ultimately calendar-dependent, but the calendar-dependent parts are boiled down to more bite-sized abstract operations:

  • CalendarDateArithmeticalToISO
  • CalendarDaysInMonth
  • CalendarMonthsInYear
  • YearContainsMonthCode

@sffc sffc force-pushed the 55-more-specific-ops branch 3 times, most recently from ca08cb5 to d423175 Compare July 29, 2025 06:35
@sffc sffc force-pushed the 55-more-specific-ops branch from a72de40 to 12195b9 Compare July 29, 2025 07:37
@sffc sffc changed the title Use more specific calendar-dependent operations in NonISODateUntil Use more specific calendar-dependent operations in Until, Add, and ToISO Jul 29, 2025
@sffc sffc requested review from ptomato and ryzokuken July 29, 2025 07:57
Copy link
Contributor

@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.

Partial review up til NonISODateSurpasses

<emu-clause id="sec-temporal-parsemonthcode" type="implementation-defined abstract operation">
<h1>
ParseMonthCode (
_monthCode_: a String,
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: I think you can say something like a String which is a valid month code

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I was considering introducing a type for it. I say "valid month code" in the description. Happy for editorial advice from @ptomato or others.

Copy link
Member

Choose a reason for hiding this comment

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

I agree we should narrow down the possible set of month codes (which we already have) and define that (essentially mark the existing definition) as the term "month code" or "month code type" akin to "calendar type" that we utilize from upstream Temporal. I don't think it's a bad idea to place this definition in the Temporal spec since it can be utilized in the upstream AO https://tc39.es/proposal-temporal/#sec-temporal-tomonthcode.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Would you like me to add the definitions in this PR or can I do it in another PR? I'd also like to define terms like "arithmetical month"

spec.emu Outdated
1. Let _monthsBefore_ be 0.
1. Let _number_ be 1.
1. Let _isLeap_ be *false*.
1. Repeat, while _number_ &le; 12,
Copy link
Contributor

Choose a reason for hiding this comment

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

observation/praise: this will work for calendars that have multiple leap months in a year, like a hindu lunisolar calendar (especially a purnimanta calendar, where you are required to model fortnights as months since otherwise your leap months get "split" in half)

Implementors will likely optimize this to not need a for loop and instead query something like "does this year have a leap month and if so which month is it" which is potentially a useful other way of writing this spec text to avoid the loop. It falls within the type of thing that is possible to leave to implementors without confusion, though.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

To make it support Hindu (although Hindu is not yet in the spec), should I start _number_ at 0 in order to catch M00L?

Copy link
Collaborator Author

@sffc sffc Jul 29, 2025

Choose a reason for hiding this comment

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

I considered "does this year have a leap month and if so which month is it" but that is a more confusing calendar-dependent behavior, whereas "does year contain this particular month code" is more clear. I expect implementations to implement it as they see fit so long as the behaviors match.

Copy link
Contributor

Choose a reason for hiding this comment

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

I would not spend too much time thinking about the nitty gritty specifics of Hindu until we have a concrete proposal on the table since there are multiple ways to model ir. I don't think it needs an M00L, but it might. The fact that Hindu days can be "merged" is probably going to be the biggest problem.

Choose a reason for hiding this comment

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

To make it support Hindu (although Hindu is not yet in the spec), should I start _number_ at 0 in order to catch M00L?

Yes, I think the algorithm should be able to handle M00L. Ignoring that possibility just creates future liability. And likewise for hard-coding 12 rather than something more clear like HighestMonthNumberForCalendarYear(calendar, year).

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I resolved the month 0 / month 13 issue by adding an assertion in 6abbe6e

Was there anything else actionable from this thread?

spec.emu Outdated
1. Set _monthCode_ to *"M06"*.
1. Else if _isLeap1_ is *false* and _isLeap2_ is *true*, then
1. If _monthCode_ is *"M06"*, then
1. Set _monthCode_ to *"M05L"*.
Copy link
Contributor

Choose a reason for hiding this comment

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

interesting choice; any particular reason behind it?

Not that I dislike it, but I would have thought keeping M06 as M06 would be clearer since Adar II is supposed to be the "regular" Adar

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

#70

Choose a reason for hiding this comment

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

Please explain the intent of these steps with a "NOTE: The following steps…".

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I deleted this AO in 6abbe6e and replaced it with a table reference


<emu-clause id="sec-temporal-balancenonisodate" type="implementation-defined abstract operation">
<h1>
BalanceNonISODate (
Copy link
Contributor

Choose a reason for hiding this comment

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

praise: well written.

suggestion: I suppose this could be refactored into BalanceNonISOYearMonth and BalanceNonISODate to avoid the duplication of the resolvedMonth balancing. But I don't know if that's better.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Thanks. I want to highlight that this logic is novel to Intl Era Month Code; the equivalent ISO operation in Temporal does month balancing with divrem and then goes to Epoch Days for weeks and days.

I think I prefer keeping it in one AO because it is easier to see how month and day balancing interact.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I changed around the AOs in 6abbe6e

Copy link
Contributor

Choose a reason for hiding this comment

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

LGTM

spec.emu Outdated

<emu-clause id="sec-temporal-regulatenonisoyearmonth" type="implementation-defined abstract operation">
<h1>
RegulateNonISOYearMonth (
Copy link
Contributor

Choose a reason for hiding this comment

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

thought: I find this naming a bit confusing. In the original spec, "Regulate" is about applying overflow options, but here it's really doing a type of addition operation.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Yeah; my mental model was "regulate" means "if overflow, constrain the least significant unit" and "balance" means "if overflow, roll over the smallest unit into higher units". This AO does the regulating behavior, so that's how I named it. I'm happy to explore other naming options, or refactor exactly what role the AO serves.

Copy link
Member

Choose a reason for hiding this comment

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

ConstrainNonISOYearMonth sounds good as well but yeah don't mind Regulate either since it's consistent with upstream

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I replaced RegulateNonISOYearMonth with ConstrainMonthCodeToArithmeticalMonth in 6abbe6e

spec.emu Outdated
<p>It performs the following steps when called:</p>
<emu-alg>
1. Let _parts_ be CalendarISOToDate(_calendar_, _baseDate_).
1. Let _yearMonth_ be RegulateNonISOYearMonth(_calendar_, _parts_.[[Year]], _parts_.[[MonthCode]], _years_).
Copy link
Contributor

Choose a reason for hiding this comment

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

thought related to previous: My immediate response to this was "shouldn't the result of CalendarISOToDate already be regulated" before I remembered that RegulateNonISOYearMonth also does some year addition.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I acknowledge that this was confusing, and it should be fixed in 6abbe6e

1. Else,
1. Let _y1_ be _endOfMonth_.[[Year]].
1. Let _m1_ be _endOfMonth_.[[Month]].
1. Let _d1_ be _baseDay_.
Copy link
Contributor

Choose a reason for hiding this comment

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

issue: this doesn't clamp, but the previous one does. Is that a discrepancy we are okay with?

This means Surpasses(calendar, sign = 1, baseDate, isoDate2, years, months, weeks = 0, days = 0) could be true whilst Surpasses(calendar, sign = 1, baseDate, isoDate2, years, months, weels = 0, days = 1) could be false if, say, the other numbers were something like "add 1 month to to January 31st" since with days = 1 it will get normalized to Feb 28 + 1 (assume non leap year) but with days = 0 it'll get normalized to Feb 32.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

This matches the behavior in tc39/proposal-temporal#3138.

The AO has one call site, Until, which wants a true return value for things like "march 31 + 1 month ?< april 30", since the Temporal Addition Algorithm only regulates to the end of the month immediately before adding weeks and days.

I could have written this AO with an extra argument, regulationBehavior or something, or even as a completely different AO, but I realized that checking weeks == 0 and days == 0 was equivalent.

Copy link
Contributor

Choose a reason for hiding this comment

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

Hmm, okay. I don't fully understand the Until argument but I see there is a reason. Perhaps worth including a note.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Is this thread resolved?

Copy link
Contributor

Choose a reason for hiding this comment

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

Yes!

Copy link
Contributor

@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.

Very minor points overall. Looks good!

Implementing this efficiently may be tricky but that's fine; at least now we have more concrete guidance for behavior

spec.emu Outdated
1. Add _duration_.[[Months]] to _calendarDate_, balancing _calendarDate_ if it goes over a year boundary.
1. If the date described by _calendarDate_ does not exist, then
1. Let _parts_ be CalendarISOToDate(_calendar_, _isoDate_).
1. Let _yearMonth_ be RegulateNonISOYearMonth(_calendar_, _parts_.[[Year]], _parts_.[[MonthCode]], _duration_.[[Years]]).
Copy link
Contributor

Choose a reason for hiding this comment

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

observation: so we don't end up doing anything about the overflow with months, only with day

question: is there a reason we should try to be more consistent? is there a reason we should not?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Feel free to weigh in on #56. I took the position that overflow should be applied consistently for overflowed days only, and not have an extra error case in lunisolar.

Copy link
Contributor

Choose a reason for hiding this comment

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

Okay, that makes sense, because day is a final thing whereas months is still incomplete.

Might be worth having a note here to that effect.

Copy link
Contributor

Choose a reason for hiding this comment

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

Wait, hold on, but the day here is partial as well; we have not yet finished adding.

But I guess that's somewhat the behavior you ask for with REJECT.

1. Update _calendarDate_.[[Month]] accordingly.
1. Add _duration_.[[Months]] to _calendarDate_, balancing _calendarDate_ if it goes over a year boundary.
1. If the date described by _calendarDate_ does not exist, then
1. Let _parts_ be CalendarISOToDate(_calendar_, _isoDate_).
Copy link
Contributor

Choose a reason for hiding this comment

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

praise: I appreciate the simplicity of this AO

@sffc
Copy link
Collaborator Author

sffc commented Jul 29, 2025

A bit more commentary on my choices of AOs:

  1. RegulateNonISOYearMonth ( calendar, arithmeticalYear, monthCode, years )
    • This is a key calendar-dependent operation that adds years but not months. This is where we resolve common/leap month constraining issues in Chinese, Dangi, and Hebrew.
  2. BalanceNonISODate ( calendar, year, month, day )
    • This AO works the same across all calendars. There are zero special cases. It depends only on CalendarDaysInMonth and CalendarMonthsInYear.
  3. NonISODateSurpasses ( calendar, sign, baseDate, isoDate2, years, months, weeks, days )
    • This is my generalization of Editorial: Move regulating and balancing logic into ISODateSurpasses proposal-temporal#3138 to non-ISO. I tweaked the dependent AOs slightly. The ISO version combines the first few steps into a BalanceISOYearMonth, but that is a fundamentally different operation because it can be implemented with a DivRem. For non-ISO, I wanted to emphasize that there are multiple steps: first you add years, then you convert month codes to arithmetical months, then you continue with the rest of the balancing. This order of operations is crucial for the interoperability of calendar arithmetic.
  4. NonISODateAdd and NonISODateUntil

@sffc
Copy link
Collaborator Author

sffc commented Jul 29, 2025

To put Add and Until's algorithm in words:

  1. Add years
  2. Constrain to a valid year/month
  3. Add and balance months
  4. Constrain to a valid year/month/day
  5. Add and balance weeks and days

The key difference with non-ISO is step 2. ISO doesn't need to constrain to a valid year/month because all months are valid in all years.

This also feeds into #56. Is it more consistent or less consistent for non-ISO to reject when constraining year/month? I took the position that non-ISO is better off rejecting on step 4 only. I think it's a valid position that it should reject on both steps 2 and 4. However, maybe there should be a third option in the enum to allow for either behavior.

spec.emu Outdated
_calendar_: a calendar type that is not *"iso8601"*,
_year_: an integer,
_monthCode_: a String,
): an integer or ~invalid~

Choose a reason for hiding this comment

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

An integer result is always positive, right?

Suggested change
): an integer or ~invalid~
): a positive integer or ~invalid~

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Done in 3dacca7

spec.emu Outdated
Comment on lines 952 to 954
1. If _calendar_ is not *"chinese"*, *"dangi"*, or *"hebrew"*, then
1. Return _monthCodeParts_.[[Number]].
1. Let _monthsBefore_ be 0.

Choose a reason for hiding this comment

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

This kind of implicit assumption that all possible exceptions have been enumerated makes me uncomfortable. I'd like an assertion about what calendar must be if it is not "chinese", "dangi", or "hebrew", or better yet, representation of the underlying concept:

Suggested change
1. If _calendar_ is not *"chinese"*, *"dangi"*, or *"hebrew"*, then
1. Return _monthCodeParts_.[[Number]].
1. Let _monthsBefore_ be 0.
1. If CalendarHasLeapMonths(_calendar_) is *false*, then
1. Return _monthCodeParts_.[[Number]].
1. Let _monthsBefore_ be 0.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

In 6abbe6e, I am checking for leap months by the presence of a new column in #table-additional-month-codes.

spec.emu Outdated
1. Let _number_ be 1.
1. Let _isLeap_ be *false*.
1. Repeat, while _number_ &le; 12,
1. If _calendar_ contains CreateMonthCode(_number_, _isLeap_) in _year_, according to a calendar-dependent algorithm, then

Choose a reason for hiding this comment

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

Shouldn't this have a named operation?

Suggested change
1. If _calendar_ contains CreateMonthCode(_number_, _isLeap_) in _year_, according to a calendar-dependent algorithm, then
1. If IsValidYearMonthCodeForCalendar(_calendar_, _year_, CreateMonthCode(_number_, _isLeap_)) is *true*, then

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Yes, except the AO that we're currently inside is also how I would implement IsValidYearMonthCodeForCalendar... in fact this AO was called that in an earlier draft before I made it start returning an integer. I only need the sub-AO in order to determine if it is a valid leap month.

Choose a reason for hiding this comment

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

I'm more comfortable with the sub-AO than this kind of handwavy algorithm step. Or alternatively, with just making the entirety of MonthCodeToArithmeticalMonth calendar-dependent rather than trivially wrapping a calendar-dependent core.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I made a standalone AO YearContainsMonthCode in 6abbe6e.

spec.emu Outdated
1. Let _monthsBefore_ be 0.
1. Let _number_ be 1.
1. Let _isLeap_ be *false*.
1. Repeat, while _number_ &le; 12,

Choose a reason for hiding this comment

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

To make it support Hindu (although Hindu is not yet in the spec), should I start _number_ at 0 in order to catch M00L?

Yes, I think the algorithm should be able to handle M00L. Ignoring that possibility just creates future liability. And likewise for hard-coding 12 rather than something more clear like HighestMonthNumberForCalendarYear(calendar, year).

spec.emu Outdated
1. Repeat, while _number_ &le; 12,
1. If _calendar_ contains CreateMonthCode(_number_, _isLeap_) in _year_, according to a calendar-dependent algorithm, then
1. Set _monthsBefore_ to _monthsBefore_ + 1.
1. If _monthCodeParts_.[[Number]] is _number_ and _monthCodeParts.[[IsLeap]] is _isLeap_, then

Choose a reason for hiding this comment

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

Suggested change
1. If _monthCodeParts_.[[Number]] is _number_ and _monthCodeParts.[[IsLeap]] is _isLeap_, then
1. If _monthCodeParts_.[[Number]] is _number_ and _monthCodeParts_.[[IsLeap]] is _isLeap_, then

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I changed it to check for month code string equality instead, in 6abbe6e.

spec.emu Outdated
1. Set _monthCode_ to *"M06"*.
1. Else if _isLeap1_ is *false* and _isLeap2_ is *true*, then
1. If _monthCode_ is *"M06"*, then
1. Set _monthCode_ to *"M05L"*.

Choose a reason for hiding this comment

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

Please explain the intent of these steps with a "NOTE: The following steps…".

spec.emu Outdated
Comment on lines 1067 to 1071
1. Let _month_ be MonthCodeToArithmeticalMonth(_calendar_, _monthCode_, _y2_).
1. Assert: _month_ is not ~invalid~.
1. Else,
1. Let _month_ be ParseMonthCode(_monthCode_).[[Number]].
1. Return the Record { [[Year]]: _y2_, [[Month]]: _month_ }.

Choose a reason for hiding this comment

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

Suggested change
1. Let _month_ be MonthCodeToArithmeticalMonth(_calendar_, _monthCode_, _y2_).
1. Assert: _month_ is not ~invalid~.
1. Else,
1. Let _month_ be ParseMonthCode(_monthCode_).[[Number]].
1. Return the Record { [[Year]]: _y2_, [[Month]]: _month_ }.
1. Let _arithmeticalMonth_ be MonthCodeToArithmeticalMonth(_calendar_, _monthCode_, _y2_).
1. Assert: _arithmeticalMonth_ is not ~invalid~.
1. Else,
1. Let _arithmeticalMonth_ be ParseMonthCode(_monthCode_).[[Number]].
1. Return the Record { [[Year]]: _y2_, [[Month]]: _arithmeticalMonth_ }.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

This code was deleted in 6abbe6e

spec.emu Outdated
Comment on lines 1080 to 1094
_month_: an integer,
_day_: an integer,
): a Record with fields [[Year]] (an integer), [[Month]] (an integer), and [[Day]] (an integer)
</h1>
<dl class="header">
<dt>description</dt>
<dd>
It interprets the given _year_ and potentially out-of-range _month_ and _day_ as arithmetical values in the given _calendar_ and returns in-range values by overflowing out-of-range _month_ or _day_ values into the next-highest unit.
This date may be outside the range given by ISODateTimeWithinLimits.
</dd>
</dl>
<p>It performs the following steps when called:</p>
<emu-alg>
1. Let _resolvedYear_ be _year_.
1. Let _resolvedMonth_ be _month_.

Choose a reason for hiding this comment

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

Suggested change
_month_: an integer,
_day_: an integer,
): a Record with fields [[Year]] (an integer), [[Month]] (an integer), and [[Day]] (an integer)
</h1>
<dl class="header">
<dt>description</dt>
<dd>
It interprets the given _year_ and potentially out-of-range _month_ and _day_ as arithmetical values in the given _calendar_ and returns in-range values by overflowing out-of-range _month_ or _day_ values into the next-highest unit.
This date may be outside the range given by ISODateTimeWithinLimits.
</dd>
</dl>
<p>It performs the following steps when called:</p>
<emu-alg>
1. Let _resolvedYear_ be _year_.
1. Let _resolvedMonth_ be _month_.
_arithmeticalMonth_: an integer,
_day_: an integer,
): a Record with fields [[Year]] (an integer), [[Month]] (an integer), and [[Day]] (an integer)
</h1>
<dl class="header">
<dt>description</dt>
<dd>
It interprets the given _year_ and potentially out-of-range _arithmeticalMonth_ and _day_ as arithmetical values in the given _calendar_ and returns in-range values by overflowing out-of-range _arithmeticalMonth_ or _day_ values into the next-highest unit.
This date may be outside the range given by ISODateTimeWithinLimits.
</dd>
</dl>
<p>It performs the following steps when called:</p>
<emu-alg>
1. Let _resolvedYear_ be _year_.
1. Let _resolvedArithmeticalMonth_ be _arithmeticalMonth_.

and so on.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

In this particular function, I don't think the verbosity of repeating "arithmetical" a dozen times lends clarity. This AO is very much defined as working on arithmetical values only.

However, I did change the argument name in f54148f.

spec.emu Outdated
Comment on lines 1142 to 1143
The return value indicates whether the date _date1_, the result of adding the duration denoted by _years_, _months_, _weeks_, and _days_ to _baseDate_ in the calendar system denoted by _calendar_, surpasses _isoDate2_ in the direction denoted by _sign_.
If _weeks_ and _days_ are both zero, then _date1_ need not exist (for example, it could be February 30).

Choose a reason for hiding this comment

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

I don't like the asymmetry of baseDate vs. isoDate2. What about fromIsoDate and toIsoDate?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I had used isoDate2 to match the name in 262 but I will change this according to your suggestion here which I agree is better. f157b7a

spec.emu Outdated
Comment on lines 1291 to 1306
1. Let _month_ be MonthCodeToArithmeticalMonth(_calendar_, _year_, _fields_.[[MonthCode]]).
1. If _month_ is ~invalid~, throw a *RangeError* exception.
1. Else,
1. Let _month_ be RegulateNonISOYearMonth(_calendar_, _year_, _fields_.[[MonthCode]], 0).[[Month]].
1. Else,
1. Assert: _fields_.[[Month]] is not ~unset~.
1. Let _month_ be _fields_.[[Month]].
1. Let _day_ be _fields_.[[Day]].
1. Assert: _day_ is not ~unset~.
1. Let _daysInMonth_ be CalendarDaysInMonth(_calendar_, _year_, _month_).
1. If _daysInMonth_ &le; _day_, then
1. If _overflow_ is ~reject~, throw a *RangeError* exception.
1. Let _regulatedDay_ be _daysInMonth_.
1. Else,
1. Let _regulatedDay_ be _day_.
1. Return ? CalendarDateArithmeticalToISO(_calendar_, _year_, _month_, _regulatedDay_).

Choose a reason for hiding this comment

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

Suggested change
1. Let _month_ be MonthCodeToArithmeticalMonth(_calendar_, _year_, _fields_.[[MonthCode]]).
1. If _month_ is ~invalid~, throw a *RangeError* exception.
1. Else,
1. Let _month_ be RegulateNonISOYearMonth(_calendar_, _year_, _fields_.[[MonthCode]], 0).[[Month]].
1. Else,
1. Assert: _fields_.[[Month]] is not ~unset~.
1. Let _month_ be _fields_.[[Month]].
1. Let _day_ be _fields_.[[Day]].
1. Assert: _day_ is not ~unset~.
1. Let _daysInMonth_ be CalendarDaysInMonth(_calendar_, _year_, _month_).
1. If _daysInMonth_ &le; _day_, then
1. If _overflow_ is ~reject~, throw a *RangeError* exception.
1. Let _regulatedDay_ be _daysInMonth_.
1. Else,
1. Let _regulatedDay_ be _day_.
1. Return ? CalendarDateArithmeticalToISO(_calendar_, _year_, _month_, _regulatedDay_).
1. Let _arithmeticalMonth_ be MonthCodeToArithmeticalMonth(_calendar_, _year_, _fields_.[[MonthCode]]).
1. If _arithmeticalMonth_ is ~invalid~, throw a *RangeError* exception.
1. Else,
1. Let _arithmeticalMonth_ be RegulateNonISOYearMonth(_calendar_, _year_, _fields_.[[MonthCode]], 0).[[Month]].
1. Else,
1. Assert: _fields_.[[Month]] is not ~unset~.
1. Let _arithmeticalMonth_ be _fields_.[[Month]].
1. Let _day_ be _fields_.[[Day]].
1. Assert: _day_ is not ~unset~.
1. Let _daysInMonth_ be CalendarDaysInMonth(_calendar_, _year_, _arithmeticalMonth_).
1. If _daysInMonth_ &le; _day_, then
1. If _overflow_ is ~reject~, throw a *RangeError* exception.
1. Let _regulatedDay_ be _daysInMonth_.
1. Else,
1. Let _regulatedDay_ be _day_.
1. Return ? CalendarDateArithmeticalToISO(_calendar_, _year_, _arithmeticalMonth_, _regulatedDay_).

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Resolved in f54148f

<emu-clause id="sec-temporal-parsemonthcode" type="implementation-defined abstract operation">
<h1>
ParseMonthCode (
_monthCode_: a String,
Copy link
Member

Choose a reason for hiding this comment

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

I agree we should narrow down the possible set of month codes (which we already have) and define that (essentially mark the existing definition) as the term "month code" or "month code type" akin to "calendar type" that we utilize from upstream Temporal. I don't think it's a bad idea to place this definition in the Temporal spec since it can be utilized in the upstream AO https://tc39.es/proposal-temporal/#sec-temporal-tomonthcode.

spec.emu Outdated
1. If IsValidMonthCodeForCalendar(_calendar_, _monthCode_) is *false*, then
1. Return ~invalid~.
1. Let _monthCodeParts_ be ParseMonthCode(_monthCode_).
1. If _calendar_ is not *"chinese"*, *"dangi"*, or *"hebrew"*, then
Copy link
Member

Choose a reason for hiding this comment

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

nit: we can move this elsewhere as the list or table of supported calendars with leap months to make it easier to reuse and maintain and refer to that "central" (not necessarily for now, just disjointed) definition here.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I like the idea of moving this to a table, and the same table can also be used in @gibson042's proposed AO CalendarHasLeapMonths.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Added it to #table-additional-month-codes in 6abbe6e

spec.emu Outdated

<emu-clause id="sec-temporal-regulatenonisoyearmonth" type="implementation-defined abstract operation">
<h1>
RegulateNonISOYearMonth (
Copy link
Member

Choose a reason for hiding this comment

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

ConstrainNonISOYearMonth sounds good as well but yeah don't mind Regulate either since it's consistent with upstream

spec.emu Outdated
<dl class="header">
<dt>description</dt>
<dd>
Interprets _isoDate_ as a date in _calendar_, adds _years_, and returns the best-match arithmetic _year_ and _month_ according to the calendar.
Copy link
Member

Choose a reason for hiding this comment

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

isoDate isn't defined here, replace with "a YearMonth corresponding to arithmeticYear and monthCode".

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I deleted this AO in 6abbe6e

@sffc
Copy link
Collaborator Author

sffc commented Jul 30, 2025

This PR was included in the Stage 2.7 presentation. This PR was approved as part of Stage 2.7 with the following understanding: "changes the spec text to transform some parts of the spec from prose to algorithmic spec steps"

@sffc
Copy link
Collaborator Author

sffc commented Jul 31, 2025

I believe I have responded to all of the threads. PTAL. Thank you @Manishearth @gibson042 @ryzokuken!

Copy link

@justingrant justingrant left a comment

Choose a reason for hiding this comment

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

Minor suggestion: the adjective for "arithmetic" can be "arithmetical" but could also be "arithmetic", e.g. "arithmetic mean".

Brevity is important in spec text, so I'd suggest globally replacing "arithmetical" wtih just "arithmetic".

Copy link

@justingrant justingrant left a comment

Choose a reason for hiding this comment

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

Overall looks good. A few minor suggestions only.

<emu-clause id="sec-temporal-createmonthcode" type="implementation-defined abstract operation">
<h1>
CreateMonthCode (
_number_: an integer,

Choose a reason for hiding this comment

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

Is there a more descriptive name than number that could be appropriate here, specifcally that would distinguish this numnber from arithmetic month, common month, etc.? For example, for a month code like M06L, do we have a term for the "6" part of that code? If not, should we have a term for it?

At a minimum, maybe change this to _monthNumber_ if we don't want to use a separate term?

<h1>
CreateMonthCode (
_number_: an integer,
_isLeap_: a Boolean,

Choose a reason for hiding this comment

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

Suggested change
_isLeap_: a Boolean,
_isLeapMonth_: a Boolean,

Might be good to distinguish from leap years vs. leap months. I know it's obvious to us, but for readers who are not familar wtih lunisolar calendars it might be clearer to emphasize that this is a leap month not a leap year.

<emu-clause id="sec-temporal-calendarmonthsinyear" type="implementation-defined abstract operation">
<h1>
CalendarMonthsInYear (
_calendar_: a calendar type that is not *"iso8601"*,

Choose a reason for hiding this comment

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

Is there a reason why we chose "calendar type" instead of "calendar identifier" to align with "time zone identifier" ?

Copy link
Collaborator

Choose a reason for hiding this comment

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

It was the term already in use in 402.

<dl class="header">
<dt>description</dt>
<dd>
It interprets _year_ as arithmetical value in the given _calendar_ and returns the number of months in the corresponding year.

Choose a reason for hiding this comment

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

Suggested change
It interprets _year_ as arithmetical value in the given _calendar_ and returns the number of months in the corresponding year.
It interprets _year_ as arithmetic year in the given _calendar_ and returns the number of months in the corresponding year.

"Arithmethical Value" sounds like it could be a floating point thing that could be confused wtih "mathematical value" in ECMA-262. Also, do we need the "-al" per my earlier comment?

<dl class="header">
<dt>description</dt>
<dd>
It interprets the given _year_ and potentially out-of-range _arithmeticalMonth_ and _day_ as arithmetical values in the given _calendar_ and returns in-range values by overflowing out-of-range _arithmeticalMonth_ or _day_ values into the next-highest unit.

Choose a reason for hiding this comment

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

Suggested change
It interprets the given _year_ and potentially out-of-range _arithmeticalMonth_ and _day_ as arithmetical values in the given _calendar_ and returns in-range values by overflowing out-of-range _arithmeticalMonth_ or _day_ values into the next-highest unit.
It interprets the given _year_ and potentially out-of-range _arithmeticalMonth_ and/or _day_ as arithmetical values in the given _calendar_ and returns in-range values by overflowing out-of-range _arithmeticalMonth_ or _day_ values into the next-highest unit.

There's a minor ambiguity possible where it's unclear whether the _day_could also be out of range. Using and/or might clarify,.

@sffc
Copy link
Collaborator Author

sffc commented Aug 2, 2025

Minor suggestion: the adjective for "arithmetic" can be "arithmetical" but could also be "arithmetic", e.g. "arithmetic mean".

Brevity is important in spec text, so I'd suggest globally replacing "arithmetical" wtih just "arithmetic".

I prefer using the explicitly-adjective "arithmetical", but I was actually thinking of using a term like "monotonic" to emphasize how they actually behave, in another PR. But since this spec currently uses "arithmetic", I can keep using that term to reduce the diff, if other people feel the difference warrants fixing.

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.

Stage 2.7 review feedback: symmetry of CalendarDateAdd and CalendarDateUntil
6 participants