From 70d775c57d30c71b4857fc1892773e2a3eafb7af Mon Sep 17 00:00:00 2001 From: printfn <1643883+printfn@users.noreply.github.com> Date: Tue, 18 Feb 2025 03:02:34 +1300 Subject: [PATCH] fix(unstable/temporal): implement `Temporal.ZonedDateTime.getTimeZoneTransition` (#27770) This patches Temporal to support the `getTimeZoneTransition` method on `ZonedDateTime`. See https://github.com/denoland/deno/issues/27731. Co-authored-by: printfn --- runtime/js/99_main.js | 321 ++++++++---------- .../run/unstable_temporal_api_list/main.out | 32 +- .../run/unstable_temporal_api_patch/main.out | 1 + .../run/unstable_temporal_api_patch/main.ts | 5 + 4 files changed, 155 insertions(+), 204 deletions(-) diff --git a/runtime/js/99_main.js b/runtime/js/99_main.js index 2ff6094e7d..47c6b419a5 100644 --- a/runtime/js/99_main.js +++ b/runtime/js/99_main.js @@ -42,6 +42,7 @@ const { ObjectSetPrototypeOf, PromisePrototypeThen, PromiseResolve, + RangeError, StringPrototypePadEnd, Symbol, SymbolIterator, @@ -473,6 +474,136 @@ function exposeUnstableFeaturesForWindowOrWorkerGlobalScope(unstableFeatures) { } } +function updateTemporal() { + // Removes the obsoleted `Temporal` API. + // https://github.com/tc39/proposal-temporal/pull/2895 + // https://github.com/tc39/proposal-temporal/pull/2914 + // https://github.com/tc39/proposal-temporal/pull/2925 + if (typeof globalThis.Temporal.Instant.fromEpochSeconds === "undefined") { + throw "V8 removes obsoleted Temporal API now, no need to delete them"; + } + delete globalThis.Temporal.Instant.fromEpochSeconds; + delete globalThis.Temporal.Instant.fromEpochMicroseconds; + delete globalThis.Temporal.Instant.prototype.epochSeconds; + delete globalThis.Temporal.Instant.prototype.epochMicroseconds; + delete globalThis.Temporal.Instant.prototype.toZonedDateTime; + delete globalThis.Temporal.PlainDate.prototype.getISOFiels; // weird + delete globalThis.Temporal.PlainDate.prototype.getISOFields; + delete globalThis.Temporal.PlainDateTime.prototype.withPlainDate; + delete globalThis.Temporal.PlainDateTime.prototype.toPlainYearMonth; + delete globalThis.Temporal.PlainDateTime.prototype.toPlainMonthDay; + delete globalThis.Temporal.PlainDateTime.prototype.getISOFields; + delete globalThis.Temporal.PlainMonthDay.prototype.getISOFields; + delete globalThis.Temporal.PlainTime.prototype.calendar; + delete globalThis.Temporal.PlainTime.prototype.toPlainDateTime; + delete globalThis.Temporal.PlainTime.prototype.toZonedDateTime; + delete globalThis.Temporal.PlainTime.prototype.getISOFields; + delete globalThis.Temporal.PlainYearMonth.prototype.getISOFields; + delete globalThis.Temporal.ZonedDateTime.prototype.epochSeconds; + delete globalThis.Temporal.ZonedDateTime.prototype.epochMicroseconds; + delete globalThis.Temporal.ZonedDateTime.prototype.withPlainDate; + delete globalThis.Temporal.ZonedDateTime.prototype.toPlainYearMonth; + delete globalThis.Temporal.ZonedDateTime.prototype.toPlainMonthDay; + delete globalThis.Temporal.ZonedDateTime.prototype.getISOFields; + delete globalThis.Temporal.Now.zonedDateTime; + delete globalThis.Temporal.Now.plainDateTime; + delete globalThis.Temporal.Now.plainDate; + delete globalThis.Temporal.Calendar; + delete globalThis.Temporal.TimeZone; + + // Modify `Temporal.Calendar` to calendarId string + ArrayPrototypeForEach([ + globalThis.Temporal.PlainDate, + globalThis.Temporal.PlainDateTime, + globalThis.Temporal.PlainMonthDay, + globalThis.Temporal.PlainYearMonth, + globalThis.Temporal.ZonedDateTime, + ], (target) => { + const getCalendar = + ObjectGetOwnPropertyDescriptor(target.prototype, "calendar").get; + ObjectDefineProperty(target.prototype, "calendarId", { + __proto__: null, + get: function calendarId() { + return FunctionPrototypeCall(getCalendar, this).id; + }, + enumerable: false, + configurable: true, + }); + delete target.prototype.calendar; + }); + + // Modify `Temporal.TimeZone` to timeZoneId string + { + const getTimeZone = ObjectGetOwnPropertyDescriptor( + globalThis.Temporal.ZonedDateTime.prototype, + "timeZone", + ).get; + ObjectDefineProperty( + globalThis.Temporal.ZonedDateTime.prototype, + "timeZoneId", + { + __proto__: null, + get: function timeZoneId() { + return FunctionPrototypeCall(getTimeZone, this).id; + }, + enumerable: false, + configurable: true, + }, + ); + ObjectAssign(globalThis.Temporal.ZonedDateTime.prototype, { + getTimeZoneTransition(options) { + if (options === undefined) { + throw new TypeError("options parameter is required"); + } + if (typeof options === "string") { + options = { + direction: options, + }; + } + const direction = options.direction; + if (direction === undefined) { + throw new TypeError("direction option is required"); + } + const tz = FunctionPrototypeCall(getTimeZone, this); + let resultInstant; + switch (direction) { + case "next": + resultInstant = tz.getNextTransition(this.toInstant()); + break; + case "previous": + resultInstant = tz.getPreviousTransition(this.toInstant()); + break; + default: + throw new RangeError( + `direction must be one of next, previous, not ${options.direction}`, + ); + } + return resultInstant?.toZonedDateTimeISO(tz.id) ?? null; + }, + }); + delete globalThis.Temporal.ZonedDateTime.prototype.timeZone; + } + { + const nowTimeZone = globalThis.Temporal.Now.timeZone; + ObjectDefineProperty(globalThis.Temporal.Now, "timeZoneId", { + __proto__: null, + value: function timeZoneId() { + return nowTimeZone().id; + }, + writable: true, + enumerable: false, + configurable: true, + }); + delete globalThis.Temporal.Now.timeZone; + } + + // deno-lint-ignore prefer-primordials + if (new Temporal.Duration().toLocaleString("en-US") !== "PT0S") { + throw "V8 supports Temporal.Duration.prototype.toLocaleString now, no need to shim it"; + } + shimTemporalDurationToLocaleString(); +} + function shimTemporalDurationToLocaleString() { const DurationFormat = Intl.DurationFormat; if (!DurationFormat) { @@ -803,102 +934,7 @@ function bootstrapMainRuntime(runtimeOptions, warmup = false) { delete globalThis.Temporal; delete globalThis.Date.prototype.toTemporalInstant; } else { - // Removes the obsoleted `Temporal` API. - // https://github.com/tc39/proposal-temporal/pull/2895 - // https://github.com/tc39/proposal-temporal/pull/2914 - // https://github.com/tc39/proposal-temporal/pull/2925 - if (typeof globalThis.Temporal.Instant.fromEpochSeconds === "undefined") { - throw "V8 removes obsoleted Temporal API now, no need to delete them"; - } - delete globalThis.Temporal.Instant.fromEpochSeconds; - delete globalThis.Temporal.Instant.fromEpochMicroseconds; - delete globalThis.Temporal.Instant.prototype.epochSeconds; - delete globalThis.Temporal.Instant.prototype.epochMicroseconds; - delete globalThis.Temporal.Instant.prototype.toZonedDateTime; - delete globalThis.Temporal.PlainDate.prototype.getISOFiels; // weird - delete globalThis.Temporal.PlainDate.prototype.getISOFields; - delete globalThis.Temporal.PlainDateTime.prototype.withPlainDate; - delete globalThis.Temporal.PlainDateTime.prototype.toPlainYearMonth; - delete globalThis.Temporal.PlainDateTime.prototype.toPlainMonthDay; - delete globalThis.Temporal.PlainDateTime.prototype.getISOFields; - delete globalThis.Temporal.PlainMonthDay.prototype.getISOFields; - delete globalThis.Temporal.PlainTime.prototype.calendar; - delete globalThis.Temporal.PlainTime.prototype.toPlainDateTime; - delete globalThis.Temporal.PlainTime.prototype.toZonedDateTime; - delete globalThis.Temporal.PlainTime.prototype.getISOFields; - delete globalThis.Temporal.PlainYearMonth.prototype.getISOFields; - delete globalThis.Temporal.ZonedDateTime.prototype.epochSeconds; - delete globalThis.Temporal.ZonedDateTime.prototype.epochMicroseconds; - delete globalThis.Temporal.ZonedDateTime.prototype.withPlainDate; - delete globalThis.Temporal.ZonedDateTime.prototype.toPlainYearMonth; - delete globalThis.Temporal.ZonedDateTime.prototype.toPlainMonthDay; - delete globalThis.Temporal.ZonedDateTime.prototype.getISOFields; - delete globalThis.Temporal.Now.zonedDateTime; - delete globalThis.Temporal.Now.plainDateTime; - delete globalThis.Temporal.Now.plainDate; - delete globalThis.Temporal.Calendar; - delete globalThis.Temporal.TimeZone; - - // Modify `Temporal.Calendar` to calendarId string - ArrayPrototypeForEach([ - globalThis.Temporal.PlainDate, - globalThis.Temporal.PlainDateTime, - globalThis.Temporal.PlainMonthDay, - globalThis.Temporal.PlainYearMonth, - globalThis.Temporal.ZonedDateTime, - ], (target) => { - const getCalendar = - ObjectGetOwnPropertyDescriptor(target.prototype, "calendar").get; - ObjectDefineProperty(target.prototype, "calendarId", { - __proto__: null, - get: function calendarId() { - return FunctionPrototypeCall(getCalendar, this).id; - }, - enumerable: false, - configurable: true, - }); - delete target.prototype.calendar; - }); - - // Modify `Temporal.TimeZone` to timeZoneId string - { - const getTimeZone = ObjectGetOwnPropertyDescriptor( - globalThis.Temporal.ZonedDateTime.prototype, - "timeZone", - ).get; - ObjectDefineProperty( - globalThis.Temporal.ZonedDateTime.prototype, - "timeZoneId", - { - __proto__: null, - get: function timeZoneId() { - return FunctionPrototypeCall(getTimeZone, this).id; - }, - enumerable: false, - configurable: true, - }, - ); - delete globalThis.Temporal.ZonedDateTime.prototype.timeZone; - } - { - const nowTimeZone = globalThis.Temporal.Now.timeZone; - ObjectDefineProperty(globalThis.Temporal.Now, "timeZoneId", { - __proto__: null, - value: function timeZoneId() { - return nowTimeZone().id; - }, - writable: true, - enumerable: false, - configurable: true, - }); - delete globalThis.Temporal.Now.timeZone; - } - - // deno-lint-ignore prefer-primordials - if (new Temporal.Duration().toLocaleString("en-US") !== "PT0S") { - throw "V8 supports Temporal.Duration.prototype.toLocaleString now, no need to shim it"; - } - shimTemporalDurationToLocaleString(); + updateTemporal(); } // Setup `Deno` global - we're actually overriding already existing global @@ -1014,98 +1050,7 @@ function bootstrapWorkerRuntime( delete globalThis.Temporal; delete globalThis.Date.prototype.toTemporalInstant; } else { - // Removes the obsoleted `Temporal` API. - // https://github.com/tc39/proposal-temporal/pull/2895 - // https://github.com/tc39/proposal-temporal/pull/2914 - // https://github.com/tc39/proposal-temporal/pull/2925 - if (typeof globalThis.Temporal.Instant.fromEpochSeconds === "undefined") { - throw "V8 removes obsoleted Temporal API now, no need to delete them"; - } - delete globalThis.Temporal.Instant.fromEpochSeconds; - delete globalThis.Temporal.Instant.fromEpochMicroseconds; - delete globalThis.Temporal.Instant.prototype.epochSeconds; - delete globalThis.Temporal.Instant.prototype.epochMicroseconds; - delete globalThis.Temporal.Instant.prototype.toZonedDateTime; - delete globalThis.Temporal.PlainDate.prototype.getISOFiels; // weird - delete globalThis.Temporal.PlainDate.prototype.getISOFields; - delete globalThis.Temporal.PlainDateTime.prototype.withPlainDate; - delete globalThis.Temporal.PlainDateTime.prototype.toPlainYearMonth; - delete globalThis.Temporal.PlainDateTime.prototype.toPlainMonthDay; - delete globalThis.Temporal.PlainDateTime.prototype.getISOFields; - delete globalThis.Temporal.PlainMonthDay.prototype.getISOFields; - delete globalThis.Temporal.PlainTime.prototype.calendar; - delete globalThis.Temporal.PlainTime.prototype.toPlainDateTime; - delete globalThis.Temporal.PlainTime.prototype.toZonedDateTime; - delete globalThis.Temporal.PlainTime.prototype.getISOFields; - delete globalThis.Temporal.PlainYearMonth.prototype.getISOFields; - delete globalThis.Temporal.ZonedDateTime.prototype.epochSeconds; - delete globalThis.Temporal.ZonedDateTime.prototype.epochMicroseconds; - delete globalThis.Temporal.ZonedDateTime.prototype.withPlainDate; - delete globalThis.Temporal.ZonedDateTime.prototype.toPlainYearMonth; - delete globalThis.Temporal.ZonedDateTime.prototype.toPlainMonthDay; - delete globalThis.Temporal.ZonedDateTime.prototype.getISOFields; - delete globalThis.Temporal.Now.zonedDateTime; - delete globalThis.Temporal.Now.plainDateTime; - delete globalThis.Temporal.Now.plainDate; - delete globalThis.Temporal.Calendar; - delete globalThis.Temporal.TimeZone; - - // Modify `Temporal.Calendar` to calendarId string - ArrayPrototypeForEach([ - globalThis.Temporal.PlainDate, - globalThis.Temporal.PlainDateTime, - globalThis.Temporal.PlainMonthDay, - globalThis.Temporal.PlainYearMonth, - globalThis.Temporal.ZonedDateTime, - ], (target) => { - const getCalendar = - ObjectGetOwnPropertyDescriptor(target.prototype, "calendar").get; - ObjectDefineProperty(target.prototype, "calendarId", { - __proto__: null, - get: function calendarId() { - return FunctionPrototypeCall(getCalendar, this).id; - }, - enumerable: false, - configurable: true, - }); - delete target.prototype.calendar; - }); - - // Modify `Temporal.TimeZone` to timeZoneId string - { - const getTimeZone = ObjectGetOwnPropertyDescriptor( - globalThis.Temporal.ZonedDateTime.prototype, - "timeZone", - ).get; - ObjectDefineProperty( - globalThis.Temporal.ZonedDateTime.prototype, - "timeZoneId", - { - __proto__: null, - get: function timeZoneId() { - return FunctionPrototypeCall(getTimeZone, this).id; - }, - enumerable: false, - configurable: true, - }, - ); - delete globalThis.Temporal.ZonedDateTime.prototype.timeZone; - } - { - const nowTimeZone = globalThis.Temporal.Now.timeZone; - ObjectDefineProperty(globalThis.Temporal.Now, "timeZoneId", { - __proto__: null, - value: function timeZoneId() { - return nowTimeZone().id; - }, - writable: true, - enumerable: false, - configurable: true, - }); - delete globalThis.Temporal.Now.timeZone; - } - - shimTemporalDurationToLocaleString(); + updateTemporal(); } // Setup `Deno` global - we're actually overriding already existing global diff --git a/tests/specs/run/unstable_temporal_api_list/main.out b/tests/specs/run/unstable_temporal_api_list/main.out index bdfeab5ace..d13fbb420c 100644 --- a/tests/specs/run/unstable_temporal_api_list/main.out +++ b/tests/specs/run/unstable_temporal_api_list/main.out @@ -39,22 +39,22 @@ Temporal.Instant.prototype ] Temporal.ZonedDateTime.prototype [ - "add", "calendarId", "constructor", - "day", "dayOfWeek", "dayOfYear", - "daysInMonth", "daysInWeek", "daysInYear", - "epochMilliseconds", "epochNanoseconds", "equals", - "era", "eraYear", "hour", - "hoursInDay", "inLeapYear", "microsecond", - "millisecond", "minute", "month", - "monthCode", "monthsInYear", "nanosecond", - "offset", "offsetNanoseconds", "round", - "second", "since", "startOfDay", - "subtract", "timeZoneId", "toInstant", - "toJSON", "toLocaleString", "toPlainDate", - "toPlainDateTime", "toPlainTime", "toString", - "until", "valueOf", "weekOfYear", - "with", "withCalendar", "withPlainTime", - "withTimeZone", "year" + "add", "calendarId", "constructor", + "day", "dayOfWeek", "dayOfYear", + "daysInMonth", "daysInWeek", "daysInYear", + "epochMilliseconds", "epochNanoseconds", "equals", + "era", "eraYear", "getTimeZoneTransition", + "hour", "hoursInDay", "inLeapYear", + "microsecond", "millisecond", "minute", + "month", "monthCode", "monthsInYear", + "nanosecond", "offset", "offsetNanoseconds", + "round", "second", "since", + "startOfDay", "subtract", "timeZoneId", + "toInstant", "toJSON", "toLocaleString", + "toPlainDate", "toPlainDateTime", "toPlainTime", + "toString", "until", "valueOf", + "weekOfYear", "with", "withCalendar", + "withPlainTime", "withTimeZone", "year" ] Temporal.PlainDate.prototype [ diff --git a/tests/specs/run/unstable_temporal_api_patch/main.out b/tests/specs/run/unstable_temporal_api_patch/main.out index ec12b9e463..35651f6971 100644 --- a/tests/specs/run/unstable_temporal_api_patch/main.out +++ b/tests/specs/run/unstable_temporal_api_patch/main.out @@ -6,3 +6,4 @@ UTC undefined undefined 1 day, 6 hr, 30 min +2020-03-08T03:00:00-04:00[America/New_York] diff --git a/tests/specs/run/unstable_temporal_api_patch/main.ts b/tests/specs/run/unstable_temporal_api_patch/main.ts index ff92cf91c2..f4076332b5 100644 --- a/tests/specs/run/unstable_temporal_api_patch/main.ts +++ b/tests/specs/run/unstable_temporal_api_patch/main.ts @@ -12,3 +12,8 @@ console.log(zoned.timeZone); const duration = Temporal.Duration.from("P1DT6H30M"); console.log(duration.toLocaleString("en-US")); + +const transition = Temporal.ZonedDateTime.from( + "2020-01-01T00:00:00-05:00[America/New_York]", +).getTimeZoneTransition("next"); +console.log(transition);