Format Temporal values in console.log, util.inspect, and test pretty-format

Closed
RO
3d ago
+485-14

What

Temporal values now print as a single token, Temporal.<Class> <toString()>, in all three formatters:

Temporal.PlainTime 10:20:23
Temporal.Instant 1970-01-01T00:00:00Z
Temporal.ZonedDateTime 2020-01-01T00:00:00-10:00[Pacific/Honolulu]
Temporal.Duration PT1H30M
{ hi: Temporal.PlainTime 10:20:23 }

Before this change, Temporal cells were classified as generic objects everywhere:

  • console.log/Bun.inspect dumped prototype methods and slot getters as if they were own properties (33 lines for a PlainDate, 50 for a ZonedDateTime)
  • util.inspect printed PlainDate [Temporal.PlainDate] {}, with the value invisible
  • the test runner's pretty-format printed PlainDate {}, so every instance of a class produced the same snapshot text, and a failing toEqual diffed two identical sides

How

The text is the spec toString() with default options: lossless, round-trips through Temporal.X.from(), keeps [Zone]/[u-ca=] annotations. It is computed from internal slots (ClassInfo detection, internal toString implementations), so inspection never calls user-observable toString or Symbol.toStringTag.

  • src/jsc/bindings/bindings.cpp + new src/jsc/bindings/Temporal.{h,cpp}: one shared native helper for all 8 types. Bun__JSValue__temporalObjectType classifies a value (0 = not Temporal, 1-8 = type), Bun__Temporal__toDisplayString / Bun::temporalDisplayString produce the text. Seven types use the classes' internal toString(); ZonedDateTime replicates the spec recipe (temporalZonedDateTimeToString is file-static in JSC) from getLocalDateTime, the minute-rounded offset, the zone id, and the calendar annotation. Two host functions expose the helper to internal JS.
  • src/jsc/ConsoleObject.rs: a Temporal tag next to the JSDate -> JSON path. Atomic at any depth, own properties and subclass names ignored (the existing Date/Map behavior), label uncolored, value in Date's magenta.
  • src/runtime/test_runner/pretty_format.rs: same tag and the same text, so expect diffs and snapshots show the value. The format arm is hoisted to #[inline(never)] like the console arms so deep recursion frames stay small.
  • src/js/internal/util/inspect.js: follows nodejs/node#63154 (the open Node PR for the same feature): getPrefix handling for subclasses and null prototypes, 'date' style, extra own properties appended. One adaptation: JSC names the constructors PlainDate where V8 uses Temporal.PlainDate, so direct instances map onto the full label to produce the same output as Node (Temporal.PlainDate 2020-01-02, subclass X [Temporal.PlainDate] 2020-01-02).

Temporal.Now, the prototypes, and Intl objects are unaffected (the classifier rejects them).

Verification

  • test/js/bun/util/inspect.test.js: all 8 types, annotations, nesting, depth, own-property/subclass behavior, colors, console.log via spawn
  • test/js/node/util/node-inspect-tests/parallel/util-inspect-temporal.test.js: port of the test from nodejs/node#63154; all 48 cases produce byte-identical output to Node's expectations
  • test/js/bun/bun-object/deep-equals-temporal.test.ts: toEqual failure messages now show Expected: Temporal.PlainDate 1999-12-31 / Received: Temporal.PlainDate 2020-01-02
  • test/js/bun/test/snapshot-tests/bun-snapshots.test.ts: snapshot serialization for all 8 types (committed .snap)

All new tests fail on the released bun and pass with this change. Also verified against the engine itself: for historic sub-minute offsets (Africa/Monrovia 1950), extreme years, and non-ISO calendars, the inspected text is byte-identical to the real toString(). Existing suites (inspect.test.js, expect.test.js 417 tests, console, snapshot, util, deep-equals-temporal) pass.

Note for #37018

#37018 (TOML Temporal) adds the same Bun__JSValue__temporalObjectType classifier (same name, same discriminants, same location in bindings.cpp). Whichever lands second will hit a small merge conflict there; resolve by keeping one copy of the classifier. The TOML-specific formatter in that PR stays separate since TOML needs annotation-free output.


no test proof · iteration 0 · Platform-specific test(s) that do not run on this machine. Deferring to CI, which covers all platforms: test/js/bun/util/inspect.test.js