From d983dfacc960b2c86edad3e2451bec96c02da3b2 Mon Sep 17 00:00:00 2001 From: Justin Grant Date: Mon, 12 Jun 2023 15:12:57 -0700 Subject: [PATCH 1/2] Editorial: Refactor time zone identifier spec text This commit builds on the new ECMA-262 text for time zone identifiers that was introduced in https://github.com/tc39/ecma262/pull/3035. This commit adds Temporal-specific AOs and makes a handful of edits to ECMA-262 AOs for changes that didn't apply to %Date% but that will be required after Temporal is merged into ECMA-262. This commit also revises the ASCII-case-insensitive section to address feedback from ECMA-262 editors before this text was removed from https://github.com/tc39/ecma262/pull/3035: * Remove the "sequence of code points" info because only Strings seemed to use these definitions. * Minor wordsmithing, e.g. "string value" => "String" --- package-lock.json | 14 +-- package.json | 2 +- spec/intl.html | 187 ++++++++++++++++++++++++++++------------ spec/mainadditions.html | 81 ++++++++++++++--- spec/temporal.html | 6 +- spec/timezone.html | 148 ++++++++++--------------------- 6 files changed, 262 insertions(+), 176 deletions(-) diff --git a/package-lock.json b/package-lock.json index fa8ffd313a..8b64fdad77 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,7 +9,7 @@ "version": "1.0.0", "license": "BSD-3-Clause", "devDependencies": { - "@tc39/ecma262-biblio": "=2.1.2576", + "@tc39/ecma262-biblio": "=2.1.2577", "@typescript-eslint/eslint-plugin": "^5.59.9", "@typescript-eslint/parser": "^5.59.9", "ecmarkup": "^17.0.0", @@ -323,9 +323,9 @@ } }, "node_modules/@tc39/ecma262-biblio": { - "version": "2.1.2576", - "resolved": "https://registry.npmjs.org/@tc39/ecma262-biblio/-/ecma262-biblio-2.1.2576.tgz", - "integrity": "sha512-GG+y00NrCcRy6oN2/4t50xaKXE0FA76ZuVAw0NjhjVS/yboxkN0QrPUMUY6OI9TzzuTM0sazRJOp6qsOJ0Kerg==", + "version": "2.1.2577", + "resolved": "https://registry.npmjs.org/@tc39/ecma262-biblio/-/ecma262-biblio-2.1.2577.tgz", + "integrity": "sha512-Grv210DOA5HGNo56tHAxP4ScujqG7NRMWIraGNSxtTxKxUdcTAubrcglqa6bbKNsTK+Dg2Bew8cSr06fJX827g==", "dev": true }, "node_modules/@tootallnate/once": { @@ -3062,9 +3062,9 @@ } }, "@tc39/ecma262-biblio": { - "version": "2.1.2576", - "resolved": "https://registry.npmjs.org/@tc39/ecma262-biblio/-/ecma262-biblio-2.1.2576.tgz", - "integrity": "sha512-GG+y00NrCcRy6oN2/4t50xaKXE0FA76ZuVAw0NjhjVS/yboxkN0QrPUMUY6OI9TzzuTM0sazRJOp6qsOJ0Kerg==", + "version": "2.1.2577", + "resolved": "https://registry.npmjs.org/@tc39/ecma262-biblio/-/ecma262-biblio-2.1.2577.tgz", + "integrity": "sha512-Grv210DOA5HGNo56tHAxP4ScujqG7NRMWIraGNSxtTxKxUdcTAubrcglqa6bbKNsTK+Dg2Bew8cSr06fJX827g==", "dev": true }, "@tootallnate/once": { diff --git a/package.json b/package.json index e84e0b727e..c3918f5973 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,7 @@ }, "main": "polyfill/lib/index.mjs", "devDependencies": { - "@tc39/ecma262-biblio": "=2.1.2576", + "@tc39/ecma262-biblio": "=2.1.2577", "@typescript-eslint/eslint-plugin": "^5.59.9", "@typescript-eslint/parser": "^5.59.9", "ecmarkup": "^17.0.0", diff --git a/spec/intl.html b/spec/intl.html index 087d5e3d72..53e0579911 100644 --- a/spec/intl.html +++ b/spec/intl.html @@ -8,6 +8,7 @@

Amendments to the ECMAScript® 2023 Internationalization API Specification This section lists amendments which must be made to ECMA-402, the ECMAScript® 2023 Internationalization API Specification. Text to be added is marked like this, and text to be deleted is marked like this. + Blocks of unmodified text between modified sections are marked by [...].

This text is based on top of the ECMA-402 spec text from commit eb81befe8b739f976a3d6e68ec567302c4e217f0. @@ -40,15 +41,89 @@

Cas - -

Time Zone Names

+ + +

Use of the IANA Time Zone Database

- + This section replaces the Time Zone Names section in ECMA-402. +

+ Implementations that adopt the ECMAScript Internationalization API Specification are time zone aware: they use the IANA Time Zone Database https://www.iana.org/time-zones/ to supply time zone identifiers and data used in ECMAScript calculations and formatting. + This section defines how the IANA Time Zone Database should be used by time zone aware implementations. +

+

+ Except as overridden by AvailableNamedTimeZoneIdentifiers, each Zone in the IANA Time Zone Database must be a primary time zone identifier and each Link name in the IANA Time Zone Database must be a non-primary time zone identifier. + No String may be an available named time zone identifier unless it is a Zone name or a Link name in the IANA Time Zone Database. + Available named time zone identifiers returned by ECMAScript built-in objects must use the casing found in the IANA Time Zone Database. +

+

+ In the IANA Time Zone Database, the UTC time zone is represented by the Zone *"Etc/UTC"* which is distinct from the Zone *"Etc/GMT"*. + For historical reasons, ECMAScript uses *"UTC"* as the primary identifier for the former Zone and does not recognize the latter Zone as distinct, instead requiring *"Etc/UTC"*, *"Etc/GMT"*, and *"GMT"* (if available) to be non-primary identifiers that resolve to *"UTC"*. + This is the only deviation from the IANA Time Zone Database that is required of a time zone aware ECMAScript implementation. +

+

+ The IANA Time Zone Database is typically updated between five and ten times per year. + These updates may add new Zone or Link names, may change Zones to Links, and may change the UTC offsets and transitions associated with any Zone. + ECMAScript implementations are recommended to include updates to the IANA Time Zone Database as soon as possible. + Such prompt action ensures that ECMAScript programs can accurately perform time-zone-sensitive calculations and can use newly-added available named time zone identifiers supplied by external input or the host environment. +

+

+ If implementations revise time zone information during the lifetime of an agent, then which identifiers are supported, the primary time zone identifier associated with any identifier, and the UTC offsets and transitions associated with any Zone, must be consistent with results previously observed by that agent. + Due to the complexity of supporting this requirement, it is recommended that implementations maintain a fully consistent copy of the IANA Time Zone Database for the lifetime of each agent. +

+ +

This section complements but does not supersede .

+ +

- defines a set of abstract operations concerning the names of supported time zones. - This section introduces additional requirements on these operations for implementations. + The IANA Time Zone Database offers build options that affect which time zone identifiers are primary. + The default build options merge different countries' time zones, for example *"Atlantic/Reykjavik"* being a Link to the Zone *"Africa/Abidjan"*. + Geographically and politically distinct locations are likely to introduce divergent time zone rules in a future version of the IANA Time Zone Database. + Therefore, it is recommended that ECMAScript implementations instead use build options such as PACKRATDATA=backzone PACKRATLIST=zone.tab or a similar alternative that ensures at least one primary identifier for each ISO 3166-1 Alpha-2 country code.

-
+ + + +

AvailableNamedTimeZoneIdentifiers ( ): a List of Time Zone Identifier Records

+
+
description
+
+ Its result describes all available named time zone identifiers in this implementation, as well as the primary time zone identifier corresponding to each available named time zone identifier. + The List is ordered according to the [[Identifier]] field of each Time Zone Identifier Record. +
+
redefinition
+
true
+
+

This definition supersedes the definition provided in .

+ + 1. Let _identifiers_ be a List containing the String value of each Zone or Link name in the IANA Time Zone Database. + 1. Assert: No element of _identifiers_ is an ASCII-case-insensitive match for any other element. + 1. Assert: Every element of _identifiers_ identifies a Zone or Link name in the IANA Time Zone Database. + 1. Set _identifiers_ to SortStringListByCodeUnit(_identifiers_). + 1. Let _result_ be a new empty List. + 1. For each element _identifier_ of _identifiers_, do + 1. Let _primary_ be _identifier_. + 1. If _identifier_ is a non-primary time zone identifier and _identifier_ is not *"UTC"*, then + 1. Set _primary_ to the name of the primary time zone identifier that _identifier_ resolves to, according to the rules for resolving Link names in the IANA Time Zone Database. + 1. NOTE: An implementation may need to resolve _identifier_ iteratively to obtain the primary time zone identifier. + 1. If _primary_ is one of *"Etc/UTC"*, *"Etc/GMT"*, or *"GMT"*, set _primary_ to *"UTC"*. + 1. Let _record_ be the Time Zone Identifier Record { [[Identifier]]: _identifier_, [[PrimaryIdentifier]]: _primary_ }. + 1. Append _record_ to _result_. + 1. Assert: _result_ contains a Time Zone Identifier Record _r_ such that _r_.[[Identifier]] is *"UTC"* and _r_.[[PrimaryIdentifier]] is *"UTC"*. + 1. Return _result_. + + + + Time zone identifiers in the IANA Time Zone Database can change over time. + At a minimum, it is recommended that implementations limit changes to the result of AvailableNamedTimeZoneIdentifiers to the changes allowed by GetAvailableNamedTimeZoneIdentifier, for the lifetime of the surrounding agent. + Due to the complexity of supporting these recommendations, it is recommended that the result of AvailableNamedTimeZoneIdentifiers remains the same for the lifetime of the surrounding agent. + +
+
+
+ + + +

Time Zone Names

The ECMAScript 2023 Internationalization API Specification identifies time zones using the Zone and Link names of the IANA Time Zone Database. Their canonical form is the corresponding Zone name in the casing used in the IANA Time Zone Database except as specifically overridden by CanonicalizeTimeZoneName. @@ -58,70 +133,71 @@

Time Zone Namesonly such names), and use best available current and historical information about their offsets from UTC and their daylight saving time rules in calculations. However, the set of combinations of time zone name and language tag for which localized time zone names are available is implementation dependent.

- - -

IsValidTimeZoneName ( _timeZone_ )

+ +

IsValidTimeZoneName ( _timeZone_ )

-

- The abstract operation IsValidTimeZoneName takes argument _timeZone_, a String value, and verifies that it represents a valid Zone or Link name of the IANA Time Zone Database. -

+

+ The abstract operation IsValidTimeZoneName takes argument _timeZone_, a String value, and verifies that it represents a valid Zone or Link name of the IANA Time Zone Database. +

- - 1. If one of the Zone or Link names of the IANA Time Zone Database is an ASCII-case-insensitive match of _timeZone_, return *true*. - 1. If _timeZone_ is an ASCII-case-insensitive match of *"UTC"*, return *true*. - 1. Return *false*. - -
-
+ + 1. If one of the Zone or Link names of the IANA Time Zone Database is an ASCII-case-insensitive match of _timeZone_, return *true*. + 1. If _timeZone_ is an ASCII-case-insensitive match of *"UTC"*, return *true*. + 1. Return *false*. + + - +

CanonicalizeTimeZoneName ( - _timeZone_: a String value that is a validan available time zone name as verified by IsValidTimeZoneNameIsAvailableTimeZoneName, + _timeZone_: a String value that is a valid time zone name as verified by IsValidTimeZoneName, )

description
It returns the canonical and case-regularized form of _timeZone_.
-
redefinition
-
true
- 1. Let _ianaTimeZone_ be the String value of the Zone or Link name of the IANA Time Zone Database that is an ASCII-case-insensitive match of _timeZone_. 1. If _ianaTimeZone_ is a Link name, let _ianaTimeZone_ be the String value of the corresponding Zone name as specified in the file backward of the IANA Time Zone Database. 1. If _ianaTimeZone_ is *"Etc/UTC"* or *"Etc/GMT"*, return *"UTC"*. 1. Return _ianaTimeZone_. +
- -

This definition supersedes the definition provided in .

-
+ +

DefaultTimeZone ( ): a String

+ +
+
description
+
It returns a String value representing the host environment's current time zone, which is a valid () and canonicalized () time zone name.
+
+ +

+ This definition supersedes the definition provided in es2024, . +

- - -

- AvailableTimeZones ( - ): a List of Strings -

-
-
description
-
The returned List is a sorted List of supported Zone and Link names in the IANA Time Zone Database.
-
redefinition
-
true
-
- - 1. Let _timeZones_ be a List containing the String value of each Zone or Link name in the IANA Time Zone Database that is supported by the implementation. - 1. Assert: _timeZones_ contains *"UTC"*. - 1. Assert: _timeZones_ does not contain any element that does not identify a Zone or Link name in the IANA Time Zone Database. - 1. Return SortStringListByCodeUnit(_timeZones_). - + +

+ AvailableTimeZones ( + ): a List of Strings +

+
+
description
+
The returned List is a sorted List of supported Zone and Link names in the IANA Time Zone Database.
+
+ + 1. Let _timeZones_ be a List containing the String value of each Zone or Link name in the IANA Time Zone Database that is supported by the implementation. + 1. Assert: _timeZones_ contains *"UTC"*. + 1. Assert: _timeZones_ does not contain any element that does not identify a Zone or Link name in the IANA Time Zone Database. + 1. Return SortStringListByCodeUnit(_timeZones_). + -

This definition supersedes the definition provided in .

-
-
+

This definition supersedes the definition provided in [removed].

+
+

Abstract Operations

@@ -244,13 +320,14 @@

InitializeDateTimeFormat ( _dateTimeFormat_, _locales_, _options_ [ , _ 1. If _toLocaleStringTimeZone_ is present, then 1. Set _timeZone_ to _toLocaleStringTimeZone_. 1. Else, - 1. Set _timeZone_ to DefaultTimeZone(). + 1. Set _timeZone_ to SystemTimeZoneIdentifier(). 1. Else, 1. If _toLocaleStringTimeZone_ is present, throw a *TypeError* exception. 1. Set _timeZone_ to ? ToString(_timeZone_). - 1. If the result of IsValidTimeZoneName(_timeZone_)IsAvailableTimeZoneName(_timeZone_) is *false*, then + 1. Let _timeZoneIdentifierRecord_ be GetAvailableNamedTimeZoneIdentifier(_timeZone_). + 1. If the result of IsValidTimeZoneName(_timeZone_) is *false*_timeZoneIdentifierRecord_ is ~empty~, then 1. Throw a *RangeError* exception. - 1. Set _timeZone_ to CanonicalizeTimeZoneName(_timeZone_). + 1. Set _timeZone_ to CanonicalizeTimeZoneName(_timeZone_)_timeZoneIdentifierRecord_.[[PrimaryIdentifier]]. 1. Set _dateTimeFormat_.[[TimeZone]] to _timeZone_. 1. Let _formatOptions_ be a new Record. 1. Set _formatOptions_.[[hourCycle]] to _hc_. @@ -1275,7 +1352,8 @@

Intl.DateTimeFormat.prototype.resolvedOptions ( )

- In this version of the ECMAScript 2023 Internationalization API, the *"timeZone"* property will be the name of the default time zone if no *"timeZone"* property was provided in the options object provided to the Intl.DateTimeFormat constructor. The first edition left the *"timeZone"* property *undefined* in this case. + In this version of the ECMAScript Internationalization API, the *"timeZone"* property will be the name of the system time zone identifier if no *"timeZone"* property was provided in the options object provided to the Intl.DateTimeFormat constructor. + The first edition left the *"timeZone"* property *undefined* in this case. @@ -2465,8 +2543,9 @@

Temporal.ZonedDateTime.prototype.toLocaleString ( [ _locales_ [ , _options_ 1. Let _dateTimeFormat_ be ! OrdinaryCreateFromConstructor(%DateTimeFormat%, %DateTimeFormat.prototype%, « [[InitializedDateTimeFormat]], [[Locale]], [[Calendar]], [[NumberingSystem]], [[TimeZone]], [[Weekday]], [[Era]], [[Year]], [[Month]], [[Day]], [[DayPeriod]], [[Hour]], [[Minute]], [[Second]], [[FractionalSecondDigits]], [[TimeZoneName]], [[HourCycle]], [[Pattern]], [[BoundFormat]] »). 1. Let _timeZone_ be ? ToTemporalTimeZoneIdentifier(_zonedDateTime_.[[TimeZone]]). 1. If IsTimeZoneOffsetString(_timeZone_) is *true*, throw a *RangeError* exception. - 1. If IsAvailableTimeZoneName(_timeZone_) is *false*, throw a *RangeError* exception. - 1. Set _timeZone_ to CanonicalizeTimeZoneName(_timeZone_). + 1. Let _timeZoneIdentifierRecord_ be GetAvailableNamedTimeZoneIdentifier(_timeZone_). + 1. If _timeZoneIdentifierRecord_ is ~empty~, throw a *RangeError* exception. + 1. Set _timeZone_ to _timeZoneIdentifierRecord_.[[PrimaryIdentifier]]. 1. Perform ? InitializeDateTimeFormat(_dateTimeFormat_, _locales_, _options_, _timeZone_). 1. Let _calendar_ be ? ToTemporalCalendarIdentifier(_zonedDateTime_.[[Calendar]]). 1. If _calendar_ is not *"iso8601"* and not equal to _dateTimeFormat_.[[Calendar]], then diff --git a/spec/mainadditions.html b/spec/mainadditions.html index 2997428e6f..dc71ca178f 100644 --- a/spec/mainadditions.html +++ b/spec/mainadditions.html @@ -8,6 +8,7 @@

Amendments to the ECMAScript® 2023 Language Specification

This section lists amendments which must be made to ECMA-262, the ECMAScript® 2023 Language Specification, other than the addition of the new sections specifying the Temporal object and everything related to it. Text to be added is marked like this, and text to be deleted is marked like this. + Blocks of unmodified text between modified sections are marked by [...].

@@ -16,22 +17,17 @@

- This section intends to move the definitions of ASCII-uppercase, ASCII-lowercase, and ASCII-case-insensitive match from ECMA-402 into ECMA-262, after the definition of the ASCII word characters, and generalizes the former two definitions to cover sequences of code points. + This section intends to move the definitions of ASCII-uppercase, ASCII-lowercase, and ASCII-case-insensitive match from ECMA-402 into ECMA-262, after the definition of the ASCII word characters. + The definitions include minor editorial changes from ECMA-402 to address feedback from the ECMA-262 editors.

[...]

-

- The ASCII-uppercase of a String value _S_ is the String value derived from _S_ by replacing each occurrence of an ASCII lowercase letter code unit (0x0061 through 0x007A, inclusive) with the corresponding ASCII uppercase letter code unit (0x0041 through 0x005A, inclusive) while preserving all other code units. The ASCII-uppercase of a sequence of Unicode code points _A_ is the sequence of code points derived from _A_ by replacing each occurrence of an ASCII lowercase letter code point (U+0061 through U+007A, inclusive) with the corresponding ASCII uppercase letter code point (U+0041 through U+005A, inclusive) while preserving all other code points. -

-

- The ASCII-lowercase of a String value _S_ is the String value derived from _S_ by replacing each occurrence of an ASCII uppercase letter code unit (0x0041 through 0x005A, inclusive) with the corresponding ASCII lowercase letter code unit (0x0061 through 0x007A, inclusive) while preserving all other code units. The ASCII-lowercase of a sequence of Unicode code points _A_ is the sequence of code points derived from _A_ by replacing each occurrence of an ASCII uppercase letter code point (U+0041 through U+005A, inclusive) with the corresponding ASCII lowercase letter code point (U+0061 through U+007A, inclusive) while preserving all other code points. -

-

- A String value _A_ is an ASCII-case-insensitive match for String value _B_ if the ASCII-uppercase of _A_ is exactly the same sequence of code units as the ASCII-uppercase of _B_. A sequence of Unicode code points _A_ is an ASCII-case-insensitive match for _B_ if _B_ is an ASCII-case-insensitive match for ! CodePointsToString(_A_). -

+

The ASCII-uppercase of a String _S_ is the String derived from _S_ by replacing each occurrence of an ASCII lowercase letter code unit (0x0061 through 0x007A, inclusive) with the corresponding ASCII uppercase letter code unit (0x0041 through 0x005A, inclusive) while preserving all other code units.

+

The ASCII-lowercase of a String _S_ is the String derived from _S_ by replacing each occurrence of an ASCII uppercase letter code unit (0x0041 through 0x005A, inclusive) with the corresponding ASCII lowercase letter code unit (0x0061 through 0x007A, inclusive) while preserving all other code units.

+

A String _A_ is an ASCII-case-insensitive match for a String _B_ if the ASCII-lowercase of _A_ is the ASCII-lowercase of _B_.

@@ -212,6 +208,71 @@

+ +

Overview of Date Objects and Definitions of Abstract Operations

+ +

[...]

+ + +

Time Zone Identifiers

+ +

+ Time zones in ECMAScript are represented by time zone identifiers, which are Strings composed entirely of code units in the inclusive interval from 0x0000 to 0x007F0x0021 to 0x007E. + Time zones supported by an ECMAScript implementation may be available named time zones, represented by the [[Identifier]] field of the Time Zone Identifier Records returned by AvailableNamedTimeZoneIdentifiers, or offset time zones, represented by Strings for which IsTimeZoneOffsetString returns *true*. + Time zone identifiers are compared using ASCII-case-insensitive comparisons. +

+

+ A primary time zone identifier is the preferred identifier for an available named time zone. + A non-primary time zone identifier is an identifier for an available named time zone that is not a primary time zone identifier. + An available named time zone identifier is either a primary time zone identifier or a non-primary time zone identifier. + Each available named time zone identifier is associated with exactly one available named time zone. + Each available named time zone is associated with exactly one primary time zone identifier and zero or more non-primary time zone identifiers. +

+

+ ECMAScript implementations must support an available named time zone with the identifier *"UTC"*, which must be the primary time zone identifier for the UTC time zone. + In addition, implementations may support any number of other available named time zones. +

+

+ Implementations that follow the requirements for time zones as described in the ECMA-402 Internationalization API specification are called time zone aware. + Time zone aware implementations must support available named time zones corresponding to the Zone and Link names of the IANA Time Zone Database, and only such names. + In time zone aware implementations, a primary time zone identifier is a Zone name, and a non-primary time zone identifier is a Link name, respectively, in the IANA Time Zone Database except as specifically overridden by AvailableNamedTimeZoneIdentifiers as specified in the ECMA-402 specification. + Implementations that do not support the entire IANA Time Zone Database are still recommended to use IANA Time Zone Database names as identifiers to represent time zones. +

+
+ + +

AvailableNamedTimeZoneIdentifiers ( ): a List of Time Zone Identifier Records

+
+
description
+
+ Its result describes all available named time zone identifiers in this implementation, as well as the primary time zone identifier corresponding to each available named time zone identifier. + The List is ordered according to the [[Identifier]] field of each Time Zone Identifier Record. +
+
+

+ Time zone aware implementations, including all implementations that implement the ECMA-402 Internationalization API, must implement the AvailableNamedTimeZoneIdentifiers abstract operation as specified in the ECMA-402 specification. + For implementations that are not time zone aware, AvailableNamedTimeZoneIdentifiers performs the following steps when called: +

+ + 1. If the implementation does not include local political rules for any time zones, then + 1. Return « the Time Zone Identifier Record { [[Identifier]]: *"UTC"*, [[PrimaryIdentifier]]: *"UTC"* } ». + 1. Let _identifiers_ be the List of unique available named time zone identifiers. + 1. Sort _identifiers_ into the same order as if an Array of the same values had been sorted using %Array.prototype.sort% with *undefined* as the argument. + 1. Set _identifiers_ to SortStringListByCodeUnit(_identifiers_). + 1. Let _result_ be a new empty List. + 1. For each element _identifier_ of _identifiers_, do + 1. Let _primary_ be _identifier_. + 1. If _identifier_ is a non-primary time zone identifier in this implementation and _identifier_ is not *"UTC"*, then + 1. Set _primary_ to the primary time zone identifier associated with _identifier_. + 1. NOTE: An implementation may need to resolve _identifier_ iteratively to obtain the primary time zone identifier. + 1. Let _record_ be the Time Zone Identifier Record { [[Identifier]]: _identifier_, [[PrimaryIdentifier]]: _primary_ }. + 1. Append _record_ to _result_. + 1. Assert: _result_ contains a Time Zone Identifier Record _r_ such that _r_.[[Identifier]] is *"UTC"* and _r_.[[PrimaryIdentifier]] is *"UTC"*. + 1. Return _result_. + +
+
+

The Date Constructor

diff --git a/spec/temporal.html b/spec/temporal.html index 3b2bc25a68..454a9a47b3 100644 --- a/spec/temporal.html +++ b/spec/temporal.html @@ -97,7 +97,7 @@

Temporal.Now.timeZoneId ( )

This function performs the following steps when called:

- 1. Return DefaultTimeZone(). + 1. Return SystemTimeZoneIdentifier().
@@ -250,7 +250,7 @@

SystemInstant ( )

SystemDateTime ( _temporalTimeZoneLike_, _calendarLike_ )

1. If _temporalTimeZoneLike_ is *undefined*, then - 1. Let _timeZone_ be DefaultTimeZone(). + 1. Let _timeZone_ be SystemTimeZoneIdentifier(). 1. Else, 1. Let _timeZone_ be ? ToTemporalTimeZoneSlotValue(_temporalTimeZoneLike_). 1. Let _calendar_ be ? ToTemporalCalendarSlotValue(_calendarLike_). @@ -263,7 +263,7 @@

SystemDateTime ( _temporalTimeZoneLike_, _calendarLike_ )

SystemZonedDateTime ( _temporalTimeZoneLike_, _calendarLike_ )

1. If _temporalTimeZoneLike_ is *undefined*, then - 1. Let _timeZone_ be DefaultTimeZone(). + 1. Let _timeZone_ be SystemTimeZoneIdentifier(). 1. Else, 1. Let _timeZone_ be ? ToTemporalTimeZoneSlotValue(_temporalTimeZoneLike_). 1. Let _calendar_ be ? ToTemporalCalendarSlotValue(_calendarLike_). diff --git a/spec/timezone.html b/spec/timezone.html index f02fbfd02b..5ad6a4e1a3 100644 --- a/spec/timezone.html +++ b/spec/timezone.html @@ -5,99 +5,6 @@

Temporal.TimeZone Objects

A Temporal.TimeZone object is an Object referencing a time zone.

- -

Time Zone Names

- -

- An ECMAScript implementation must support a number of built-in time zones. - At a minimum, implementations must support a built-in time zone named *"UTC"*. - If the return value _tz_ of DefaultTimeZone is different from *"UTC"*, and IsTimeZoneOffsetString(_tz_) is *false*, then implementations must support that time zone as a built-in time zone as well. - In addition, implementations may support any number of other built-in time zones. -

-

- Built-in time zones may be named time zones, represented by Strings for which IsAvailableTimeZoneName returns *true*. - They may also be offset time zones, represented by Strings for which IsTimeZoneOffsetString returns *true*. -

-

- The `Temporal.TimeZone` constructor, when called with the name of a built-in time zone as the argument, will return a valid `Temporal.TimeZone` object. - When called with any other string, it will throw a *RangeError* exception. -

-

- An ECMAScript implementation that includes the ECMA-402 Internationalization API must define built-in named time zones in correspondence with the Zone and Link names of the IANA Time Zone Database (and only such names) and use best available current and historical information about their offsets from UTC and their daylight saving time rules in calculations as specified in the ECMA-402 specification. - Other implementations are encouraged to do the same. -

- - -

- IsAvailableTimeZoneName ( - _timeZone_: a String, - ): a Boolean -

-
-
description
-
The returned value is *true* if _timeZone_ is an ASCII-case-insensitive match for a supported time zone name, and *false* otherwise.
-
-

- Once IsAvailableTimeZoneName(_timeZone_) has returned *true*, for the lifetime of the surrounding agent, IsAvailableTimeZoneName(_variant_) must return *true* if _variant_ is an ASCII-case-insensitive match for either _timeZone_ or CanonicalizeTimeZoneName(_timeZone_). -

- - - 1. Let _timeZones_ be AvailableTimeZones(). - 1. For each String _candidate_ in _timeZones_, do - 1. If _timeZone_ is an ASCII-case-insensitive match for _candidate_, return *true*. - 1. Return *false*. - -
- - -

- CanonicalizeTimeZoneName ( - _timeZone_: a String that is a valid time zone name as verified by IsAvailableTimeZoneName, - ) -

-
-
description
-
It returns the canonical and case-regularized form of _timeZone_.
-
- -

- An ECMAScript implementation that includes the ECMA-402 Internationalization API must implement the CanonicalizeTimeZoneName abstract operation as specified in the ECMA-402 specification. -

-

- The minimum implementation of CanonicalizeTimeZoneName for ECMAScript implementations that do not include local political rules for any time zones performs the following steps when called: -

- - - 1. Assert: _timeZone_ is an ASCII-case-insensitive match for *"UTC"*. - 1. Return *"UTC"*. - -
- - -

- AvailableTimeZones ( - ): a List of Strings -

-
-
description
-
The returned List is a sorted List of identifiers for time zones for which the implementation includes local political rules, and aliases for those identifiers.
-
- -

- An ECMAScript implementation that includes the ECMA-402 Internationalization API must implement the AvailableTimeZones abstract operation as specified in the ECMA-402 specification. - If an ECMAScript implementation does not include the ECMA-402 API the following specification of the AvailableTimeZones abstract operation is used. -

- - 1. Let _timeZones_ be the List of String values representing time zones supported by the implementation. - 1. Assert: _timeZones_ contains *"UTC"*. - 1. Return SortStringListByCodeUnit(_timeZones_). - -

- For example, an ECMAScript implementation that does not include local political rules for any time zone could return a List with the single String *"UTC"* here. -

-
-
-

The Temporal.TimeZone Constructor

The Temporal.TimeZone constructor:

@@ -126,9 +33,9 @@

Temporal.TimeZone ( _identifier_ )

1. Throw a *TypeError* exception. 1. Set _identifier_ to ? ToString(_identifier_). 1. If IsTimeZoneOffsetString(_identifier_) is *false*, then - 1. If IsAvailableTimeZoneName(_identifier_) is *false*, then - 1. Throw a *RangeError* exception. - 1. Set _identifier_ to ! CanonicalizeTimeZoneName(_identifier_). + 1. Let _timeZoneIdentifierRecord_ be GetAvailableNamedTimeZoneIdentifier(_identifier_). + 1. If _timeZoneIdentifierRecord_ is ~empty~, throw a *RangeError* exception. + 1. Set _identifier_ to _timeZoneIdentifierRecord_.[[PrimaryIdentifier]]. 1. Return ? CreateTemporalTimeZone(_identifier_, NewTarget).
@@ -385,6 +292,19 @@

Properties of Temporal.TimeZone Instances

Abstract operations

+ + In ECMA-262, many time-zone-related sections and abstract operations are contained in the Date Objects section of the specification. + Now that ECMAScript has a built-in %Temporal.TimeZone% object, it may be appropriate to move those sections here, for example: +
    +
  • Time Zone Identifiers
  • +
  • AvailableNamedTimeZoneIdentifiers
  • +
  • SystemTimeZoneIdentifier
  • +
  • IsTimeZoneOffsetString
  • +
  • GetNamedTimeZoneEpochNanoseconds
  • +
  • GetNamedTimeZoneOffsetNanoseconds
  • +
+
+

CreateTemporalTimeZone ( @@ -403,7 +323,7 @@

1. Set _object_.[[Identifier]] to ~empty~. 1. Set _object_.[[OffsetNanoseconds]] to ParseTimeZoneOffsetString(_identifier_). 1. Else, - 1. Assert: ! CanonicalizeTimeZoneName(_identifier_) is _identifier_. + 1. Assert: GetAvailableNamedTimeZoneIdentifier(_identifier_).[[PrimaryIdentifier]] is _identifier_. 1. Set _object_.[[Identifier]] to _identifier_. 1. Set _object_.[[OffsetNanoseconds]] to ~empty~. 1. Return _object_. @@ -421,7 +341,32 @@

Implementations are free to store either or both representations.

- + + + +

+ GetAvailableNamedTimeZoneIdentifier ( + _timeZoneIdentifier_: a String, + ): either a Time Zone Identifier Record or ~empty~ +

+
+
description
+
+ If _timeZoneIdentifier_ is an available named time zone identifier, then it returns one of the records in the List returned by AvailableNamedTimeZoneIdentifiers. + Otherwise, ~empty~ will be returned. +
+
+ + 1. For each element _record_ of AvailableNamedTimeZoneIdentifiers(), do + 1. If _record_.[[Identifier]] is an ASCII-case-insensitive match for _timeZoneIdentifier_, return _record_. + 1. Return ~empty~. + + + For any _timeZoneIdentifier_, or any value that is an ASCII-case-insensitive match for it, the resulting Time Zone Identifier Record must contain the same field values for the lifetime of the surrounding agent. + Furthermore, time zone identifiers must not dynamically change from primary to non-primary during the lifetime of the surrounding agent, meaning that if _timeZoneIdentifier_ is an ASCII-case-insensitive match for the [[PrimaryIdentifier]] field of the result of a previous call to GetAvailableNamedTimeZoneIdentifier, then GetAvailableNamedTimeZoneIdentifier(_timeZoneIdentifier_) must return a record where [[Identifier]] is [[PrimaryIdentifier]]. + Due to the complexity of supporting these requirements, it is recommended that the result of AvailableNamedTimeZoneIdentifiers (and therefore GetAvailableNamedTimeZoneIdentifier) remains the same for the lifetime of the surrounding agent. + +

@@ -622,8 +567,9 @@

1. If _parseResult_.[[Name]] is not *undefined*, then 1. Let _name_ be _parseResult_.[[Name]]. 1. If IsTimeZoneOffsetString(_name_) is *true*, return CanonicalizeTimeZoneOffsetString(_name_). - 1. If IsAvailableTimeZoneName(_name_) is *false*, throw a *RangeError* exception. - 1. Return ! CanonicalizeTimeZoneName(_name_). + 1. Let _timeZoneIdentifierRecord_ be GetAvailableNamedTimeZoneIdentifier(_name_). + 1. If _timeZoneIdentifierRecord_ is ~empty~, throw a *RangeError* exception. + 1. Return _timeZoneIdentifierRecord_.[[PrimaryIdentifier]]. 1. If _parseResult_.[[Z]] is *true*, return *"UTC"*. 1. Return CanonicalizeTimeZoneOffsetString(_parseResult_.[[OffsetString]]). @@ -641,7 +587,7 @@

1. If _timeZoneSlotValue_ is a String, then - 1. Assert: Either IsTimeZoneOffsetString(_timeZoneSlotValue_) or IsAvailableTimeZoneName(_timeZoneSlotValue_) is *true*. + 1. Assert: Either IsTimeZoneOffsetString(_timeZoneSlotValue_) is *true*, or GetAvailableNamedTimeZoneIdentifier(_timeZoneSlotValue_) is not ~empty~. 1. Return _timeZoneSlotValue_. 1. Let _identifier_ be ? Get(_timeZoneSlotValue_, *"id"*). 1. If _identifier_ is not a String, throw a *TypeError* exception. From 192acba66059c776c9a286482249b5a9ec9bb2f2 Mon Sep 17 00:00:00 2001 From: Justin Grant Date: Mon, 12 Jun 2023 21:28:30 -0700 Subject: [PATCH 2/2] Polyfill: Refactor time zone identifier handling --- polyfill/lib/ecmascript.mjs | 95 +- polyfill/lib/intl.mjs | 38 +- polyfill/lib/timezone.mjs | 5 +- polyfill/lib/zoneddatetime.mjs | 12 +- polyfill/test/cldr-timezone.json | 1927 ++++++++++++++++++++++++++++++ polyfill/test/ecmascript.mjs | 56 + 6 files changed, 2108 insertions(+), 25 deletions(-) create mode 100644 polyfill/test/cldr-timezone.json diff --git a/polyfill/lib/ecmascript.mjs b/polyfill/lib/ecmascript.mjs index 4777893822..25d54d96f2 100644 --- a/polyfill/lib/ecmascript.mjs +++ b/polyfill/lib/ecmascript.mjs @@ -4,6 +4,7 @@ const ArrayIncludes = Array.prototype.includes; const ArrayPrototypePush = Array.prototype.push; const ArrayPrototypeSort = Array.prototype.sort; const IntlDateTimeFormat = globalThis.Intl.DateTimeFormat; +const IntlSupportedValuesOf = globalThis.Intl.supportedValuesOf; const MathAbs = Math.abs; const MathFloor = Math.floor; const MathMax = Math.max; @@ -368,7 +369,9 @@ export function ParseTemporalTimeZone(stringIdent) { const { tzName, offset, z } = ParseTemporalTimeZoneString(stringIdent); if (tzName) { if (IsTimeZoneOffsetString(tzName)) return CanonicalizeTimeZoneOffsetString(tzName); - return GetCanonicalTimeZoneIdentifier(tzName); + const record = GetAvailableNamedTimeZoneIdentifier(tzName); + if (!record) throw new RangeError(`Unrecognized time zone ${tzName}`); + return record.primaryIdentifier; } if (z) return 'UTC'; // if !tzName && !z then offset must be present @@ -2589,15 +2592,87 @@ export function ParseTimeZoneOffsetString(string) { return sign * (((hours * 60 + minutes) * 60 + seconds) * 1e9 + nanoseconds); } -// In the spec, GetCanonicalTimeZoneIdentifier is infallible and is always -// preceded by a call to IsAvailableTimeZoneName. However in the polyfill, -// we don't (yet) have a way to check if a time zone ID is valid without -// also canonicalizing it. So we combine both operations into one function, -// which will return the canonical ID if the ID is valid, and will throw -// if it's not. -export function GetCanonicalTimeZoneIdentifier(timeZoneIdentifier) { - const formatter = getIntlDateTimeFormatEnUsForTimeZone(timeZoneIdentifier); - return formatter.resolvedOptions().timeZone; +let canonicalTimeZoneIdsCache = undefined; + +export function GetAvailableNamedTimeZoneIdentifier(identifier) { + // The most common case is when the identifier is a canonical time zone ID. + // Fast-path that case by caching all canonical IDs. For old ECMAScript + // implementations lacking this API, set the cache to `null` to avoid retries. + if (canonicalTimeZoneIdsCache === undefined) { + const canonicalTimeZoneIds = IntlSupportedValuesOf?.('timeZone'); + canonicalTimeZoneIdsCache = canonicalTimeZoneIds + ? new Map(canonicalTimeZoneIds.map((id) => [ASCIILowercase(id), id])) + : null; + } + + const lower = ASCIILowercase(identifier); + let primaryIdentifier = canonicalTimeZoneIdsCache?.get(lower); + if (primaryIdentifier) return { identifier: primaryIdentifier, primaryIdentifier }; + + // It's not already a primary identifier, so get its primary identifier (or + // return if it's not an available named time zone ID). + try { + const formatter = getIntlDateTimeFormatEnUsForTimeZone(identifier); + primaryIdentifier = formatter.resolvedOptions().timeZone; + } catch { + return undefined; + } + + // The identifier is an alias (a deprecated identifier that's a synonym for a + // primary identifier), so we need to case-normalize the identifier to match + // the IANA TZDB, e.g. america/new_york => America/New_York. There's no + // built-in way to do this using Intl.DateTimeFormat, but the we can normalize + // almost all aliases (modulo a few special cases) using the TZDB's basic + // capitalization pattern: + // 1. capitalize the first letter of the identifier + // 2. capitalize the letter after every slash, dash, or underscore delimiter + const standardCase = [...lower] + .map((c, i) => (i === 0 || '/-_'.includes(lower[i - 1]) ? c.toUpperCase() : c)) + .join(''); + const segments = standardCase.split('/'); + + if (segments.length === 1) { + // If a single-segment legacy ID is 2-3 chars or contains a number or dash, then + // (except for the "GB-Eire" special case) the case-normalized form is uppercase. + // These are: GMT+0, GMT-0, GB, NZ, PRC, ROC, ROK, UCT, GMT, GMT0, CET, CST6CDT, + // EET, EST, HST, MET, MST, MST7MDT, PST8PDT, WET, NZ-CHAT, and W-SU. + // Otherwise it's standard form: first letter capitalized, e.g. Iran, Egypt, Hongkong + if (lower === 'gb-eire') return { identifier: 'GB-Eire', primaryIdentifier }; + return { + identifier: lower.length <= 3 || /[-0-9]/.test(lower) ? lower.toUpperCase() : segments[0], + primaryIdentifier + }; + } + + // All Etc zone names are uppercase except three exceptions. + if (segments[0] === 'Etc') { + const etcName = ['Zulu', 'Greenwich', 'Universal'].includes(segments[1]) ? segments[1] : segments[1].toUpperCase(); + return { identifier: `Etc/${etcName}`, primaryIdentifier }; + } + + // Legacy US identifiers like US/Alaska or US/Indiana-Starke are 2 segments and use standard form. + if (segments[0] === 'Us') return { identifier: `US/${segments[1]}`, primaryIdentifier }; + + // For multi-segment IDs, there's a few special cases in the second/third segments + const specialCases = { + Act: 'ACT', + Lhi: 'LHI', + Nsw: 'NSW', + Dar_Es_Salaam: 'Dar_es_Salaam', + Port_Of_Spain: 'Port_of_Spain', + Isle_Of_Man: 'Isle_of_Man', + Comodrivadavia: 'ComodRivadavia', + Knox_In: 'Knox_IN', + Dumontdurville: 'DumontDUrville', + Mcmurdo: 'McMurdo', + Denoronha: 'DeNoronha', + Easterisland: 'EasterIsland', + Bajanorte: 'BajaNorte', + Bajasur: 'BajaSur' + }; + segments[1] = specialCases[segments[1]] ?? segments[1]; + if (segments.length > 2) segments[2] = specialCases[segments[2]] ?? segments[2]; + return { identifier: segments.join('/'), primaryIdentifier }; } export function GetNamedTimeZoneOffsetNanoseconds(id, epochNanoseconds) { diff --git a/polyfill/lib/intl.mjs b/polyfill/lib/intl.mjs index 3312d581ee..e3c6a40369 100644 --- a/polyfill/lib/intl.mjs +++ b/polyfill/lib/intl.mjs @@ -21,7 +21,8 @@ const TIME = Symbol('time'); const DATETIME = Symbol('datetime'); const INST = Symbol('instant'); const ORIGINAL = Symbol('original'); -const TZ_RESOLVED = Symbol('timezone'); +const TZ_CANONICAL = Symbol('timezone-canonical'); +const TZ_ORIGINAL = Symbol('timezone-original'); const CAL_ID = Symbol('calendar-id'); const LOCALE = Symbol('locale'); const OPTIONS = Symbol('options'); @@ -81,7 +82,7 @@ export function DateTimeFormat(locale = undefined, options = undefined) { this[LOCALE] = ro.locale; this[ORIGINAL] = original; - this[TZ_RESOLVED] = ro.timeZone; + this[TZ_CANONICAL] = ro.timeZone; this[CAL_ID] = ro.calendar; this[DATE] = dateAmend; this[YM] = yearMonthAmend; @@ -89,6 +90,25 @@ export function DateTimeFormat(locale = undefined, options = undefined) { this[TIME] = timeAmend; this[DATETIME] = datetimeAmend; this[INST] = instantAmend; + + // Save the original time zone, for a few reasons: + // - Clearer error messages + // - More clearly follows the spec for InitializeDateTimeFormat + // - Because it follows the spec more closely, will make it easier to integrate + // support of offset strings and other potential changes like proposal-canonical-tz. + const timeZoneOption = hasOptions ? options.timeZone : undefined; + if (timeZoneOption === undefined) { + this[TZ_ORIGINAL] = ro.timeZone; + } else { + const id = ES.ToString(timeZoneOption); + if (ES.IsTimeZoneOffsetString(id)) { + // Note: https://github.com/tc39/ecma402/issues/683 will remove this + throw new RangeError('Intl.DateTimeFormat does not currently support offset time zones'); + } + const record = ES.GetAvailableNamedTimeZoneIdentifier(id); + if (!record) throw new RangeError(`Intl.DateTimeFormat formats built-in time zones, not ${id}`); + this[TZ_ORIGINAL] = record.identifier; + } } DateTimeFormat.supportedLocalesOf = function (...args) { @@ -118,7 +138,9 @@ Object.defineProperty(DateTimeFormat, 'prototype', { }); function resolvedOptions() { - return this[ORIGINAL].resolvedOptions(); + const resolved = this[ORIGINAL].resolvedOptions(); + resolved.timeZone = this[TZ_CANONICAL]; + return resolved; } function format(datetime, ...rest) { @@ -335,7 +357,7 @@ function extractOverrides(temporalObj, main) { const nanosecond = GetSlot(temporalObj, ISO_NANOSECOND); const datetime = new DateTime(1970, 1, 1, hour, minute, second, millisecond, microsecond, nanosecond, main[CAL_ID]); return { - instant: ES.GetInstantFor(main[TZ_RESOLVED], datetime, 'compatible'), + instant: ES.GetInstantFor(main[TZ_CANONICAL], datetime, 'compatible'), formatter: getPropLazy(main, TIME) }; } @@ -352,7 +374,7 @@ function extractOverrides(temporalObj, main) { } const datetime = new DateTime(isoYear, isoMonth, referenceISODay, 12, 0, 0, 0, 0, 0, calendar); return { - instant: ES.GetInstantFor(main[TZ_RESOLVED], datetime, 'compatible'), + instant: ES.GetInstantFor(main[TZ_CANONICAL], datetime, 'compatible'), formatter: getPropLazy(main, YM) }; } @@ -369,7 +391,7 @@ function extractOverrides(temporalObj, main) { } const datetime = new DateTime(referenceISOYear, isoMonth, isoDay, 12, 0, 0, 0, 0, 0, calendar); return { - instant: ES.GetInstantFor(main[TZ_RESOLVED], datetime, 'compatible'), + instant: ES.GetInstantFor(main[TZ_CANONICAL], datetime, 'compatible'), formatter: getPropLazy(main, MD) }; } @@ -384,7 +406,7 @@ function extractOverrides(temporalObj, main) { } const datetime = new DateTime(isoYear, isoMonth, isoDay, 12, 0, 0, 0, 0, 0, main[CAL_ID]); return { - instant: ES.GetInstantFor(main[TZ_RESOLVED], datetime, 'compatible'), + instant: ES.GetInstantFor(main[TZ_CANONICAL], datetime, 'compatible'), formatter: getPropLazy(main, DATE) }; } @@ -421,7 +443,7 @@ function extractOverrides(temporalObj, main) { ); } return { - instant: ES.GetInstantFor(main[TZ_RESOLVED], datetime, 'compatible'), + instant: ES.GetInstantFor(main[TZ_CANONICAL], datetime, 'compatible'), formatter: getPropLazy(main, DATETIME) }; } diff --git a/polyfill/lib/timezone.mjs b/polyfill/lib/timezone.mjs index 97071c83fd..9796e980f6 100644 --- a/polyfill/lib/timezone.mjs +++ b/polyfill/lib/timezone.mjs @@ -26,12 +26,13 @@ export class TimeZone { if (arguments.length < 1) { throw new RangeError('missing argument: identifier is required'); } - let stringIdentifier = ES.ToString(identifier); if (ES.IsTimeZoneOffsetString(stringIdentifier)) { stringIdentifier = ES.CanonicalizeTimeZoneOffsetString(stringIdentifier); } else { - stringIdentifier = ES.GetCanonicalTimeZoneIdentifier(stringIdentifier); + const record = ES.GetAvailableNamedTimeZoneIdentifier(stringIdentifier); + if (!record) throw new RangeError(`Invalid time zone identifier: ${stringIdentifier}`); + stringIdentifier = record.primaryIdentifier; } CreateSlots(this); SetSlot(this, TIMEZONE_ID, stringIdentifier); diff --git a/polyfill/lib/zoneddatetime.mjs b/polyfill/lib/zoneddatetime.mjs index b386343aef..1a593c7f15 100644 --- a/polyfill/lib/zoneddatetime.mjs +++ b/polyfill/lib/zoneddatetime.mjs @@ -471,13 +471,15 @@ export class ZonedDateTime { // The rest of the defaults will be filled in by formatting the Instant } - let timeZone = ES.ToTemporalTimeZoneIdentifier(GetSlot(this, TIME_ZONE)); - if (ES.IsTimeZoneOffsetString(timeZone)) { + const timeZoneIdentifier = ES.ToTemporalTimeZoneIdentifier(GetSlot(this, TIME_ZONE)); + if (ES.IsTimeZoneOffsetString(timeZoneIdentifier)) { // Note: https://github.com/tc39/ecma402/issues/683 will remove this - throw new RangeError('toLocaleString does not support offset string time zones'); + throw new RangeError('toLocaleString does not currently support offset time zones'); + } else { + const record = ES.GetAvailableNamedTimeZoneIdentifier(timeZoneIdentifier); + if (!record) throw new RangeError(`toLocaleString formats built-in time zones, not ${timeZoneIdentifier}`); + optionsCopy.timeZone = record.primaryIdentifier; } - timeZone = ES.GetCanonicalTimeZoneIdentifier(timeZone); - optionsCopy.timeZone = timeZone; const formatter = new DateTimeFormat(locales, optionsCopy); diff --git a/polyfill/test/cldr-timezone.json b/polyfill/test/cldr-timezone.json new file mode 100644 index 0000000000..80c80a785b --- /dev/null +++ b/polyfill/test/cldr-timezone.json @@ -0,0 +1,1927 @@ +{ + "version": { + "_number": "$Revision$" + }, + "keyword": { + "u": { + "tz": { + "_description": "Time zone key", + "_alias": "timezone", + "adalv": { + "_description": "Andorra", + "_alias": "Europe/Andorra" + }, + "aedxb": { + "_description": "Dubai, United Arab Emirates", + "_alias": "Asia/Dubai" + }, + "afkbl": { + "_description": "Kabul, Afghanistan", + "_alias": "Asia/Kabul" + }, + "aganu": { + "_description": "Antigua", + "_alias": "America/Antigua" + }, + "aiaxa": { + "_description": "Anguilla", + "_alias": "America/Anguilla" + }, + "altia": { + "_description": "Tirane, Albania", + "_alias": "Europe/Tirane" + }, + "amevn": { + "_description": "Yerevan, Armenia", + "_alias": "Asia/Yerevan" + }, + "ancur": { + "_description": "Curaçao", + "_alias": "America/Curacao" + }, + "aolad": { + "_description": "Luanda, Angola", + "_alias": "Africa/Luanda" + }, + "aqams": { + "_deprecated": true, + "_description": "Amundsen-Scott Station, South Pole", + "_preferred": "nzakl" + }, + "aqcas": { + "_description": "Casey Station, Bailey Peninsula", + "_alias": "Antarctica/Casey" + }, + "aqdav": { + "_description": "Davis Station, Vestfold Hills", + "_alias": "Antarctica/Davis" + }, + "aqddu": { + "_description": "Dumont d'Urville Station, Terre Adélie", + "_alias": "Antarctica/DumontDUrville" + }, + "aqmaw": { + "_description": "Mawson Station, Holme Bay", + "_alias": "Antarctica/Mawson" + }, + "aqmcm": { + "_description": "McMurdo Station, Ross Island", + "_alias": "Antarctica/McMurdo" + }, + "aqplm": { + "_description": "Palmer Station, Anvers Island", + "_alias": "Antarctica/Palmer" + }, + "aqrot": { + "_description": "Rothera Station, Adelaide Island", + "_alias": "Antarctica/Rothera" + }, + "aqsyw": { + "_description": "Syowa Station, East Ongul Island", + "_alias": "Antarctica/Syowa" + }, + "aqtrl": { + "_description": "Troll Station, Queen Maud Land", + "_alias": "Antarctica/Troll", + "_since": "26" + }, + "aqvos": { + "_description": "Vostok Station, Lake Vostok", + "_alias": "Antarctica/Vostok" + }, + "arbue": { + "_description": "Buenos Aires, Argentina", + "_alias": "America/Buenos_Aires America/Argentina/Buenos_Aires" + }, + "arcor": { + "_description": "Córdoba, Argentina", + "_alias": "America/Cordoba America/Argentina/Cordoba America/Rosario" + }, + "arctc": { + "_description": "Catamarca, Argentina", + "_alias": "America/Catamarca America/Argentina/Catamarca America/Argentina/ComodRivadavia" + }, + "arirj": { + "_description": "La Rioja, Argentina", + "_alias": "America/Argentina/La_Rioja" + }, + "arjuj": { + "_description": "Jujuy, Argentina", + "_alias": "America/Jujuy America/Argentina/Jujuy" + }, + "arluq": { + "_description": "San Luis, Argentina", + "_alias": "America/Argentina/San_Luis" + }, + "armdz": { + "_description": "Mendoza, Argentina", + "_alias": "America/Mendoza America/Argentina/Mendoza" + }, + "arrgl": { + "_description": "Río Gallegos, Argentina", + "_alias": "America/Argentina/Rio_Gallegos" + }, + "arsla": { + "_description": "Salta, Argentina", + "_alias": "America/Argentina/Salta" + }, + "artuc": { + "_description": "Tucumán, Argentina", + "_alias": "America/Argentina/Tucuman" + }, + "aruaq": { + "_description": "San Juan, Argentina", + "_alias": "America/Argentina/San_Juan" + }, + "arush": { + "_description": "Ushuaia, Argentina", + "_alias": "America/Argentina/Ushuaia" + }, + "asppg": { + "_description": "Pago Pago, American Samoa", + "_alias": "Pacific/Pago_Pago Pacific/Samoa US/Samoa" + }, + "atvie": { + "_description": "Vienna, Austria", + "_alias": "Europe/Vienna" + }, + "auadl": { + "_description": "Adelaide, Australia", + "_alias": "Australia/Adelaide Australia/South" + }, + "aubhq": { + "_description": "Broken Hill, Australia", + "_alias": "Australia/Broken_Hill Australia/Yancowinna" + }, + "aubne": { + "_description": "Brisbane, Australia", + "_alias": "Australia/Brisbane Australia/Queensland" + }, + "audrw": { + "_description": "Darwin, Australia", + "_alias": "Australia/Darwin Australia/North" + }, + "aueuc": { + "_description": "Eucla, Australia", + "_alias": "Australia/Eucla" + }, + "auhba": { + "_description": "Hobart, Australia", + "_alias": "Australia/Hobart Australia/Tasmania" + }, + "aukns": { + "_description": "Currie, Australia", + "_alias": "Australia/Currie" + }, + "auldc": { + "_description": "Lindeman Island, Australia", + "_alias": "Australia/Lindeman" + }, + "auldh": { + "_description": "Lord Howe Island, Australia", + "_alias": "Australia/Lord_Howe Australia/LHI" + }, + "aumel": { + "_description": "Melbourne, Australia", + "_alias": "Australia/Melbourne Australia/Victoria" + }, + "aumqi": { + "_description": "Macquarie Island Station, Macquarie Island", + "_alias": "Antarctica/Macquarie", + "_since": "1.8.1" + }, + "auper": { + "_description": "Perth, Australia", + "_alias": "Australia/Perth Australia/West" + }, + "ausyd": { + "_description": "Sydney, Australia", + "_alias": "Australia/Sydney Australia/ACT Australia/Canberra Australia/NSW" + }, + "awaua": { + "_description": "Aruba", + "_alias": "America/Aruba" + }, + "azbak": { + "_description": "Baku, Azerbaijan", + "_alias": "Asia/Baku" + }, + "basjj": { + "_description": "Sarajevo, Bosnia and Herzegovina", + "_alias": "Europe/Sarajevo" + }, + "bbbgi": { + "_description": "Barbados", + "_alias": "America/Barbados" + }, + "bddac": { + "_description": "Dhaka, Bangladesh", + "_alias": "Asia/Dhaka Asia/Dacca" + }, + "bebru": { + "_description": "Brussels, Belgium", + "_alias": "Europe/Brussels" + }, + "bfoua": { + "_description": "Ouagadougou, Burkina Faso", + "_alias": "Africa/Ouagadougou" + }, + "bgsof": { + "_description": "Sofia, Bulgaria", + "_alias": "Europe/Sofia" + }, + "bhbah": { + "_description": "Bahrain", + "_alias": "Asia/Bahrain" + }, + "bibjm": { + "_description": "Bujumbura, Burundi", + "_alias": "Africa/Bujumbura" + }, + "bjptn": { + "_description": "Porto-Novo, Benin", + "_alias": "Africa/Porto-Novo" + }, + "bmbda": { + "_description": "Bermuda", + "_alias": "Atlantic/Bermuda" + }, + "bnbwn": { + "_description": "Brunei", + "_alias": "Asia/Brunei" + }, + "bolpb": { + "_description": "La Paz, Bolivia", + "_alias": "America/La_Paz" + }, + "bqkra": { + "_description": "Bonaire, Sint Estatius and Saba", + "_alias": "America/Kralendijk", + "_since": "21" + }, + "braux": { + "_description": "Araguaína, Brazil", + "_alias": "America/Araguaina" + }, + "brbel": { + "_description": "Belém, Brazil", + "_alias": "America/Belem" + }, + "brbvb": { + "_description": "Boa Vista, Brazil", + "_alias": "America/Boa_Vista" + }, + "brcgb": { + "_description": "Cuiabá, Brazil", + "_alias": "America/Cuiaba" + }, + "brcgr": { + "_description": "Campo Grande, Brazil", + "_alias": "America/Campo_Grande" + }, + "brern": { + "_description": "Eirunepé, Brazil", + "_alias": "America/Eirunepe" + }, + "brfen": { + "_description": "Fernando de Noronha, Brazil", + "_alias": "America/Noronha Brazil/DeNoronha" + }, + "brfor": { + "_description": "Fortaleza, Brazil", + "_alias": "America/Fortaleza" + }, + "brmao": { + "_description": "Manaus, Brazil", + "_alias": "America/Manaus Brazil/West" + }, + "brmcz": { + "_description": "Maceió, Brazil", + "_alias": "America/Maceio" + }, + "brpvh": { + "_description": "Porto Velho, Brazil", + "_alias": "America/Porto_Velho" + }, + "brrbr": { + "_description": "Rio Branco, Brazil", + "_alias": "America/Rio_Branco America/Porto_Acre Brazil/Acre" + }, + "brrec": { + "_description": "Recife, Brazil", + "_alias": "America/Recife" + }, + "brsao": { + "_description": "São Paulo, Brazil", + "_alias": "America/Sao_Paulo Brazil/East" + }, + "brssa": { + "_description": "Bahia, Brazil", + "_alias": "America/Bahia" + }, + "brstm": { + "_description": "Santarém, Brazil", + "_alias": "America/Santarem" + }, + "bsnas": { + "_description": "Nassau, Bahamas", + "_alias": "America/Nassau" + }, + "btthi": { + "_description": "Thimphu, Bhutan", + "_alias": "Asia/Thimphu Asia/Thimbu" + }, + "bwgbe": { + "_description": "Gaborone, Botswana", + "_alias": "Africa/Gaborone" + }, + "bymsq": { + "_description": "Minsk, Belarus", + "_alias": "Europe/Minsk" + }, + "bzbze": { + "_description": "Belize", + "_alias": "America/Belize" + }, + "cacfq": { + "_description": "Creston, Canada", + "_alias": "America/Creston", + "_since": "21.0.1" + }, + "caedm": { + "_description": "Edmonton, Canada", + "_alias": "America/Edmonton Canada/Mountain" + }, + "caffs": { + "_description": "Rainy River, Canada", + "_alias": "America/Rainy_River" + }, + "cafne": { + "_description": "Fort Nelson, Canada", + "_alias": "America/Fort_Nelson", + "_since": "29" + }, + "caglb": { + "_description": "Glace Bay, Canada", + "_alias": "America/Glace_Bay" + }, + "cagoo": { + "_description": "Goose Bay, Canada", + "_alias": "America/Goose_Bay" + }, + "cahal": { + "_description": "Halifax, Canada", + "_alias": "America/Halifax Canada/Atlantic" + }, + "caiql": { + "_description": "Iqaluit, Canada", + "_alias": "America/Iqaluit" + }, + "camon": { + "_description": "Moncton, Canada", + "_alias": "America/Moncton" + }, + "camtr": { + "_deprecated": true, + "_description": "Montreal, Canada", + "_preferred": "cator" + }, + "canpg": { + "_description": "Nipigon, Canada", + "_alias": "America/Nipigon" + }, + "capnt": { + "_description": "Pangnirtung, Canada", + "_alias": "America/Pangnirtung" + }, + "careb": { + "_description": "Resolute, Canada", + "_alias": "America/Resolute" + }, + "careg": { + "_description": "Regina, Canada", + "_alias": "America/Regina Canada/East-Saskatchewan Canada/Saskatchewan" + }, + "casjf": { + "_description": "St. John's, Canada", + "_alias": "America/St_Johns Canada/Newfoundland" + }, + "cathu": { + "_description": "Thunder Bay, Canada", + "_alias": "America/Thunder_Bay" + }, + "cator": { + "_description": "Toronto, Canada", + "_alias": "America/Toronto America/Montreal Canada/Eastern" + }, + "cavan": { + "_description": "Vancouver, Canada", + "_alias": "America/Vancouver Canada/Pacific" + }, + "cawnp": { + "_description": "Winnipeg, Canada", + "_alias": "America/Winnipeg Canada/Central" + }, + "caybx": { + "_description": "Blanc-Sablon, Canada", + "_alias": "America/Blanc-Sablon" + }, + "caycb": { + "_description": "Cambridge Bay, Canada", + "_alias": "America/Cambridge_Bay" + }, + "cayda": { + "_description": "Dawson, Canada", + "_alias": "America/Dawson" + }, + "caydq": { + "_description": "Dawson Creek, Canada", + "_alias": "America/Dawson_Creek" + }, + "cayek": { + "_description": "Rankin Inlet, Canada", + "_alias": "America/Rankin_Inlet" + }, + "cayev": { + "_description": "Inuvik, Canada", + "_alias": "America/Inuvik" + }, + "cayxy": { + "_description": "Whitehorse, Canada", + "_alias": "America/Whitehorse Canada/Yukon" + }, + "cayyn": { + "_description": "Swift Current, Canada", + "_alias": "America/Swift_Current" + }, + "cayzf": { + "_description": "Yellowknife, Canada", + "_alias": "America/Yellowknife" + }, + "cayzs": { + "_description": "Atikokan, Canada", + "_alias": "America/Coral_Harbour America/Atikokan" + }, + "cccck": { + "_description": "Cocos (Keeling) Islands", + "_alias": "Indian/Cocos" + }, + "cdfbm": { + "_description": "Lubumbashi, Democratic Republic of the Congo", + "_alias": "Africa/Lubumbashi" + }, + "cdfih": { + "_description": "Kinshasa, Democratic Republic of the Congo", + "_alias": "Africa/Kinshasa" + }, + "cfbgf": { + "_description": "Bangui, Central African Republic", + "_alias": "Africa/Bangui" + }, + "cgbzv": { + "_description": "Brazzaville, Republic of the Congo", + "_alias": "Africa/Brazzaville" + }, + "chzrh": { + "_description": "Zurich, Switzerland", + "_alias": "Europe/Zurich" + }, + "ciabj": { + "_description": "Abidjan, Côte d'Ivoire", + "_alias": "Africa/Abidjan" + }, + "ckrar": { + "_description": "Rarotonga, Cook Islands", + "_alias": "Pacific/Rarotonga" + }, + "clipc": { + "_description": "Easter Island, Chile", + "_alias": "Pacific/Easter Chile/EasterIsland" + }, + "clpuq": { + "_description": "Punta Arenas, Chile", + "_alias": "America/Punta_Arenas", + "_since": "31" + }, + "clscl": { + "_description": "Santiago, Chile", + "_alias": "America/Santiago Chile/Continental" + }, + "cmdla": { + "_description": "Douala, Cameroon", + "_alias": "Africa/Douala" + }, + "cnckg": { + "_deprecated": true, + "_description": "Chongqing, China", + "_preferred": "cnsha" + }, + "cnhrb": { + "_deprecated": true, + "_description": "Harbin, China", + "_preferred": "cnsha" + }, + "cnkhg": { + "_deprecated": true, + "_description": "Kashgar, China", + "_preferred": "cnurc" + }, + "cnsha": { + "_description": "Shanghai, China", + "_alias": "Asia/Shanghai Asia/Chongqing Asia/Chungking Asia/Harbin PRC" + }, + "cnurc": { + "_description": "Ürümqi, China", + "_alias": "Asia/Urumqi Asia/Kashgar" + }, + "cobog": { + "_description": "Bogotá, Colombia", + "_alias": "America/Bogota" + }, + "crsjo": { + "_description": "Costa Rica", + "_alias": "America/Costa_Rica" + }, + "cst6cdt": { + "_description": "POSIX style time zone for US Central Time", + "_alias": "CST6CDT", + "_since": "1.8" + }, + "cuhav": { + "_description": "Havana, Cuba", + "_alias": "America/Havana Cuba" + }, + "cvrai": { + "_description": "Cape Verde", + "_alias": "Atlantic/Cape_Verde" + }, + "cxxch": { + "_description": "Christmas Island", + "_alias": "Indian/Christmas" + }, + "cyfmg": { + "_description": "Famagusta, Cyprus", + "_alias": "Asia/Famagusta", + "_since": "31" + }, + "cynic": { + "_description": "Nicosia, Cyprus", + "_alias": "Asia/Nicosia Europe/Nicosia" + }, + "czprg": { + "_description": "Prague, Czech Republic", + "_alias": "Europe/Prague" + }, + "deber": { + "_description": "Berlin, Germany", + "_alias": "Europe/Berlin" + }, + "debsngn": { + "_description": "Busingen, Germany", + "_alias": "Europe/Busingen", + "_since": "23" + }, + "djjib": { + "_description": "Djibouti", + "_alias": "Africa/Djibouti" + }, + "dkcph": { + "_description": "Copenhagen, Denmark", + "_alias": "Europe/Copenhagen" + }, + "dmdom": { + "_description": "Dominica", + "_alias": "America/Dominica" + }, + "dosdq": { + "_description": "Santo Domingo, Dominican Republic", + "_alias": "America/Santo_Domingo" + }, + "dzalg": { + "_description": "Algiers, Algeria", + "_alias": "Africa/Algiers" + }, + "ecgps": { + "_description": "Galápagos Islands, Ecuador", + "_alias": "Pacific/Galapagos" + }, + "ecgye": { + "_description": "Guayaquil, Ecuador", + "_alias": "America/Guayaquil" + }, + "eetll": { + "_description": "Tallinn, Estonia", + "_alias": "Europe/Tallinn" + }, + "egcai": { + "_description": "Cairo, Egypt", + "_alias": "Africa/Cairo Egypt" + }, + "eheai": { + "_description": "El Aaiún, Western Sahara", + "_alias": "Africa/El_Aaiun" + }, + "erasm": { + "_description": "Asmara, Eritrea", + "_alias": "Africa/Asmera Africa/Asmara" + }, + "esceu": { + "_description": "Ceuta, Spain", + "_alias": "Africa/Ceuta" + }, + "eslpa": { + "_description": "Canary Islands, Spain", + "_alias": "Atlantic/Canary" + }, + "esmad": { + "_description": "Madrid, Spain", + "_alias": "Europe/Madrid" + }, + "est5edt": { + "_description": "POSIX style time zone for US Eastern Time", + "_alias": "EST5EDT", + "_since": "1.8" + }, + "etadd": { + "_description": "Addis Ababa, Ethiopia", + "_alias": "Africa/Addis_Ababa" + }, + "fihel": { + "_description": "Helsinki, Finland", + "_alias": "Europe/Helsinki" + }, + "fimhq": { + "_description": "Mariehamn, Åland, Finland", + "_alias": "Europe/Mariehamn" + }, + "fjsuv": { + "_description": "Fiji", + "_alias": "Pacific/Fiji" + }, + "fkpsy": { + "_description": "Stanley, Falkland Islands", + "_alias": "Atlantic/Stanley" + }, + "fmksa": { + "_description": "Kosrae, Micronesia", + "_alias": "Pacific/Kosrae" + }, + "fmpni": { + "_description": "Pohnpei, Micronesia", + "_alias": "Pacific/Ponape Pacific/Pohnpei" + }, + "fmtkk": { + "_description": "Chuuk, Micronesia", + "_alias": "Pacific/Truk Pacific/Chuuk Pacific/Yap" + }, + "fotho": { + "_description": "Faroe Islands", + "_alias": "Atlantic/Faeroe Atlantic/Faroe" + }, + "frpar": { + "_description": "Paris, France", + "_alias": "Europe/Paris" + }, + "galbv": { + "_description": "Libreville, Gabon", + "_alias": "Africa/Libreville" + }, + "gaza": { + "_deprecated": true, + "_description": "Gaza Strip, Palestinian Territories", + "_preferred": "gazastrp" + }, + "gazastrp": { + "_description": "Gaza Strip, Palestinian Territories", + "_alias": "Asia/Gaza", + "_since": "40" + }, + "gblon": { + "_description": "London, United Kingdom", + "_alias": "Europe/London Europe/Belfast GB GB-Eire" + }, + "gdgnd": { + "_description": "Grenada", + "_alias": "America/Grenada" + }, + "getbs": { + "_description": "Tbilisi, Georgia", + "_alias": "Asia/Tbilisi" + }, + "gfcay": { + "_description": "Cayenne, French Guiana", + "_alias": "America/Cayenne" + }, + "gggci": { + "_description": "Guernsey", + "_alias": "Europe/Guernsey" + }, + "ghacc": { + "_description": "Accra, Ghana", + "_alias": "Africa/Accra" + }, + "gigib": { + "_description": "Gibraltar", + "_alias": "Europe/Gibraltar" + }, + "gldkshvn": { + "_description": "Danmarkshavn, Greenland", + "_alias": "America/Danmarkshavn" + }, + "glgoh": { + "_description": "Nuuk (Godthåb), Greenland", + "_alias": "America/Godthab America/Nuuk" + }, + "globy": { + "_description": "Ittoqqortoormiit (Scoresbysund), Greenland", + "_alias": "America/Scoresbysund" + }, + "glthu": { + "_description": "Qaanaaq (Thule), Greenland", + "_alias": "America/Thule" + }, + "gmbjl": { + "_description": "Banjul, Gambia", + "_alias": "Africa/Banjul" + }, + "gmt": { + "_description": "Greenwich Mean Time", + "_alias": "Etc/GMT Etc/GMT+0 Etc/GMT-0 Etc/GMT0 Etc/Greenwich GMT GMT+0 GMT-0 GMT0 Greenwich", + "_since": "31" + }, + "gncky": { + "_description": "Conakry, Guinea", + "_alias": "Africa/Conakry" + }, + "gpbbr": { + "_description": "Guadeloupe", + "_alias": "America/Guadeloupe" + }, + "gpmsb": { + "_description": "Marigot, Saint Martin", + "_alias": "America/Marigot" + }, + "gpsbh": { + "_description": "Saint Barthélemy", + "_alias": "America/St_Barthelemy" + }, + "gqssg": { + "_description": "Malabo, Equatorial Guinea", + "_alias": "Africa/Malabo" + }, + "grath": { + "_description": "Athens, Greece", + "_alias": "Europe/Athens" + }, + "gsgrv": { + "_description": "South Georgia and the South Sandwich Islands", + "_alias": "Atlantic/South_Georgia" + }, + "gtgua": { + "_description": "Guatemala", + "_alias": "America/Guatemala" + }, + "gugum": { + "_description": "Guam", + "_alias": "Pacific/Guam" + }, + "gwoxb": { + "_description": "Bissau, Guinea-Bissau", + "_alias": "Africa/Bissau" + }, + "gygeo": { + "_description": "Guyana", + "_alias": "America/Guyana" + }, + "hebron": { + "_description": "West Bank, Palestinian Territories", + "_alias": "Asia/Hebron", + "_since": "21" + }, + "hkhkg": { + "_description": "Hong Kong SAR China", + "_alias": "Asia/Hong_Kong Hongkong" + }, + "hntgu": { + "_description": "Tegucigalpa, Honduras", + "_alias": "America/Tegucigalpa" + }, + "hrzag": { + "_description": "Zagreb, Croatia", + "_alias": "Europe/Zagreb" + }, + "htpap": { + "_description": "Port-au-Prince, Haiti", + "_alias": "America/Port-au-Prince" + }, + "hubud": { + "_description": "Budapest, Hungary", + "_alias": "Europe/Budapest" + }, + "iddjj": { + "_description": "Jayapura, Indonesia", + "_alias": "Asia/Jayapura" + }, + "idjkt": { + "_description": "Jakarta, Indonesia", + "_alias": "Asia/Jakarta" + }, + "idmak": { + "_description": "Makassar, Indonesia", + "_alias": "Asia/Makassar Asia/Ujung_Pandang" + }, + "idpnk": { + "_description": "Pontianak, Indonesia", + "_alias": "Asia/Pontianak" + }, + "iedub": { + "_description": "Dublin, Ireland", + "_alias": "Europe/Dublin Eire" + }, + "imdgs": { + "_description": "Isle of Man", + "_alias": "Europe/Isle_of_Man" + }, + "inccu": { + "_description": "Kolkata, India", + "_alias": "Asia/Calcutta Asia/Kolkata" + }, + "iodga": { + "_description": "Chagos Archipelago", + "_alias": "Indian/Chagos" + }, + "iqbgw": { + "_description": "Baghdad, Iraq", + "_alias": "Asia/Baghdad" + }, + "irthr": { + "_description": "Tehran, Iran", + "_alias": "Asia/Tehran Iran" + }, + "isrey": { + "_description": "Reykjavik, Iceland", + "_alias": "Atlantic/Reykjavik Iceland" + }, + "itrom": { + "_description": "Rome, Italy", + "_alias": "Europe/Rome" + }, + "jeruslm": { + "_description": "Jerusalem", + "_alias": "Asia/Jerusalem Asia/Tel_Aviv Israel" + }, + "jesth": { + "_description": "Jersey", + "_alias": "Europe/Jersey" + }, + "jmkin": { + "_description": "Jamaica", + "_alias": "America/Jamaica Jamaica" + }, + "joamm": { + "_description": "Amman, Jordan", + "_alias": "Asia/Amman" + }, + "jptyo": { + "_description": "Tokyo, Japan", + "_alias": "Asia/Tokyo Japan" + }, + "kenbo": { + "_description": "Nairobi, Kenya", + "_alias": "Africa/Nairobi" + }, + "kgfru": { + "_description": "Bishkek, Kyrgyzstan", + "_alias": "Asia/Bishkek" + }, + "khpnh": { + "_description": "Phnom Penh, Cambodia", + "_alias": "Asia/Phnom_Penh" + }, + "kicxi": { + "_description": "Kiritimati, Kiribati", + "_alias": "Pacific/Kiritimati" + }, + "kipho": { + "_description": "Enderbury Island, Kiribati", + "_alias": "Pacific/Enderbury Pacific/Kanton" + }, + "kitrw": { + "_description": "Tarawa, Kiribati", + "_alias": "Pacific/Tarawa" + }, + "kmyva": { + "_description": "Comoros", + "_alias": "Indian/Comoro" + }, + "knbas": { + "_description": "Saint Kitts", + "_alias": "America/St_Kitts" + }, + "kpfnj": { + "_description": "Pyongyang, North Korea", + "_alias": "Asia/Pyongyang" + }, + "krsel": { + "_description": "Seoul, South Korea", + "_alias": "Asia/Seoul ROK" + }, + "kwkwi": { + "_description": "Kuwait", + "_alias": "Asia/Kuwait" + }, + "kygec": { + "_description": "Cayman Islands", + "_alias": "America/Cayman" + }, + "kzaau": { + "_description": "Aqtau, Kazakhstan", + "_alias": "Asia/Aqtau" + }, + "kzakx": { + "_description": "Aqtobe, Kazakhstan", + "_alias": "Asia/Aqtobe" + }, + "kzala": { + "_description": "Almaty, Kazakhstan", + "_alias": "Asia/Almaty" + }, + "kzguw": { + "_description": "Atyrau (Guryev), Kazakhstan", + "_alias": "Asia/Atyrau", + "_since": "31" + }, + "kzksn": { + "_description": "Qostanay (Kostanay), Kazakhstan", + "_alias": "Asia/Qostanay", + "_since": "35" + }, + "kzkzo": { + "_description": "Kyzylorda, Kazakhstan", + "_alias": "Asia/Qyzylorda" + }, + "kzura": { + "_description": "Oral, Kazakhstan", + "_alias": "Asia/Oral" + }, + "lavte": { + "_description": "Vientiane, Laos", + "_alias": "Asia/Vientiane" + }, + "lbbey": { + "_description": "Beirut, Lebanon", + "_alias": "Asia/Beirut" + }, + "lccas": { + "_description": "Saint Lucia", + "_alias": "America/St_Lucia" + }, + "livdz": { + "_description": "Vaduz, Liechtenstein", + "_alias": "Europe/Vaduz" + }, + "lkcmb": { + "_description": "Colombo, Sri Lanka", + "_alias": "Asia/Colombo" + }, + "lrmlw": { + "_description": "Monrovia, Liberia", + "_alias": "Africa/Monrovia" + }, + "lsmsu": { + "_description": "Maseru, Lesotho", + "_alias": "Africa/Maseru" + }, + "ltvno": { + "_description": "Vilnius, Lithuania", + "_alias": "Europe/Vilnius" + }, + "lulux": { + "_description": "Luxembourg", + "_alias": "Europe/Luxembourg" + }, + "lvrix": { + "_description": "Riga, Latvia", + "_alias": "Europe/Riga" + }, + "lytip": { + "_description": "Tripoli, Libya", + "_alias": "Africa/Tripoli Libya" + }, + "macas": { + "_description": "Casablanca, Morocco", + "_alias": "Africa/Casablanca" + }, + "mcmon": { + "_description": "Monaco", + "_alias": "Europe/Monaco" + }, + "mdkiv": { + "_description": "Chişinău, Moldova", + "_alias": "Europe/Chisinau Europe/Tiraspol" + }, + "metgd": { + "_description": "Podgorica, Montenegro", + "_alias": "Europe/Podgorica" + }, + "mgtnr": { + "_description": "Antananarivo, Madagascar", + "_alias": "Indian/Antananarivo" + }, + "mhkwa": { + "_description": "Kwajalein, Marshall Islands", + "_alias": "Pacific/Kwajalein Kwajalein" + }, + "mhmaj": { + "_description": "Majuro, Marshall Islands", + "_alias": "Pacific/Majuro" + }, + "mkskp": { + "_description": "Skopje, Macedonia", + "_alias": "Europe/Skopje" + }, + "mlbko": { + "_description": "Bamako, Mali", + "_alias": "Africa/Bamako Africa/Timbuktu" + }, + "mmrgn": { + "_description": "Yangon (Rangoon), Burma", + "_alias": "Asia/Rangoon Asia/Yangon" + }, + "mncoq": { + "_description": "Choibalsan, Mongolia", + "_alias": "Asia/Choibalsan" + }, + "mnhvd": { + "_description": "Khovd (Hovd), Mongolia", + "_alias": "Asia/Hovd" + }, + "mnuln": { + "_description": "Ulaanbaatar (Ulan Bator), Mongolia", + "_alias": "Asia/Ulaanbaatar Asia/Ulan_Bator" + }, + "momfm": { + "_description": "Macau SAR China", + "_alias": "Asia/Macau Asia/Macao" + }, + "mpspn": { + "_description": "Saipan, Northern Mariana Islands", + "_alias": "Pacific/Saipan" + }, + "mqfdf": { + "_description": "Martinique", + "_alias": "America/Martinique" + }, + "mrnkc": { + "_description": "Nouakchott, Mauritania", + "_alias": "Africa/Nouakchott" + }, + "msmni": { + "_description": "Montserrat", + "_alias": "America/Montserrat" + }, + "mst7mdt": { + "_description": "POSIX style time zone for US Mountain Time", + "_alias": "MST7MDT", + "_since": "1.8" + }, + "mtmla": { + "_description": "Malta", + "_alias": "Europe/Malta" + }, + "muplu": { + "_description": "Mauritius", + "_alias": "Indian/Mauritius" + }, + "mvmle": { + "_description": "Maldives", + "_alias": "Indian/Maldives" + }, + "mwblz": { + "_description": "Blantyre, Malawi", + "_alias": "Africa/Blantyre" + }, + "mxchi": { + "_description": "Chihuahua, Mexico", + "_alias": "America/Chihuahua" + }, + "mxcjs": { + "_description": "Ciudad Juárez, Mexico", + "_alias": "America/Ciudad_Juarez", + "_since": "43" + }, + "mxcun": { + "_description": "Cancún, Mexico", + "_alias": "America/Cancun" + }, + "mxhmo": { + "_description": "Hermosillo, Mexico", + "_alias": "America/Hermosillo" + }, + "mxmam": { + "_description": "Matamoros, Mexico", + "_alias": "America/Matamoros" + }, + "mxmex": { + "_description": "Mexico City, Mexico", + "_alias": "America/Mexico_City Mexico/General" + }, + "mxmid": { + "_description": "Mérida, Mexico", + "_alias": "America/Merida" + }, + "mxmty": { + "_description": "Monterrey, Mexico", + "_alias": "America/Monterrey" + }, + "mxmzt": { + "_description": "Mazatlán, Mexico", + "_alias": "America/Mazatlan Mexico/BajaSur" + }, + "mxoji": { + "_description": "Ojinaga, Mexico", + "_alias": "America/Ojinaga" + }, + "mxpvr": { + "_description": "Bahía de Banderas, Mexico", + "_alias": "America/Bahia_Banderas", + "_since": "1.9" + }, + "mxstis": { + "_description": "Santa Isabel (Baja California), Mexico", + "_alias": "America/Santa_Isabel" + }, + "mxtij": { + "_description": "Tijuana, Mexico", + "_alias": "America/Tijuana America/Ensenada Mexico/BajaNorte" + }, + "mykch": { + "_description": "Kuching, Malaysia", + "_alias": "Asia/Kuching" + }, + "mykul": { + "_description": "Kuala Lumpur, Malaysia", + "_alias": "Asia/Kuala_Lumpur" + }, + "mzmpm": { + "_description": "Maputo, Mozambique", + "_alias": "Africa/Maputo" + }, + "nawdh": { + "_description": "Windhoek, Namibia", + "_alias": "Africa/Windhoek" + }, + "ncnou": { + "_description": "Noumea, New Caledonia", + "_alias": "Pacific/Noumea" + }, + "nenim": { + "_description": "Niamey, Niger", + "_alias": "Africa/Niamey" + }, + "nfnlk": { + "_description": "Norfolk Island", + "_alias": "Pacific/Norfolk" + }, + "nglos": { + "_description": "Lagos, Nigeria", + "_alias": "Africa/Lagos" + }, + "nimga": { + "_description": "Managua, Nicaragua", + "_alias": "America/Managua" + }, + "nlams": { + "_description": "Amsterdam, Netherlands", + "_alias": "Europe/Amsterdam" + }, + "noosl": { + "_description": "Oslo, Norway", + "_alias": "Europe/Oslo" + }, + "npktm": { + "_description": "Kathmandu, Nepal", + "_alias": "Asia/Katmandu Asia/Kathmandu" + }, + "nrinu": { + "_description": "Nauru", + "_alias": "Pacific/Nauru" + }, + "nuiue": { + "_description": "Niue", + "_alias": "Pacific/Niue" + }, + "nzakl": { + "_description": "Auckland, New Zealand", + "_alias": "Pacific/Auckland Antarctica/South_Pole NZ" + }, + "nzcht": { + "_description": "Chatham Islands, New Zealand", + "_alias": "Pacific/Chatham NZ-CHAT" + }, + "ommct": { + "_description": "Muscat, Oman", + "_alias": "Asia/Muscat" + }, + "papty": { + "_description": "Panama", + "_alias": "America/Panama" + }, + "pelim": { + "_description": "Lima, Peru", + "_alias": "America/Lima" + }, + "pfgmr": { + "_description": "Gambiera Islands, French Polynesia", + "_alias": "Pacific/Gambier" + }, + "pfnhv": { + "_description": "Marquesas Islands, French Polynesia", + "_alias": "Pacific/Marquesas" + }, + "pfppt": { + "_description": "Tahiti, French Polynesia", + "_alias": "Pacific/Tahiti" + }, + "pgpom": { + "_description": "Port Moresby, Papua New Guinea", + "_alias": "Pacific/Port_Moresby" + }, + "pgraw": { + "_description": "Bougainville, Papua New Guinea", + "_alias": "Pacific/Bougainville", + "_since": "27" + }, + "phmnl": { + "_description": "Manila, Philippines", + "_alias": "Asia/Manila" + }, + "pkkhi": { + "_description": "Karachi, Pakistan", + "_alias": "Asia/Karachi" + }, + "plwaw": { + "_description": "Warsaw, Poland", + "_alias": "Europe/Warsaw Poland" + }, + "pmmqc": { + "_description": "Saint Pierre and Miquelon", + "_alias": "America/Miquelon" + }, + "pnpcn": { + "_description": "Pitcairn Islands", + "_alias": "Pacific/Pitcairn" + }, + "prsju": { + "_description": "Puerto Rico", + "_alias": "America/Puerto_Rico" + }, + "pst8pdt": { + "_description": "POSIX style time zone for US Pacific Time", + "_alias": "PST8PDT", + "_since": "1.8" + }, + "ptfnc": { + "_description": "Madeira, Portugal", + "_alias": "Atlantic/Madeira" + }, + "ptlis": { + "_description": "Lisbon, Portugal", + "_alias": "Europe/Lisbon Portugal" + }, + "ptpdl": { + "_description": "Azores, Portugal", + "_alias": "Atlantic/Azores" + }, + "pwror": { + "_description": "Palau", + "_alias": "Pacific/Palau" + }, + "pyasu": { + "_description": "Asunción, Paraguay", + "_alias": "America/Asuncion" + }, + "qadoh": { + "_description": "Qatar", + "_alias": "Asia/Qatar" + }, + "rereu": { + "_description": "Réunion", + "_alias": "Indian/Reunion" + }, + "robuh": { + "_description": "Bucharest, Romania", + "_alias": "Europe/Bucharest" + }, + "rsbeg": { + "_description": "Belgrade, Serbia", + "_alias": "Europe/Belgrade" + }, + "ruasf": { + "_description": "Astrakhan, Russia", + "_alias": "Europe/Astrakhan", + "_since": "30" + }, + "rubax": { + "_description": "Barnaul, Russia", + "_alias": "Asia/Barnaul", + "_since": "30" + }, + "ruchita": { + "_description": "Chita Zabaykalsky, Russia", + "_alias": "Asia/Chita", + "_since": "26" + }, + "rudyr": { + "_description": "Anadyr, Russia", + "_alias": "Asia/Anadyr" + }, + "rugdx": { + "_description": "Magadan, Russia", + "_alias": "Asia/Magadan" + }, + "ruikt": { + "_description": "Irkutsk, Russia", + "_alias": "Asia/Irkutsk" + }, + "rukgd": { + "_description": "Kaliningrad, Russia", + "_alias": "Europe/Kaliningrad" + }, + "rukhndg": { + "_description": "Khandyga Tomponsky, Russia", + "_alias": "Asia/Khandyga", + "_since": "23" + }, + "rukra": { + "_description": "Krasnoyarsk, Russia", + "_alias": "Asia/Krasnoyarsk" + }, + "rukuf": { + "_description": "Samara, Russia", + "_alias": "Europe/Samara" + }, + "rukvx": { + "_description": "Kirov, Russia", + "_alias": "Europe/Kirov", + "_since": "30" + }, + "rumow": { + "_description": "Moscow, Russia", + "_alias": "Europe/Moscow W-SU" + }, + "runoz": { + "_description": "Novokuznetsk, Russia", + "_alias": "Asia/Novokuznetsk" + }, + "ruoms": { + "_description": "Omsk, Russia", + "_alias": "Asia/Omsk" + }, + "ruovb": { + "_description": "Novosibirsk, Russia", + "_alias": "Asia/Novosibirsk" + }, + "rupkc": { + "_description": "Kamchatka Peninsula, Russia", + "_alias": "Asia/Kamchatka" + }, + "rurtw": { + "_description": "Saratov, Russia", + "_alias": "Europe/Saratov", + "_since": "31" + }, + "rusred": { + "_description": "Srednekolymsk, Russia", + "_alias": "Asia/Srednekolymsk", + "_since": "26" + }, + "rutof": { + "_description": "Tomsk, Russia", + "_alias": "Asia/Tomsk", + "_since": "30" + }, + "ruuly": { + "_description": "Ulyanovsk, Russia", + "_alias": "Europe/Ulyanovsk", + "_since": "30" + }, + "ruunera": { + "_description": "Ust-Nera Oymyakonsky, Russia", + "_alias": "Asia/Ust-Nera", + "_since": "23" + }, + "ruuus": { + "_description": "Sakhalin, Russia", + "_alias": "Asia/Sakhalin" + }, + "ruvog": { + "_description": "Volgograd, Russia", + "_alias": "Europe/Volgograd" + }, + "ruvvo": { + "_description": "Vladivostok, Russia", + "_alias": "Asia/Vladivostok" + }, + "ruyek": { + "_description": "Yekaterinburg, Russia", + "_alias": "Asia/Yekaterinburg" + }, + "ruyks": { + "_description": "Yakutsk, Russia", + "_alias": "Asia/Yakutsk" + }, + "rwkgl": { + "_description": "Kigali, Rwanda", + "_alias": "Africa/Kigali" + }, + "saruh": { + "_description": "Riyadh, Saudi Arabia", + "_alias": "Asia/Riyadh" + }, + "sbhir": { + "_description": "Guadalcanal, Solomon Islands", + "_alias": "Pacific/Guadalcanal" + }, + "scmaw": { + "_description": "Mahé, Seychelles", + "_alias": "Indian/Mahe" + }, + "sdkrt": { + "_description": "Khartoum, Sudan", + "_alias": "Africa/Khartoum" + }, + "sesto": { + "_description": "Stockholm, Sweden", + "_alias": "Europe/Stockholm" + }, + "sgsin": { + "_description": "Singapore", + "_alias": "Asia/Singapore Singapore" + }, + "shshn": { + "_description": "Saint Helena", + "_alias": "Atlantic/St_Helena" + }, + "silju": { + "_description": "Ljubljana, Slovenia", + "_alias": "Europe/Ljubljana" + }, + "sjlyr": { + "_description": "Longyearbyen, Svalbard", + "_alias": "Arctic/Longyearbyen Atlantic/Jan_Mayen" + }, + "skbts": { + "_description": "Bratislava, Slovakia", + "_alias": "Europe/Bratislava" + }, + "slfna": { + "_description": "Freetown, Sierra Leone", + "_alias": "Africa/Freetown" + }, + "smsai": { + "_description": "San Marino", + "_alias": "Europe/San_Marino" + }, + "sndkr": { + "_description": "Dakar, Senegal", + "_alias": "Africa/Dakar" + }, + "somgq": { + "_description": "Mogadishu, Somalia", + "_alias": "Africa/Mogadishu" + }, + "srpbm": { + "_description": "Paramaribo, Suriname", + "_alias": "America/Paramaribo" + }, + "ssjub": { + "_description": "Juba, South Sudan", + "_alias": "Africa/Juba", + "_since": "21" + }, + "sttms": { + "_description": "São Tomé, São Tomé and Príncipe", + "_alias": "Africa/Sao_Tome" + }, + "svsal": { + "_description": "El Salvador", + "_alias": "America/El_Salvador" + }, + "sxphi": { + "_description": "Sint Maarten", + "_alias": "America/Lower_Princes", + "_since": "21" + }, + "sydam": { + "_description": "Damascus, Syria", + "_alias": "Asia/Damascus" + }, + "szqmn": { + "_description": "Mbabane, Swaziland", + "_alias": "Africa/Mbabane" + }, + "tcgdt": { + "_description": "Grand Turk, Turks and Caicos Islands", + "_alias": "America/Grand_Turk" + }, + "tdndj": { + "_description": "N'Djamena, Chad", + "_alias": "Africa/Ndjamena" + }, + "tfpfr": { + "_description": "Kerguelen Islands, French Southern Territories", + "_alias": "Indian/Kerguelen" + }, + "tglfw": { + "_description": "Lomé, Togo", + "_alias": "Africa/Lome" + }, + "thbkk": { + "_description": "Bangkok, Thailand", + "_alias": "Asia/Bangkok" + }, + "tjdyu": { + "_description": "Dushanbe, Tajikistan", + "_alias": "Asia/Dushanbe" + }, + "tkfko": { + "_description": "Fakaofo, Tokelau", + "_alias": "Pacific/Fakaofo" + }, + "tldil": { + "_description": "Dili, East Timor", + "_alias": "Asia/Dili" + }, + "tmasb": { + "_description": "Ashgabat, Turkmenistan", + "_alias": "Asia/Ashgabat Asia/Ashkhabad" + }, + "tntun": { + "_description": "Tunis, Tunisia", + "_alias": "Africa/Tunis" + }, + "totbu": { + "_description": "Tongatapu, Tonga", + "_alias": "Pacific/Tongatapu" + }, + "trist": { + "_description": "Istanbul, Türkiye", + "_alias": "Europe/Istanbul Asia/Istanbul Turkey" + }, + "ttpos": { + "_description": "Port of Spain, Trinidad and Tobago", + "_alias": "America/Port_of_Spain" + }, + "tvfun": { + "_description": "Funafuti, Tuvalu", + "_alias": "Pacific/Funafuti" + }, + "twtpe": { + "_description": "Taipei, Taiwan", + "_alias": "Asia/Taipei ROC" + }, + "tzdar": { + "_description": "Dar es Salaam, Tanzania", + "_alias": "Africa/Dar_es_Salaam" + }, + "uaiev": { + "_description": "Kyiv, Ukraine", + "_alias": "Europe/Kiev Europe/Kyiv" + }, + "uaozh": { + "_description": "Zaporizhia (Zaporozhye), Ukraine", + "_alias": "Europe/Zaporozhye" + }, + "uasip": { + "_description": "Simferopol, Ukraine", + "_alias": "Europe/Simferopol" + }, + "uauzh": { + "_description": "Uzhhorod (Uzhgorod), Ukraine", + "_alias": "Europe/Uzhgorod" + }, + "ugkla": { + "_description": "Kampala, Uganda", + "_alias": "Africa/Kampala" + }, + "umawk": { + "_description": "Wake Island, U.S. Minor Outlying Islands", + "_alias": "Pacific/Wake" + }, + "umjon": { + "_description": "Johnston Atoll, U.S. Minor Outlying Islands", + "_alias": "Pacific/Johnston" + }, + "ummdy": { + "_description": "Midway Islands, U.S. Minor Outlying Islands", + "_alias": "Pacific/Midway" + }, + "unk": { + "_description": "Unknown time zone", + "_alias": "Etc/Unknown" + }, + "usadk": { + "_description": "Adak (Alaska), United States", + "_alias": "America/Adak America/Atka US/Aleutian" + }, + "usaeg": { + "_description": "Marengo (Indiana), United States", + "_alias": "America/Indiana/Marengo" + }, + "usanc": { + "_description": "Anchorage, United States", + "_alias": "America/Anchorage US/Alaska" + }, + "usboi": { + "_description": "Boise (Idaho), United States", + "_alias": "America/Boise" + }, + "uschi": { + "_description": "Chicago, United States", + "_alias": "America/Chicago US/Central" + }, + "usden": { + "_description": "Denver, United States", + "_alias": "America/Denver America/Shiprock Navajo US/Mountain" + }, + "usdet": { + "_description": "Detroit, United States", + "_alias": "America/Detroit US/Michigan" + }, + "ushnl": { + "_description": "Honolulu, United States", + "_alias": "Pacific/Honolulu US/Hawaii" + }, + "usind": { + "_description": "Indianapolis, United States", + "_alias": "America/Indianapolis America/Fort_Wayne America/Indiana/Indianapolis US/East-Indiana" + }, + "usinvev": { + "_description": "Vevay (Indiana), United States", + "_alias": "America/Indiana/Vevay" + }, + "usjnu": { + "_description": "Juneau (Alaska), United States", + "_alias": "America/Juneau" + }, + "usknx": { + "_description": "Knox (Indiana), United States", + "_alias": "America/Indiana/Knox America/Knox_IN US/Indiana-Starke" + }, + "uslax": { + "_description": "Los Angeles, United States", + "_alias": "America/Los_Angeles US/Pacific US/Pacific-New" + }, + "uslui": { + "_description": "Louisville (Kentucky), United States", + "_alias": "America/Louisville America/Kentucky/Louisville" + }, + "usmnm": { + "_description": "Menominee (Michigan), United States", + "_alias": "America/Menominee" + }, + "usmoc": { + "_description": "Monticello (Kentucky), United States", + "_alias": "America/Kentucky/Monticello" + }, + "usmtm": { + "_description": "Metlakatla (Alaska), United States", + "_alias": "America/Metlakatla", + "_since": "1.9.1" + }, + "usnavajo": { + "_deprecated": true, + "_description": "Shiprock (Navajo), United States", + "_preferred": "usden" + }, + "usndcnt": { + "_description": "Center (North Dakota), United States", + "_alias": "America/North_Dakota/Center" + }, + "usndnsl": { + "_description": "New Salem (North Dakota), United States", + "_alias": "America/North_Dakota/New_Salem" + }, + "usnyc": { + "_description": "New York, United States", + "_alias": "America/New_York US/Eastern" + }, + "usoea": { + "_description": "Vincennes (Indiana), United States", + "_alias": "America/Indiana/Vincennes" + }, + "usome": { + "_description": "Nome (Alaska), United States", + "_alias": "America/Nome" + }, + "usphx": { + "_description": "Phoenix, United States", + "_alias": "America/Phoenix US/Arizona" + }, + "ussit": { + "_description": "Sitka (Alaska), United States", + "_alias": "America/Sitka", + "_since": "1.9.1" + }, + "ustel": { + "_description": "Tell City (Indiana), United States", + "_alias": "America/Indiana/Tell_City" + }, + "uswlz": { + "_description": "Winamac (Indiana), United States", + "_alias": "America/Indiana/Winamac" + }, + "uswsq": { + "_description": "Petersburg (Indiana), United States", + "_alias": "America/Indiana/Petersburg" + }, + "usxul": { + "_description": "Beulah (North Dakota), United States", + "_alias": "America/North_Dakota/Beulah", + "_since": "1.9.1" + }, + "usyak": { + "_description": "Yakutat (Alaska), United States", + "_alias": "America/Yakutat" + }, + "utc": { + "_description": "UTC (Coordinated Universal Time)", + "_alias": "Etc/UTC Etc/UCT Etc/Universal Etc/Zulu UCT UTC Universal Zulu" + }, + "utce01": { + "_description": "1 hour ahead of UTC", + "_alias": "Etc/GMT-1" + }, + "utce02": { + "_description": "2 hours ahead of UTC", + "_alias": "Etc/GMT-2" + }, + "utce03": { + "_description": "3 hours ahead of UTC", + "_alias": "Etc/GMT-3" + }, + "utce04": { + "_description": "4 hours ahead of UTC", + "_alias": "Etc/GMT-4" + }, + "utce05": { + "_description": "5 hours ahead of UTC", + "_alias": "Etc/GMT-5" + }, + "utce06": { + "_description": "6 hours ahead of UTC", + "_alias": "Etc/GMT-6" + }, + "utce07": { + "_description": "7 hours ahead of UTC", + "_alias": "Etc/GMT-7" + }, + "utce08": { + "_description": "8 hours ahead of UTC", + "_alias": "Etc/GMT-8" + }, + "utce09": { + "_description": "9 hours ahead of UTC", + "_alias": "Etc/GMT-9" + }, + "utce10": { + "_description": "10 hours ahead of UTC", + "_alias": "Etc/GMT-10" + }, + "utce11": { + "_description": "11 hours ahead of UTC", + "_alias": "Etc/GMT-11" + }, + "utce12": { + "_description": "12 hours ahead of UTC", + "_alias": "Etc/GMT-12" + }, + "utce13": { + "_description": "13 hours ahead of UTC", + "_alias": "Etc/GMT-13" + }, + "utce14": { + "_description": "14 hours ahead of UTC", + "_alias": "Etc/GMT-14" + }, + "utcw01": { + "_description": "1 hour behind UTC", + "_alias": "Etc/GMT+1" + }, + "utcw02": { + "_description": "2 hours behind UTC", + "_alias": "Etc/GMT+2" + }, + "utcw03": { + "_description": "3 hours behind UTC", + "_alias": "Etc/GMT+3" + }, + "utcw04": { + "_description": "4 hours behind UTC", + "_alias": "Etc/GMT+4" + }, + "utcw05": { + "_description": "5 hours behind UTC", + "_alias": "Etc/GMT+5 EST" + }, + "utcw06": { + "_description": "6 hours behind UTC", + "_alias": "Etc/GMT+6" + }, + "utcw07": { + "_description": "7 hours behind UTC", + "_alias": "Etc/GMT+7 MST" + }, + "utcw08": { + "_description": "8 hours behind UTC", + "_alias": "Etc/GMT+8" + }, + "utcw09": { + "_description": "9 hours behind UTC", + "_alias": "Etc/GMT+9" + }, + "utcw10": { + "_description": "10 hours behind UTC", + "_alias": "Etc/GMT+10 HST" + }, + "utcw11": { + "_description": "11 hours behind UTC", + "_alias": "Etc/GMT+11" + }, + "utcw12": { + "_description": "12 hours behind UTC", + "_alias": "Etc/GMT+12" + }, + "uymvd": { + "_description": "Montevideo, Uruguay", + "_alias": "America/Montevideo" + }, + "uzskd": { + "_description": "Samarkand, Uzbekistan", + "_alias": "Asia/Samarkand" + }, + "uztas": { + "_description": "Tashkent, Uzbekistan", + "_alias": "Asia/Tashkent" + }, + "vavat": { + "_description": "Vatican City", + "_alias": "Europe/Vatican" + }, + "vcsvd": { + "_description": "Saint Vincent, Saint Vincent and the Grenadines", + "_alias": "America/St_Vincent" + }, + "veccs": { + "_description": "Caracas, Venezuela", + "_alias": "America/Caracas" + }, + "vgtov": { + "_description": "Tortola, British Virgin Islands", + "_alias": "America/Tortola" + }, + "vistt": { + "_description": "Saint Thomas, U.S. Virgin Islands", + "_alias": "America/St_Thomas America/Virgin" + }, + "vnsgn": { + "_description": "Ho Chi Minh City, Vietnam", + "_alias": "Asia/Saigon Asia/Ho_Chi_Minh" + }, + "vuvli": { + "_description": "Efate, Vanuatu", + "_alias": "Pacific/Efate" + }, + "wfmau": { + "_description": "Wallis Islands, Wallis and Futuna", + "_alias": "Pacific/Wallis" + }, + "wsapw": { + "_description": "Apia, Samoa", + "_alias": "Pacific/Apia" + }, + "yeade": { + "_description": "Aden, Yemen", + "_alias": "Asia/Aden" + }, + "ytmam": { + "_description": "Mayotte", + "_alias": "Indian/Mayotte" + }, + "zajnb": { + "_description": "Johannesburg, South Africa", + "_alias": "Africa/Johannesburg" + }, + "zmlun": { + "_description": "Lusaka, Zambia", + "_alias": "Africa/Lusaka" + }, + "zwhre": { + "_description": "Harare, Zimbabwe", + "_alias": "Africa/Harare" + } + } + } + } +} diff --git a/polyfill/test/ecmascript.mjs b/polyfill/test/ecmascript.mjs index 03fc56e7e1..94d3c752b3 100644 --- a/polyfill/test/ecmascript.mjs +++ b/polyfill/test/ecmascript.mjs @@ -8,6 +8,7 @@ import { strict as assert } from 'assert'; const { deepEqual, equal, throws } = assert; import bigInt from 'big-integer'; +import { readFileSync } from 'fs'; import * as ES from '../lib/ecmascript.mjs'; import { GetSlot, TIMEZONE_ID } from '../lib/slots.mjs'; @@ -64,6 +65,61 @@ describe('ECMAScript', () => { }); } }); + + describe('GetAvailableNamedTimeZoneIdentifier', () => { + it('Case-normalizes time zone IDs', () => { + // eslint-disable-next-line max-len + // curl -s https://raw.githubusercontent.com/unicode-org/cldr-json/main/cldr-json/cldr-bcp47/bcp47/timezone.json > cldr-timezone.json + const cldrTimeZonePath = new URL('./cldr-timezone.json', import.meta.url); + const cldrTimeZoneJson = JSON.parse(readFileSync(cldrTimeZonePath)); + + // get CLDR's time zone IDs + const cldrIdentifiers = Object.entries(cldrTimeZoneJson.keyword.u.tz) + .filter((z) => !z[0].startsWith('_')) // ignore metadata elements + .map((z) => z[1]._alias) // pull out the list of IANA IDs for each CLDR zone + .filter(Boolean) // CLDR deprecated zones no longer have an IANA ID + .flatMap((ids) => ids.split(' ')) // expand all space-delimited IANA IDs for each zone + .filter((id) => !['America/Ciudad_Juarez'].includes(id)) // exclude IDs that are too new to be supported + .filter((id) => !['Etc/Unknown'].includes(id)); // see https://github.com/tc39/proposal-canonical-tz/pull/25 + + // These 4 legacy IDs are in TZDB, in Wikipedia, and accepted by ICU, but they're not in CLDR data. + // Not sure where they come from, perhaps hard-coded into ICU, but we'll test them anyway. + const missingFromCLDR = ['CET', 'EET', 'MET', 'WET']; + + // All IDs that we know about + const ids = [...new Set([...missingFromCLDR, ...cldrIdentifiers, ...Intl.supportedValuesOf('timeZone')])]; + + for (const id of ids) { + const lower = id.toLowerCase(); + const upper = id.toUpperCase(); + equal(ES.GetAvailableNamedTimeZoneIdentifier(id)?.identifier, id); + equal(ES.GetAvailableNamedTimeZoneIdentifier(upper)?.identifier, id); + equal(ES.GetAvailableNamedTimeZoneIdentifier(lower)?.identifier, id); + } + }); + it('Returns canonical IDs', () => { + const ids = Intl.supportedValuesOf('timeZone'); + for (const id of ids) { + equal(ES.GetAvailableNamedTimeZoneIdentifier(id).primaryIdentifier, id); + } + const knownAliases = [ + ['America/Atka', 'America/Adak'], + ['America/Knox_IN', 'America/Indiana/Knox'], + ['Asia/Ashkhabad', 'Asia/Ashgabat'], + ['Asia/Dacca', 'Asia/Dhaka'], + ['Asia/Istanbul', 'Europe/Istanbul'], + ['Asia/Macao', 'Asia/Macau'], + ['Asia/Thimbu', 'Asia/Thimphu'], + ['Asia/Ujung_Pandang', 'Asia/Makassar'], + ['Asia/Ulan_Bator', 'Asia/Ulaanbaatar'] + ]; + for (const [identifier, primaryIdentifier] of knownAliases) { + const record = ES.GetAvailableNamedTimeZoneIdentifier(identifier); + equal(record.identifier, identifier); + equal(record.primaryIdentifier, primaryIdentifier); + } + }); + }); }); import { normalize } from 'path';