Semantic Diff

#9094 Add Temporal bindings to the standard library

3 commits

1a49d918eb73d0b058655608cfb2c2f4e6af4e1f..pr-9094

ad770f40e · mununki · Oct 27, 2026, 23:27 UTC

Treat Temporal as a JS global

Why

A binding printed as Temporal hides the global Temporal in its scope. The bindings of the next commit are externals with dotted names like "Temporal.PlainDate.from", which are printed as written: unlike @scope externals (#8785), they are not read through globalThis when a binding of the same name is in scope. A module named Temporal next to a call of the bindings would then be called instead.

modifiedJS module named Temporal

type instant@val external now: unit => instant = "Temporal.Now.instant" module Temporal = {  let zone = "UTC"} let stamp = () => now()
js
Before
After
let Temporal = {let $$Temporal = { zone: "UTC" zone: "UTC"};}; function stamp() {function stamp() { return Temporal.Now.instant(); // the module: a TypeError return Temporal.Now.instant(); // the global}} export {export { Temporal, $$Temporal, stamp, stamp,}}

Review

  • Why not read dotted externals through globalThis instead? That changes every dotted external, not only Temporal's. A reserved name is how Intl and the other built-ins are handled, and it also keeps raw JavaScript next to the module reading the global. The next commit tests it in Stdlib_TemporalTests.
e2d13dd26 · mununki · Oct 27, 2026, 23:27 UTC

Add Temporal bindings to the standard library

Why

JavaScript's Temporal API covers dates, times, time zones and durations, which Date handles poorly, and Node.js 26 ships it. #9044 proposed stdlib bindings for it.

addedJS Temporal bindings

let meeting = Temporal.ZonedDateTime.fromStringOrThrow(  "2024-03-09T09:30:00-05:00[America/New_York]",)let nextMeeting = meeting->Temporal.ZonedDateTime.addFields({days: 1.})let length = meeting->Temporal.ZonedDateTime.until(nextMeeting) let birthday = Temporal.PlainMonthDay.fromString("02-29")let started = Date.make()->Date.toTemporalInstant
js
After
import * as Stdlib_Temporal_PlainMonthDay from "@rescript/runtime/lib/es6/Stdlib_Temporal_PlainMonthDay.mjs"; let meeting = Temporal.ZonedDateTime.from("2024-03-09T09:30:00-05:00[America/New_York]"); let nextMeeting = meeting.add({  days: 1}); let length = meeting.until(nextMeeting); let birthday = Stdlib_Temporal_PlainMonthDay.fromString("02-29"); let started = new Date().toTemporalInstant();

Review

  • Why do == and compare treat any two Temporal values as equal? Temporal objects keep their state in internal slots and have no enumerable properties to compare. Each module's docs point to its equals and compare; #9044 keeps == as it is.
  • Why does fromString rethrow errors other than RangeError? Only a RangeError means the string is invalid. Without the Temporal global the call throws a ReferenceError, which None would hide.
  • Why are the tests not in Stdlib_TestSuite? The installation tests run that suite on Node.js 22, which lacks Temporal. Stdlib_TemporalTests is its own entry point, which scripts/test.js runs when globalThis.Temporal exists. On older Node.js versions, DocTest.res now emits the doc examples it skips as test.skip, so they are still type checked.
a56765f27 · mununki · Oct 27, 2026, 23:27 UTC

Map Temporal types to TypeScript in GenType

Why

GenType emitted the Temporal types as types imported from Stdlib.gen and Stdlib_Temporal_Common.gen, files that don't exist, so the generated TypeScript didn't compile. TypeScript 6.0 declares the same types in its esnext.temporal lib, which ESNext includes.

modifiedRepresentation genType's TypeScript type of Temporal values

@genTypelet temporalDateOfDateTime = (x: Temporal.PlainDateTime.t) => x->Temporal.PlainDateTime.toPlainDate@genTypelet temporalNextDay = (x: Temporal.PlainDate.t) => x->Temporal.PlainDate.addFields({days: 1.})
ts
Before tests/gentype_tests/stdlib-no-shims/src/StdlibNoShims.gen.tsx
After tests/gentype_tests/stdlib-no-shims/src/StdlibNoShims.gen.tsx
import type {Temporal_PlainDateTime_t as Stdlib_Temporal_PlainDateTime_t} from './Stdlib.gen';import type {Temporal_PlainDate_t as Stdlib_Temporal_PlainDate_t} from './Stdlib.gen';import type {plainDate as Stdlib_Temporal_Common_plainDate} from './Stdlib_Temporal_Common.gen'; export const temporalDateOfDateTime: (x:Stdlib_Temporal_PlainDateTime_t) => Stdlib_Temporal_Common_plainDate = StdlibNoShimsJS.temporalDateOfDateTime as any;export const temporalDateOfDateTime: (x:Temporal.PlainDateTime) => Temporal.PlainDate = StdlibNoShimsJS.temporalDateOfDateTime as any; export const temporalNextDay: (x:Stdlib_Temporal_PlainDate_t) => Stdlib_Temporal_PlainDate_t = StdlibNoShimsJS.temporalNextDay as any;export const temporalNextDay: (x:Temporal.PlainDate) => Temporal.PlainDate = StdlibNoShimsJS.temporalNextDay as any;

Review

  • Why match the types of Temporal.Common too? Conversions like PlainDateTime.toPlainDate return the shared types of Stdlib_Temporal_Common, which the module types are defined as.