Temporal stage 4, round 2 - #3966
Conversation
25.0.2 is needed to be able to search in the resulting document.
This adds the Temporal proposal, reorganizing the "Numbers and Dates" section into the "Numbers and Mathematical Functions" and "Dates and Temporal" sections. The existing section on "Time Zone Identifiers", used both by Date and Temporal, is moved into its own section under "Dates and Temporal". Most of the proposal is kept in separate temporal/*.emu files, included with `<emu-import>` tags. This is an arbitrary choice and can be done differently if so desired.
Fixes tc39/proposal-temporal#3312, which reached TC39 consensus 2026-05-19.
Fixes tc39/proposal-temporal#3316, which reached TC39 consensus 2026-05-19.
As discussed during the editors meeting, this commit shows what it'd look like if we changed AvailableCalendars and CanonicalizeCalendar to work more like time zones, where we give the specification assuming that only the minimum is supported. CanonicalizeUValue would then stay in ECMA-402 where it originated, and ECMA-402 would gain an override of CanonicalizeCalendar (it already has one for AvailableCalendars).
The Date section was rewritten to use abstract operations instead of equations, and most now use mathematical values in a way that allows us to use them in Temporal. This adapts the proposal to tc39#3193. The only things left in this section are CheckISODaysRange and the mathematical-value versions of MakeDate and MakeDay. All three deal with epoch days, so rename the section to "Epoch Days Operations".
Also change phrasing like "exact time expressed in nanoseconds since the epoch" to standardize around "epoch nanoseconds count". This adapts the proposal to tc39#3942
Adapt the proposal to tc39#3953.
This mirrors partial Duration Records. Also change the value signifying "field not present" from UNSET to EMPTY.
The naming convention is that operations that throw when the input isn't valid, should start with "Validate".
…to EMPTY Different from the change in Calendar Fields Record, here it is only the [[Era]] and [[EraYear]] fields that can be absent, in the case of calendars without eras.
Make the parameter to GetTemporalUnitValuedOption have the values of REQUIRED/OPTIONAL, rather than REQUIRED/UNSET. The return value uses NO-UNIT to indicate that the option was not provided, rather than UNSET.
…crement to NO-MAXIMUM Avoid UNSET in favour of a single-purpose enum. To discuss: This could also just be EMPTY.
Change from UNSET to NO-TIME-ZONE. This makes call sites clearer. To discuss: This could also just be EMPTY.
Instead of listing out all the types, look for the [[Calendar]] slot. This is less future-proof, but it's easier to read.
Using a spec enum instead of undefined.
Prefer having the description in the implementation-defined step, rather than in the AO description.
I _think_ that specific phrase is the only thing that really needs to change? Use it in CalendarFieldKeysToIgnore. See also tc39/proposal-temporal@67ba51e
isoDate1 is derived from baseDate plus years, months, weeks, days, so they should be given together. Move isoDate2 to the end of the arguments list.
This absorbs the preceding ISValidISODate step at some call sites into CreateISODateRecord itself. If the values are already known to form a valid ISO date, CreateISODateRecord is called with "!".
Similar to CreateISODateRecord, this absorbs the preceding IsValidTime step at some call sites into CreateTimeRecord itself. If the values are already known to form a valid time, CreateTimeRecord is called with "!".
The lookups of the "Conversion" and "Default Value" columns are already gone. Remove the remaining lookups of the "Property Key" column.
Now that the column is not used in any table lookups, it doesn't have to be in the table. Instead we create a <dfn> for the "calendar property key" type and use it instead of "value from the Property Key column of Table X".
Replace with a TemporalUnitLength AO.
Rewrite GetTemporalUnitValuedOption not to use table lookups, which was the only user of the "Plural name" column.
This AO could've used some work anyway: - Rename smallestUnit to startUnit, which is its name at the call site; smallestUnit is misleading because it can be larger than largestUnit - Narrow the type of startUnit. It could be year, month, or day, but year would hit the early return anyway, so instead narrow the type to month or day and check for year at the call site. - Put the units to be looped over into a list, beforehand, instead of checking indices. The loop is really only over a few units: from one- larger-than-startUnit in ascending order to largestUnit, so can only be [year, month], [month], [week], or []. - Eliminate the outermost if-statement inside the loop (which would really be a continue-statement in JS), instead only put week into the list if largestUnit is week. - Eliminate the "If done is true" in favour of breaking the loop by early return. This was the last consumer of the units table, so remove it and instead list the possible units in the dfn of "Temporal unit".
…s in NonISODateUntil Makes this constraint more explicit, since there is no ordering of Date Duration Record fields and no relation between units and fields.
Look for mentions of public APIs without context in AO descriptions and clean them up so that they explain how the public API uses the AO. A few places (e.g. Date Duration Record) the mention of the public API was no longer correct, so remove it. Remove the notes about Temporal.PlainDateTime and Temporal.PlainYearMonth ranges from AOs and instead put that information in the Temporal.PlainDateTime and Temporal.PlainYearMonth overview sections, respectively. Add an analogous paragraph to the Temporal.PlainDate overview section.
Make sure that all references to Temporal methods are accompanied by an
emu-xref, and remove the () from the end. This is analogous to existing
cross-references I found in ECMA-262, such as:
`JSON.stringify` (<emu-xref href="#sec-json.stringify"></emu-xref>)
Remove backticks from references to Temporal types, since those are
covered by dfns and the backticks prevent the dfns from being linked.
It can never actually return a throw completion, but we can only know that from examining the call sites. So move the assertions to the call sites.
Express the formal requirements algorithmically using a slot on the Agent Record. Change the note to be a plain-language summary of the requirements. Move the note about AvailableNamedTimeZoneIdentifiers so it's associated with that AO.
This definition is needed for "earliest possible" and "latest possible" in DisambiguatePossibleEpochNanoseconds. As long as we are defining "earlier" and "later" in terms of CompareISODateTime, use that language to replace the other use of CompareISODateTime in the same sentences.
A calendar type is a string that is syntactically valid according to the AnnotationValue nonterminal. It may be a nonsense ID, a real calendar ID that is unsupported by the implementation, an alias of a calendar ID that is supported, a supported calendar type with some of the characters uppercased, or a supported calendar type. A known calendar type is a calendar type that is supported by the implementation and is canonicalized, i.e. not an alias and not mixed-case. CanonicalizeCalendar is the way to get a known calendar type from an arbitrary string, which may or may not be a calendar type. Note that AvailableCalendars returns calendar types, not known calendar types, because it includes aliases. CalendarEquals becomes unnecessary because it is only ever used on internal slot values which are always known calendar types, so they can be compared directly.
Calendar Fields Records represent user input. As such, they might have out-of-range values for the time fields, which are only checked later in operations like InterpretTemporalDateTimeFields.
|
The rendered spec preview for this PR is available as a single page at https://tc39.es/ecma262/pr/3966 and as multiple pages at https://tc39.es/ecma262/pr/3966/multipage . |
ptomato
left a comment
There was a problem hiding this comment.
Here's a self-review with the open threads left over from #3759.
I have one issue left to address, mentioned in #3759 (comment), #3759 (comment): adding a proof-carrying type for ISO Date Record. Copying my analysis from the other PR:
I think in terms of four distinct types of year-month-day triples:
- three arbitrary integers, allowing e.g. 999999-14-50 and 2000-02-31
- valid year-month-day without limits, allowing e.g. 999999-01-01 but not 2000-02-31 or 999999-14-50
- within-limits year-month-day for PlainYearMonth storage purposes, allowing e.g. -271821-04-01 but not -271821-03-31
- within-limits year-month-day, allowing e.g. -271821-04-19 but not -271821-04-18
AOs that accept (1) I've rendered as three separate parameters:
- year: an integer
- month: an integer (in the inclusive interval from 1 to 12, if it can be guaranteed from the types known at the call site)
- day: an integer (in the inclusive interval from 1 to 31, if it can be guaranteed from the types known at the call site)
See e.g. CreateISODateRecord or RegulateISODate
AOs that accept (2) have parameters typed as "an ISO Date Record".
There is currently no separate type for AOs that accept (3) or (4).
| <emu-clause id="sec-canonicalizecalendar" type="abstract operation"> | ||
| <h1> | ||
| CanonicalizeCalendar ( | ||
| _id_: a String, | ||
| ): either a normal completion containing a known calendar type, or a throw completion | ||
| </h1> | ||
| <dl class="header"> | ||
| <dt>description</dt> | ||
| <dd>It returns the known calendar type denoted by _id_, or throws an exception if the implementation does not support that calendar.</dd> | ||
| </dl> | ||
| <p>The default implementation of CanonicalizeCalendar, to be used for ECMAScript implementations that do not support any calendar types other than *"iso8601"*, performs the following steps when called:</p> | ||
| <emu-alg> | ||
| 1. If the ASCII-lowercase of _id_ is not *"iso8601"*, throw a *RangeError* exception. | ||
| 1. Return *"iso8601"*. | ||
| </emu-alg> | ||
| </emu-clause> |
There was a problem hiding this comment.
Note #3759 (comment); there's still an open question about whether we want a more useful CanonicalizeCalendar in ECMA-262.
| @@ -34600,16 +34451,18 @@ <h1> | |||
| <emu-alg> | |||
| 1. If _t_ is not finite, return *NaN*. | |||
There was a problem hiding this comment.
Note pre-existing defect, #3759 (comment): UTC should check if t is an integer, but that would be a normative change.
| 1. Let _minute_ be ToZeroPaddedDecimalString(MinuteFromTime(_tv_), 2). | ||
| 1. Let _second_ be ToZeroPaddedDecimalString(SecondFromTime(_tv_), 2). | ||
| 1. Return the string-concatenation of _hour_, *":"*, _minute_, *":"*, _second_, the code unit 0x0020 (SPACE), and *"GMT"*. | ||
| 1. Let _timeString_ be FormatTimeString(HourFromTime(_tv_), MinuteFromTime(_tv_), SecondFromTime(_tv_), 0, 0). |
There was a problem hiding this comment.
Similarly, note pre-existing defect: #3759 (comment), TimeString should check if tv is an integer.
| GetOffsetNanosecondsFor ( | ||
| _timeZone_: an available time zone identifier, | ||
| _epochNanoseconds_: an epoch nanoseconds count, | ||
| ): an integer in the interval from -NanosecondsPerDay (exclusive) to NanosecondsPerDay (exclusive) |
There was a problem hiding this comment.
There was an open question (#3759 (comment)) about whether we wanted to name "integer in the interval from -NanosecondsPerDay (exclusive) to NanosecondsPerDay (exclusive)" something like "UTC offset nanoseconds value". I think probably not, but I don't feel strongly about it.
There was a problem hiding this comment.
I lean toward yes on this, but also do not feel strongly.
There was a problem hiding this comment.
Name bikeshed? I seem to remember "___ value" was dispreferred
|
|
||
| <emu-clause id="sec-addtimedurationtoepochnanoseconds" type="abstract operation"> | ||
| <h1> | ||
| AddTimeDurationToEpochNanoseconds ( |
There was a problem hiding this comment.
There was an open thread (#3759 (comment)) about inlining this AO, but I think it's better to keep it for type clarity reasons, since all three integers involved here mean semantically different things.
| </emu-alg> | ||
| </emu-clause> | ||
|
|
||
| <emu-clause id="sec-timedurationfromepochnanosecondsdifference" type="abstract operation"> |
There was a problem hiding this comment.
Similar to AddTimeDurationToEpochNanoseconds, there was a thread (#3759 (comment)) about inlining this AO. I'd prefer not to, for the same reason.
| The returned value _transition_ represents the first epoch nanoseconds count where the new UTC offset is used in this time zone, not the last epoch nanoseconds count where the previous UTC offset is used. | ||
| In other words, GetOffsetNanosecondsFor(_timeZone_, _transition_) ≠ GetOffsetNanosecondsFor(_timeZone_, _transition_ - 1). | ||
| </p> | ||
| <p>If this operation is called multiple times with the same values for _timeZoneIdentifier_ and _epochNanoseconds_, the result must be the same for each such call for the lifetime of the surrounding agent.</p> |
There was a problem hiding this comment.
Do we want to memoize this operation's return values (and likewise, GetNamedTimeZonePreviousTransition) on the Agent Record as with GetAvailableNamedTimeZoneIdentifier? The requirement is considerably simpler here, so it might not be necessary.
There was a problem hiding this comment.
I personally think this wording is sufficient, but attaching to the agent record would be nicer. For expediency, we probably shouldn't worry about it.
Fixed in the just-published ecmarkup 25.0.3, feel free to pull it in here. |
25.0.3 brings a fix for a linter failure on this branch.
Follow up from #3759. That PR has enough comments on it that it's getting difficult to navigate in the GitHub UI, so in agreement with the editor group I'm opening this one to replace it.
This PR consists of the fixup commits in #3759 squashed into the original, plus new fixup commits for each comment from #3759 that I addressed. For each thread in #3759 that needs a follow-up, I'll start a new thread on this PR.
Note that it will not lint until tc39/ecmarkup#725 is fixed.