⌚ Temporal

Philip Chimento
Igalia, in partnership with Bloomberg
TC39 January 2026

Progress update

  • Test262 coverage has been greatly expanded, exposing many implementation bugs.
  • We have 2 implementations at ~100% test conformance.
  • Today, we have 2 normative changes to propose to eliminate surprising behaviour in edge cases.

Test conformance as of January 2026

Path to Stage 4

  • V8 implementation is now available on the Web!
  • GraalJS implementation scheduled for unflagged release
  • Intl Era/Month Code request for stage 3 in this meeting
  • Investigate last conformance bugs in implementations
  • Move remaining tests in staging to main test262, update and expand as needed
  • Fill identified gaps in test coverage
  • Temporal moves to stage 4, together with Intl Era/Month Code

Surprise in PlainYearMonth subtract

> Temporal.PlainYearMonth.from("2025-07").subtract({ months: 1 }, { overflow: 'reject' })
  // Current:  RangeError
  // Proposed: Temporal.PlainYearMonth of 2025-06
  • Bug has been present in the spec for a long time (>5 years)
  • Found using snapshot testing technique
  • Caused by a bug in the addition/subtraction code that existed to accommodate durations with days

Surprise in PlainYearMonth subtract (2)

  • Temporal champions recommend removing the subtract-days-from-PlainYearMonth feature (PR #3253)
> Temporal.PlainYearMonth.from("2025-07").add({ days: 31 })
  // Current:  Temporal.PlainYearMonth of 2025-08
  // Proposed: RangeError
  • This also fixes the bug on the previous slide
  • ⌛ This is a late addition to the agenda
  • Fallback bugfix in PR #3208 (h/t Tim Chevalier)

Surprise in Plain types' toLocaleString

> Temporal.PlainDateTime.from('2026-03-08T02:00').toLocaleString()
  // Current:  "2026-03-08, 3:00:00 a.m." (in my locale)
  // Proposed: "2026-03-08, 2:00:00 a.m." (in my locale)
> Temporal.PlainDate.from('2011-12-30').toLocaleString('en-ca', { timeZone: 'Pacific/Apia' })
  // Current:  "2011-12-31"
  // Proposed: "2011-12-30"
  • h/t fabon-f and Adam Shaw, JS community members developing tools downstream of Temporal
  • Plain types are wall-clock times
  • Should not be subject to the formatter's time zone
  • Fix in normative PR #3246

Summary of Intl Era/Month Code changes

Will be discussed in the other presentation, but noting here:

  • PR #99 - Intl Era/Month Code must support only the listed calendars
  • PR #101 - clarification of date difference behaviour
    • Note corresponding editorial change in Temporal, to keep the two proposals in sync: PR #3245
  • PR #108 - sets an expectation for implementation-defined behaviour regarding nonsensical lunar dates in PlainMonthDay

Questions?

Requesting consensus

Proposed summary for notes

We outlined a path to stage 4 for the proposal and listed the blockers.

Two normative changes reached consensus, to eliminate surprising behaviour in:

  • Temporal.PlainYearMonth subtraction
  • toLocaleString methods of Temporal.Plain___ types.

We summarized the related changes happening in the Intl Era/Month Code proposal as it goes to stage 3.

npx test262-harness --hostType=sm --hostPath=$HOME/.esvu/bin/sm -f Temporal --fe Intl.Era-monthcode "test/**/*.js" npx test262-harness --hostType=sm --hostPath=$HOME/.esvu/bin/sm -f Intl.Era-monthcode "test/**/*.js" npx test262-harness --hostType=v8 --hostPath=$HOME/.esvu/bin/v8 -f Temporal --fe Intl.Era-monthcode --hostArgs=--harmony-temporal -- "test/**/*.js" npx test262-harness --hostType=v8 --hostPath=$HOME/.esvu/bin/v8 -f Intl.Era-monthcode --hostArgs=--harmony-temporal -- "test/**/*.js" npx test262-harness --hostType=libjs --hostPath=$HOME/.esvu/bin/ladybird-js -f Temporal --fe Intl.Era-monthcode --hostArgs=--use-test262-global -- "test/**/*.js" npx test262-harness --hostType=libjs --hostPath=$HOME/.esvu/bin/ladybird-js -f Intl.Era-monthcode --hostArgs=--use-test262-global -- "test/**/*.js" npx test262-harness --hostType=graaljs --hostPath=$HOME/.esvu/bin/graaljs -f Temporal --fe Intl.Era-monthcode --hostArgs='--experimental-options --js.temporal' -- "test/**/*.js" npx test262-harness --hostType=graaljs --hostPath=$HOME/.esvu/bin/graaljs -f Intl.Era-monthcode --hostArgs='--experimental-options --js.temporal' -- "test/**/*.js" npx test262-harness --hostType=jsc --hostPath=$HOME/.esvu/bin/jsc -f Temporal --fe Intl.Era-monthcode --hostArgs=--useTemporal=1 -- "test/**/*.js" npx test262-harness --hostType=jsc --hostPath=$HOME/.esvu/bin/jsc -f Intl.Era-monthcode --hostArgs=--useTemporal=1 -- "test/**/*.js" npx test262-harness --hostType=boa --hostPath=$HOME/.esvu/bin/boa-nightly -f Temporal --fe Intl.Era-monthcode -- "test/**/*.js" # requires https://github.com/tc39/eshost/pull/147 and https://github.com/devsnek/esvu/pull/66 npx test262-harness --hostType=boa --hostPath=$HOME/.esvu/bin/boa-nightly -f Intl.Era-monthcode -- "test/**/*.js" npx test262-harness --hostType=kiesel --hostPath=$HOME/.esvu/bin/kiesel-nightly -f Temporal --fe Intl.Era-monthcode -- "test/**/*.js" npx test262-harness --hostType=kiesel --hostPath=$HOME/.esvu/bin/kiesel-nightly -f Intl.Era-monthcode -- "test/**/*.js" npx test262-harness --hostType=node --hostPath=$(which node) -f Temporal --hostArgs=... -- "test/**/*.js" npx test262-harness --hostType=hermes --hostPath=$(which deno) -f Temporal --hostArgs='run --unstable-temporal --v8-flags=--expose-gc' -- "test/**/*.js"