Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions lib/internal/process/pre_execution.js
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,8 @@ function prepareExecution(options) {

require('internal/dns/utils').initializeDns();

require('internal/util/temporal').initialize();

if (isMainThread) {
assert(internalBinding('worker').isMainThread);
// Worker threads will get the manifest in the message handler.
Expand Down
50 changes: 50 additions & 0 deletions lib/internal/util/inspect.js
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,8 @@ const {
kValidateObjectAllowArray,
} = require('internal/validators');

const temporal = require('internal/util/temporal');

let hexSlice;
let internalUrl;

Expand Down Expand Up @@ -1404,6 +1406,54 @@ function formatRaw(ctx, value, recurseTimes, typedArray) {
if (keys.length === 0 && protoProps === undefined) {
return base;
}
} else if (temporal.isTemporalDuration(value)) {
const prefix = getPrefix(constructor, tag, 'Temporal.Duration');
base = `${prefix}${temporal.TemporalDurationPrototypeToString(value)}`;
if (keys.length === 0 && protoProps === undefined) {
return ctx.stylize(base, 'date');
}
} else if (temporal.isTemporalInstant(value)) {
const prefix = getPrefix(constructor, tag, 'Temporal.Instant');
base = `${prefix}${temporal.TemporalInstantPrototypeToString(value)}`;
if (keys.length === 0 && protoProps === undefined) {
return ctx.stylize(base, 'date');
}
} else if (temporal.isTemporalPlainDate(value)) {
const prefix = getPrefix(constructor, tag, 'Temporal.PlainDate');
base = `${prefix}${temporal.TemporalPlainDatePrototypeToString(value)}`;
if (keys.length === 0 && protoProps === undefined) {
return ctx.stylize(base, 'date');
}
} else if (temporal.isTemporalPlainDateTime(value)) {
const prefix = getPrefix(constructor, tag, 'Temporal.PlainDateTime');
base = `${prefix}${temporal.TemporalPlainDateTimePrototypeToString(value)}`;
if (keys.length === 0 && protoProps === undefined) {
return ctx.stylize(base, 'date');
}
} else if (temporal.isTemporalPlainMonthDay(value)) {
const prefix = getPrefix(constructor, tag, 'Temporal.PlainMonthDay');
base = `${prefix}${temporal.TemporalPlainMonthDayPrototypeToString(value)}`;
if (keys.length === 0 && protoProps === undefined) {
return ctx.stylize(base, 'date');
}
} else if (temporal.isTemporalPlainTime(value)) {
const prefix = getPrefix(constructor, tag, 'Temporal.PlainTime');
base = `${prefix}${temporal.TemporalPlainTimePrototypeToString(value)}`;
if (keys.length === 0 && protoProps === undefined) {
return ctx.stylize(base, 'date');
}
} else if (temporal.isTemporalPlainYearMonth(value)) {
const prefix = getPrefix(constructor, tag, 'Temporal.PlainYearMonth');
base = `${prefix}${temporal.TemporalPlainYearMonthPrototypeToString(value)}`;
if (keys.length === 0 && protoProps === undefined) {
return ctx.stylize(base, 'date');
}
} else if (temporal.isTemporalZonedDateTime(value)) {
const prefix = getPrefix(constructor, tag, 'Temporal.ZonedDateTime');
base = `${prefix}${temporal.TemporalZonedDateTimePrototypeToString(value)}`;
if (keys.length === 0 && protoProps === undefined) {
return ctx.stylize(base, 'date');
}
} else {
if (keys.length === 0 && protoProps === undefined) {
if (isExternal(value)) {
Expand Down
93 changes: 93 additions & 0 deletions lib/internal/util/temporal.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
'use strict';

// This module is initialized in the pre-execution phase, before user code is
// run. Since the module namespace is not populated at snapshot time, this
// module should be not be imported with a destructuring pattern unless
// imported lazily.

// TODO(Renegade334): Remove typecheck methods once available via V8 API.
// Replace with primordials if/when temporal_rs becomes a mandatory V8
// build dependency and the harmony_temporal flag is removed.

const {
ArrayPrototypeForEach,
FunctionPrototypeCall,
ObjectEntries,
ObjectGetOwnPropertyDescriptors,
SafeMap,
StringPrototypeSubstring,
StringPrototypeToUpperCase,
globalThis,
uncurryThis,
} = primordials;

// The keys of this Map are the Temporal constructor names.
// The values are the names of the most lightweight brand-aware getter within
// each prototype, which can be called speculatively as a brand check.
const constructors = new SafeMap()
.set('Duration', 'nanoseconds')
.set('Instant', 'epochNanoseconds')
.set('PlainDate', 'calendarId')
.set('PlainDateTime', 'calendarId')
.set('PlainMonthDay', 'calendarId')
.set('PlainTime', 'nanosecond')
.set('PlainYearMonth', 'calendarId')
.set('ZonedDateTime', 'calendarId');

exports.initialize = () => {
const { Temporal } = globalThis;

if (Temporal === undefined) {
for (const name of constructors.keys()) {
exports[`isTemporal${name}`] = () => false;
}
return;
}

for (const { 0: name, 1: brandedGetter } of constructors.entries()) {
const constructor = Temporal[name];
exports[`Temporal${name}`] = constructor;

ArrayPrototypeForEach(
ObjectEntries(ObjectGetOwnPropertyDescriptors(constructor)),
({ 0: key, 1: desc }) => {
if (typeof key !== 'string' || typeof desc.value !== 'function') return;

exports[`Temporal${name}${capitalizeKey(key)}`] = desc.value;
},
);

ArrayPrototypeForEach(
ObjectEntries(ObjectGetOwnPropertyDescriptors(constructor.prototype)),
({ 0: key, 1: desc }) => {
if (typeof key !== 'string') return;

if ('get' in desc) {
exports[`Temporal${name}PrototypeGet${capitalizeKey(key)}`] = uncurryThis(desc.get);
if (key === brandedGetter) {
exports[`isTemporal${name}`] = makeTypeCheckMethod(desc.get);
}
} else {
exports[`Temporal${name}Prototype${capitalizeKey(key)}`] = typeof desc.value === 'function' ?
uncurryThis(desc.value) :
desc.value;
}
},
);
}
};

function makeTypeCheckMethod(getter) {
return (value) => {
try {
FunctionPrototypeCall(getter, value);
return true;
} catch {
return false;
}
};
}

function capitalizeKey(key) {
return `${StringPrototypeToUpperCase(key[0])}${StringPrototypeSubstring(key, 1)}`;
}
74 changes: 74 additions & 0 deletions test/parallel/test-util-inspect-temporal.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
'use strict';

const common = require('../common');
const { suite, test } = require('node:test');
const util = require('util');

if (!common.hasTemporal) {
common.skip('Temporal is not enabled');
}

const colorRegex = new RegExp(`^\u001b\\[${util.inspect.colors[util.inspect.styles.date][0]}m`);

createSuite('Duration', [1, 2, 3, 4, 5, 6, 7, 8, 9], 'P1Y2M3W4DT5H6M7.008009S');
createSuite('Instant', [112233445566778899n], '1973-07-22T23:57:25.566778899Z');
createSuite('PlainDate', [1901, 2, 3], '1901-02-03');
createSuite('PlainDateTime', [1901, 2, 3, 4, 5, 6, 7, 8, 9], '1901-02-03T04:05:06.007008009');
createSuite('PlainMonthDay', [1, 2], '01-02');
createSuite('PlainTime', [1, 2, 3, 4, 5, 6], '01:02:03.004005006');
createSuite('PlainYearMonth', [1901, 2], '1901-02');
createSuite('ZonedDateTime', [112233445566778899n, 'UTC'], '1973-07-22T23:57:25.566778899+00:00[UTC]');

function createSuite(name, params, expected) {
const constructor = Temporal[name];

suite(`Temporal.${name}`, () => {
test('basic instance', (t) => {
t.assert.strictEqual(
util.inspect(new constructor(...params)),
`Temporal.${name} ${expected}`,
);
});

test('derived class instance', (t) => {
class X extends constructor {}
t.assert.strictEqual(
util.inspect(new X(...params)),
`X [Temporal.${name}] ${expected}`,
);
});

test('instance with additional properties', (t) => {
t.assert.strictEqual(
util.inspect(Object.assign(new constructor(...params), { foo: 'bar' }), { breakLength: Infinity }),
`Temporal.${name} ${expected} { foo: 'bar' }`,
);
});

test('prototype object', (t) => {
t.assert.strictEqual(
util.inspect(constructor.prototype),
`Object [Temporal.${name}] {}`,
);
});

test('instance with null prototype', (t) => {
t.assert.strictEqual(
util.inspect(
Object.setPrototypeOf(
new constructor(...params),
null,
),
),
`[Temporal.${name}: null prototype] ${expected}`,
);
});

test('instance with colors: true', (t) => {
t.assert.match(
util.inspect(new constructor(...params), { colors: true }),
colorRegex,
);
});
});
}