From dc57ba9207266ec96af1a66de2560bc953d5eb5e Mon Sep 17 00:00:00 2001 From: Renegade334 Date: Wed, 6 May 2026 03:21:00 +0100 Subject: [PATCH] inspect: add Temporal support Signed-off-by: Renegade334 --- lib/internal/util/inspect.js | 50 ++++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/lib/internal/util/inspect.js b/lib/internal/util/inspect.js index c611eb97fc0755..03d5ed0cb6e4a9 100644 --- a/lib/internal/util/inspect.js +++ b/lib/internal/util/inspect.js @@ -218,6 +218,47 @@ const builtInObjects = new SafeSet( // https://tc39.es/ecma262/#sec-IsHTMLDDA-internal-slot const isUndetectableObject = (v) => typeof v === 'undefined' && v !== undefined; +const temporalConstructors = [ + { name: 'Duration', fullName: 'Temporal.Duration' }, + { name: 'Instant', fullName: 'Temporal.Instant' }, + { name: 'PlainDate', fullName: 'Temporal.PlainDate' }, + { name: 'PlainDateTime', fullName: 'Temporal.PlainDateTime' }, + { name: 'PlainMonthDay', fullName: 'Temporal.PlainMonthDay' }, + { name: 'PlainTime', fullName: 'Temporal.PlainTime' }, + { name: 'PlainYearMonth', fullName: 'Temporal.PlainYearMonth' }, + { name: 'ZonedDateTime', fullName: 'Temporal.ZonedDateTime' }, +]; + +// Best-effort, using instanceof. Polyfills will likely pass these checks. +// Returns void if either the type checks fail or the attempt at calling +// .toString() fails, so that format() can fall back to standard object +// formatting in either case. +// TODO: Hopefully the V8 API will expose typechecks at some point. +function getTemporalInfo(value) { + try { + const { Temporal } = globalThis; + if (Temporal === undefined) { + return; + } + for (let i = 0; i < temporalConstructors.length; i++) { + const entry = temporalConstructors[i]; + const constructor = Temporal[entry.name]; + if (typeof constructor !== 'function') { + return; + } + if (!FunctionPrototypeSymbolHasInstance(constructor, value)) { + continue; + } + const { prototype } = constructor; + if (!ObjectPrototypeHasOwnProperty(prototype, 'toString')) { + return; + } + const result = FunctionPrototypeCall(prototype.toString, value); + return { ...entry, result }; + } + } catch { /* void */ } +} + // These options must stay in sync with `getUserOptions`. So if any option will // be added or removed, `getUserOptions` must also be updated accordingly. const inspectDefaultOptions = ObjectSeal({ @@ -1321,6 +1362,7 @@ function formatRaw(ctx, value, recurseTimes, typedArray) { if (noIterator) { keys = getKeys(value, ctx.showHidden); braces = ['{', '}']; + let temporalInfo; if (typeof value === 'function') { base = getFunctionBase(ctx, value, constructor, tag); if (keys.length === 0 && protoProps === undefined) @@ -1404,6 +1446,14 @@ function formatRaw(ctx, value, recurseTimes, typedArray) { if (keys.length === 0 && protoProps === undefined) { return base; } + } else if ((temporalInfo = getTemporalInfo(value))) { + const prefix = constructor === temporalInfo.name && tag === temporalInfo.fullName ? + (temporalInfo.fullName + ' ') : + getPrefix(constructor, tag, temporalInfo.fullName); + base = `${prefix}${temporalInfo.result}`; + if (keys.length === 0 && protoProps === undefined) { + return ctx.stylize(base, 'date'); + } } else { if (keys.length === 0 && protoProps === undefined) { if (isExternal(value)) {