@@ -4,6 +4,7 @@ const ArrayIncludes = Array.prototype.includes;
44const ArrayPrototypePush = Array . prototype . push ;
55const ArrayPrototypeSort = Array . prototype . sort ;
66const IntlDateTimeFormat = globalThis . Intl . DateTimeFormat ;
7+ const IntlSupportedValuesOf = globalThis . Intl . supportedValuesOf ;
78const MathAbs = Math . abs ;
89const MathFloor = Math . floor ;
910const MathMax = Math . max ;
@@ -368,7 +369,9 @@ export function ParseTemporalTimeZone(stringIdent) {
368369 const { tzName, offset, z } = ParseTemporalTimeZoneString ( stringIdent ) ;
369370 if ( tzName ) {
370371 if ( IsTimeZoneOffsetString ( tzName ) ) return CanonicalizeTimeZoneOffsetString ( tzName ) ;
371- return GetCanonicalTimeZoneIdentifier ( tzName ) ;
372+ const record = GetAvailableNamedTimeZoneIdentifier ( tzName ) ;
373+ if ( ! record ) throw new RangeError ( `Unrecognized time zone ${ tzName } ` ) ;
374+ return record . primaryIdentifier ;
372375 }
373376 if ( z ) return 'UTC' ;
374377 // if !tzName && !z then offset must be present
@@ -2589,15 +2592,87 @@ export function ParseTimeZoneOffsetString(string) {
25892592 return sign * ( ( ( hours * 60 + minutes ) * 60 + seconds ) * 1e9 + nanoseconds ) ;
25902593}
25912594
2592- // In the spec, GetCanonicalTimeZoneIdentifier is infallible and is always
2593- // preceded by a call to IsAvailableTimeZoneName. However in the polyfill,
2594- // we don't (yet) have a way to check if a time zone ID is valid without
2595- // also canonicalizing it. So we combine both operations into one function,
2596- // which will return the canonical ID if the ID is valid, and will throw
2597- // if it's not.
2598- export function GetCanonicalTimeZoneIdentifier ( timeZoneIdentifier ) {
2599- const formatter = getIntlDateTimeFormatEnUsForTimeZone ( timeZoneIdentifier ) ;
2600- return formatter . resolvedOptions ( ) . timeZone ;
2595+ let canonicalTimeZoneIdsCache = undefined ;
2596+
2597+ export function GetAvailableNamedTimeZoneIdentifier ( identifier ) {
2598+ // The most common case is when the identifier is a canonical time zone ID.
2599+ // Fast-path that case by caching all canonical IDs. For old ECMAScript
2600+ // implementations lacking this API, set the cache to `null` to avoid retries.
2601+ if ( canonicalTimeZoneIdsCache === undefined ) {
2602+ const canonicalTimeZoneIds = IntlSupportedValuesOf ?. ( 'timeZone' ) ;
2603+ canonicalTimeZoneIdsCache = canonicalTimeZoneIds
2604+ ? new Map ( canonicalTimeZoneIds . map ( ( id ) => [ ASCIILowercase ( id ) , id ] ) )
2605+ : null ;
2606+ }
2607+
2608+ const lower = ASCIILowercase ( identifier ) ;
2609+ let primaryIdentifier = canonicalTimeZoneIdsCache ?. get ( lower ) ;
2610+ if ( primaryIdentifier ) return { identifier : primaryIdentifier , primaryIdentifier } ;
2611+
2612+ // It's not already a primary identifier, so get its primary identifier (or
2613+ // return if it's not an available named time zone ID).
2614+ try {
2615+ const formatter = getIntlDateTimeFormatEnUsForTimeZone ( identifier ) ;
2616+ primaryIdentifier = formatter . resolvedOptions ( ) . timeZone ;
2617+ } catch {
2618+ return undefined ;
2619+ }
2620+
2621+ // The identifier is an alias (a deprecated identifier that's a synonym for a
2622+ // primary identifier), so we need to case-normalize the identifier to match
2623+ // the IANA TZDB, e.g. america/new_york => America/New_York. There's no
2624+ // built-in way to do this using Intl.DateTimeFormat, but the we can normalize
2625+ // almost all aliases (modulo a few special cases) using the TZDB's basic
2626+ // capitalization pattern:
2627+ // 1. capitalize the first letter of the identifier
2628+ // 2. capitalize the letter after every slash, dash, or underscore delimiter
2629+ const standardCase = [ ...lower ]
2630+ . map ( ( c , i ) => ( i === 0 || '/-_' . includes ( lower [ i - 1 ] ) ? c . toUpperCase ( ) : c ) )
2631+ . join ( '' ) ;
2632+ const segments = standardCase . split ( '/' ) ;
2633+
2634+ if ( segments . length === 1 ) {
2635+ // If a single-segment legacy ID is 2-3 chars or contains a number or dash, then
2636+ // (except for the "GB-Eire" special case) the case-normalized form is uppercase.
2637+ // These are: GMT+0, GMT-0, GB, NZ, PRC, ROC, ROK, UCT, GMT, GMT0, CET, CST6CDT,
2638+ // EET, EST, HST, MET, MST, MST7MDT, PST8PDT, WET, NZ-CHAT, and W-SU.
2639+ // Otherwise it's standard form: first letter capitalized, e.g. Iran, Egypt, Hongkong
2640+ if ( lower === 'gb-eire' ) return { identifier : 'GB-Eire' , primaryIdentifier } ;
2641+ return {
2642+ identifier : lower . length <= 3 || / [ - 0 - 9 ] / . test ( lower ) ? lower . toUpperCase ( ) : segments [ 0 ] ,
2643+ primaryIdentifier
2644+ } ;
2645+ }
2646+
2647+ // All Etc zone names are uppercase except three exceptions.
2648+ if ( segments [ 0 ] === 'Etc' ) {
2649+ const etcName = [ 'Zulu' , 'Greenwich' , 'Universal' ] . includes ( segments [ 1 ] ) ? segments [ 1 ] : segments [ 1 ] . toUpperCase ( ) ;
2650+ return { identifier : `Etc/${ etcName } ` , primaryIdentifier } ;
2651+ }
2652+
2653+ // Legacy US identifiers like US/Alaska or US/Indiana-Starke are 2 segments and use standard form.
2654+ if ( segments [ 0 ] === 'Us' ) return { identifier : `US/${ segments [ 1 ] } ` , primaryIdentifier } ;
2655+
2656+ // For multi-segment IDs, there's a few special cases in the second/third segments
2657+ const specialCases = {
2658+ Act : 'ACT' ,
2659+ Lhi : 'LHI' ,
2660+ Nsw : 'NSW' ,
2661+ Dar_Es_Salaam : 'Dar_es_Salaam' ,
2662+ Port_Of_Spain : 'Port_of_Spain' ,
2663+ Isle_Of_Man : 'Isle_of_Man' ,
2664+ Comodrivadavia : 'ComodRivadavia' ,
2665+ Knox_In : 'Knox_IN' ,
2666+ Dumontdurville : 'DumontDUrville' ,
2667+ Mcmurdo : 'McMurdo' ,
2668+ Denoronha : 'DeNoronha' ,
2669+ Easterisland : 'EasterIsland' ,
2670+ Bajanorte : 'BajaNorte' ,
2671+ Bajasur : 'BajaSur'
2672+ } ;
2673+ segments [ 1 ] = specialCases [ segments [ 1 ] ] ?? segments [ 1 ] ;
2674+ if ( segments . length > 2 ) segments [ 2 ] = specialCases [ segments [ 2 ] ] ?? segments [ 2 ] ;
2675+ return { identifier : segments . join ( '/' ) , primaryIdentifier } ;
26012676}
26022677
26032678export function GetNamedTimeZoneOffsetNanoseconds ( id , epochNanoseconds ) {
0 commit comments