Skip to content

Temporal stage 4, round 2 - #3966

Open
ptomato wants to merge 94 commits into
tc39:mainfrom
ptomato:temporal-stage-4-round-2
Open

Temporal stage 4, round 2#3966
ptomato wants to merge 94 commits into
tc39:mainfrom
ptomato:temporal-stage-4-round-2

Conversation

@ptomato

@ptomato ptomato commented Sep 1, 2026

Copy link
Copy Markdown
Member

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.

ptomato added 30 commits August 31, 2026 10:52
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.
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
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.
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 "!".
ptomato added 16 commits August 31, 2026 17:03
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".
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.
@ptomato ptomato mentioned this pull request Sep 1, 2026
@github-actions

github-actions Bot commented Sep 1, 2026

Copy link
Copy Markdown

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 ptomato left a comment

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

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:

  1. three arbitrary integers, allowing e.g. 999999-14-50 and 2000-02-31
  2. valid year-month-day without limits, allowing e.g. 999999-01-01 but not 2000-02-31 or 999999-14-50
  3. within-limits year-month-day for PlainYearMonth storage purposes, allowing e.g. -271821-04-01 but not -271821-03-31
  4. 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).

Comment thread temporal/abstractops.emu
Comment on lines +445 to +460
<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>

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Note #3759 (comment); there's still an open question about whether we want a more useful CanonicalizeCalendar in ECMA-262.

Comment thread spec.html
@@ -34600,16 +34451,18 @@ <h1>
<emu-alg>
1. If _t_ is not finite, return *NaN*.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Note pre-existing defect, #3759 (comment): UTC should check if t is an integer, but that would be a normative change.

Comment thread spec.html
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).

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Similarly, note pre-existing defect: #3759 (comment), TimeString should check if tv is an integer.

Comment thread temporal/timezone.emu
Comment thread temporal/timezone.emu
GetOffsetNanosecondsFor (
_timeZone_: an available time zone identifier,
_epochNanoseconds_: an epoch nanoseconds count,
): an integer in the interval from -NanosecondsPerDay (exclusive) to NanosecondsPerDay (exclusive)

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

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.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I lean toward yes on this, but also do not feel strongly.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Name bikeshed? I seem to remember "___ value" was dispreferred

Comment thread temporal/abstractops.emu

<emu-clause id="sec-addtimedurationtoepochnanoseconds" type="abstract operation">
<h1>
AddTimeDurationToEpochNanoseconds (

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

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.

Comment thread temporal/abstractops.emu Outdated
Comment thread temporal/abstractops.emu
Comment thread temporal/abstractops.emu
</emu-alg>
</emu-clause>

<emu-clause id="sec-timedurationfromepochnanosecondsdifference" type="abstract operation">

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Similar to AddTimeDurationToEpochNanoseconds, there was a thread (#3759 (comment)) about inlining this AO. I'd prefer not to, for the same reason.

Comment thread temporal/timezone.emu
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>

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

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.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

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.

Comment thread temporal/instant.emu Outdated
Comment thread temporal/abstractops.emu Outdated
@bakkot

bakkot commented Sep 8, 2026

Copy link
Copy Markdown
Member

Note that it will not lint until tc39/ecmarkup#725 is fixed.

Fixed in the just-published ecmarkup 25.0.3, feel free to pull it in here.

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.

6 participants