From dff1d8ae6adcc6300d4e517e8708dddd4fe66d02 Mon Sep 17 00:00:00 2001 From: Justin Grant Date: Wed, 19 Jul 2023 00:49:01 -0700 Subject: [PATCH] Polyfill: Implement proposal-canonical-tz Stage 3 --- polyfill/index.d.ts | 1 + polyfill/lib/ecmascript.mjs | 20 ++++++++++++++++++-- polyfill/lib/intl.mjs | 2 +- polyfill/lib/timezone.mjs | 7 ++++++- polyfill/lib/zoneddatetime.mjs | 2 +- 5 files changed, 27 insertions(+), 5 deletions(-) diff --git a/polyfill/index.d.ts b/polyfill/index.d.ts index 784677c9ce..b11758ea38 100644 --- a/polyfill/index.d.ts +++ b/polyfill/index.d.ts @@ -1138,6 +1138,7 @@ export namespace Temporal { static from(timeZone: TimeZoneLike): Temporal.TimeZone | TimeZoneProtocol; constructor(timeZoneIdentifier: string); readonly id: string; + equals(timeZone: TimeZoneLike): boolean; getOffsetNanosecondsFor(instant: Temporal.Instant | string): number; getOffsetStringFor(instant: Temporal.Instant | string): string; getPlainDateTimeFor(instant: Temporal.Instant | string, calendar?: CalendarLike): Temporal.PlainDateTime; diff --git a/polyfill/lib/ecmascript.mjs b/polyfill/lib/ecmascript.mjs index ded5447f85..e3b8d4ae8b 100644 --- a/polyfill/lib/ecmascript.mjs +++ b/polyfill/lib/ecmascript.mjs @@ -2132,7 +2132,7 @@ export function ToTemporalTimeZoneSlotValue(temporalTimeZoneLike) { const record = GetAvailableNamedTimeZoneIdentifier(tzName); if (!record) throw new RangeError(`Unrecognized time zone ${tzName}`); - return record.primaryIdentifier; + return record.identifier; } if (z) return 'UTC'; // if !tzName && !z then offset must be present @@ -2160,7 +2160,23 @@ export function TimeZoneEquals(one, two) { if (one === two) return true; const tz1 = ToTemporalTimeZoneIdentifier(one); const tz2 = ToTemporalTimeZoneIdentifier(two); - return tz1 === tz2; + if (tz1 === tz2) return true; + const offsetMinutes1 = ParseTimeZoneIdentifier(tz1).offsetMinutes; + const offsetMinutes2 = ParseTimeZoneIdentifier(tz2).offsetMinutes; + if (offsetMinutes1 === undefined && offsetMinutes2 === undefined) { + // It's costly to call GetAvailableNamedTimeZoneIdentifier, so (unlike the + // spec) the polyfill will early-return if one of them isn't recognized. Try + // the second ID first because it's more likely to be unknown, because it + // can come from the argument of TimeZone.p.equals as opposed to the first + // ID which comes from the receiver. + const idRecord2 = GetAvailableNamedTimeZoneIdentifier(tz2); + if (!idRecord2) return false; + const idRecord1 = GetAvailableNamedTimeZoneIdentifier(tz1); + if (!idRecord1) return false; + return idRecord1.primaryIdentifier === idRecord2.primaryIdentifier; + } else { + return offsetMinutes1 === offsetMinutes2; + } } export function TemporalDateTimeToDate(dateTime) { diff --git a/polyfill/lib/intl.mjs b/polyfill/lib/intl.mjs index 9d78ee4301..d86269a5a9 100644 --- a/polyfill/lib/intl.mjs +++ b/polyfill/lib/intl.mjs @@ -139,7 +139,7 @@ Object.defineProperty(DateTimeFormat, 'prototype', { function resolvedOptions() { const resolved = this[ORIGINAL].resolvedOptions(); - resolved.timeZone = this[TZ_CANONICAL]; + resolved.timeZone = this[TZ_ORIGINAL]; return resolved; } diff --git a/polyfill/lib/timezone.mjs b/polyfill/lib/timezone.mjs index 83e40d30eb..86ea38c448 100644 --- a/polyfill/lib/timezone.mjs +++ b/polyfill/lib/timezone.mjs @@ -28,7 +28,7 @@ export class TimeZone { } else { const record = ES.GetAvailableNamedTimeZoneIdentifier(stringIdentifier); if (!record) throw new RangeError(`Invalid time zone identifier: ${stringIdentifier}`); - stringIdentifier = record.primaryIdentifier; + stringIdentifier = record.identifier; } CreateSlots(this); SetSlot(this, TIMEZONE_ID, stringIdentifier); @@ -46,6 +46,11 @@ export class TimeZone { if (!ES.IsTemporalTimeZone(this)) throw new TypeError('invalid receiver'); return GetSlot(this, TIMEZONE_ID); } + equals(other) { + if (!ES.IsTemporalTimeZone(this)) throw new TypeError('invalid receiver'); + const timeZoneSlotValue = ES.ToTemporalTimeZoneSlotValue(other); + return ES.TimeZoneEquals(this, timeZoneSlotValue); + } getOffsetNanosecondsFor(instant) { if (!ES.IsTemporalTimeZone(this)) throw new TypeError('invalid receiver'); instant = ES.ToTemporalInstant(instant); diff --git a/polyfill/lib/zoneddatetime.mjs b/polyfill/lib/zoneddatetime.mjs index cbd4f8c8ce..ed1a41437d 100644 --- a/polyfill/lib/zoneddatetime.mjs +++ b/polyfill/lib/zoneddatetime.mjs @@ -478,7 +478,7 @@ export class ZonedDateTime { } else { const record = ES.GetAvailableNamedTimeZoneIdentifier(timeZoneIdentifier); if (!record) throw new RangeError(`toLocaleString formats built-in time zones, not ${timeZoneIdentifier}`); - optionsCopy.timeZone = record.primaryIdentifier; + optionsCopy.timeZone = record.identifier; } const formatter = new DateTimeFormat(locales, optionsCopy);