@@ -360,24 +360,6 @@ export function RejectTemporalLikeObject(item) {
360
360
}
361
361
}
362
362
363
- export function CanonicalizeTimeZoneOffsetString ( offsetString ) {
364
- const offsetNs = ParseTimeZoneOffsetString ( offsetString ) ;
365
- return FormatTimeZoneOffsetString ( offsetNs ) ;
366
- }
367
-
368
- export function ParseTemporalTimeZone ( stringIdent ) {
369
- const { tzName, offset, z } = ParseTemporalTimeZoneString ( stringIdent ) ;
370
- if ( tzName ) {
371
- if ( IsTimeZoneOffsetString ( tzName ) ) return CanonicalizeTimeZoneOffsetString ( tzName ) ;
372
- const record = GetAvailableNamedTimeZoneIdentifier ( tzName ) ;
373
- if ( ! record ) throw new RangeError ( `Unrecognized time zone ${ tzName } ` ) ;
374
- return record . primaryIdentifier ;
375
- }
376
- if ( z ) return 'UTC' ;
377
- // if !tzName && !z then offset must be present
378
- return CanonicalizeTimeZoneOffsetString ( offset ) ;
379
- }
380
-
381
363
export function MaybeFormatCalendarAnnotation ( calendar , showCalendar ) {
382
364
if ( showCalendar === 'never' ) return '' ;
383
365
return FormatCalendarAnnotation ( ToTemporalCalendarIdentifier ( calendar ) , showCalendar ) ;
@@ -570,6 +552,18 @@ export function ParseTemporalMonthDayString(isoString) {
570
552
return { month, day, calendar, referenceISOYear } ;
571
553
}
572
554
555
+ const TIMEZONE_IDENTIFIER = new RegExp ( `^${ PARSE . timeZoneID . source } $` , 'i' ) ;
556
+ const OFFSET_IDENTIFIER = new RegExp ( `^${ PARSE . offsetIdentifier . source } $` ) ;
557
+
558
+ export function ParseTimeZoneIdentifier ( identifier ) {
559
+ if ( ! TIMEZONE_IDENTIFIER . test ( identifier ) ) throw new RangeError ( `Invalid time zone identifier: ${ identifier } ` ) ;
560
+ if ( OFFSET_IDENTIFIER . test ( identifier ) ) {
561
+ const { offsetNanoseconds } = ParseDateTimeUTCOffset ( identifier ) ;
562
+ return { offsetNanoseconds } ;
563
+ }
564
+ return { tzName : identifier } ;
565
+ }
566
+
573
567
export function ParseTemporalTimeZoneString ( stringIdent ) {
574
568
const bareID = new RegExp ( `^${ PARSE . timeZoneID . source } $` , 'i' ) ;
575
569
if ( bareID . test ( stringIdent ) ) return { tzName : stringIdent } ;
@@ -641,7 +635,7 @@ export function ParseTemporalInstant(isoString) {
641
635
ParseTemporalInstantString ( isoString ) ;
642
636
643
637
if ( ! z && ! offset ) throw new RangeError ( 'Temporal.Instant requires a time zone offset' ) ;
644
- const offsetNs = z ? 0 : ParseTimeZoneOffsetString ( offset ) ;
638
+ const offsetNs = z ? 0 : ParseDateTimeUTCOffset ( offset ) . offsetNanoseconds ;
645
639
( { year, month, day, hour, minute, second, millisecond, microsecond, nanosecond } = BalanceISODateTime (
646
640
year ,
647
641
month ,
@@ -1005,7 +999,7 @@ export function ToRelativeTemporalObject(options) {
1005
999
calendar = ASCIILowercase ( calendar ) ;
1006
1000
}
1007
1001
if ( timeZone === undefined ) return CreateTemporalDate ( year , month , day , calendar ) ;
1008
- const offsetNs = offsetBehaviour === 'option' ? ParseTimeZoneOffsetString ( offset ) : 0 ;
1002
+ const offsetNs = offsetBehaviour === 'option' ? ParseDateTimeUTCOffset ( offset ) . offsetNanoseconds : 0 ;
1009
1003
const epochNanoseconds = InterpretISODateTimeOffset (
1010
1004
year ,
1011
1005
month ,
@@ -1406,7 +1400,7 @@ export function InterpretISODateTimeOffset(
1406
1400
// the user-provided offset doesn't match any instants for this time
1407
1401
// zone and date/time.
1408
1402
if ( offsetOpt === 'reject' ) {
1409
- const offsetStr = FormatTimeZoneOffsetString ( offsetNs ) ;
1403
+ const offsetStr = FormatOffsetTimeZoneIdentifier ( offsetNs ) ;
1410
1404
const timeZoneString = IsTemporalTimeZone ( timeZone ) ? GetSlot ( timeZone , TIMEZONE_ID ) : 'time zone' ;
1411
1405
throw new RangeError ( `Offset ${ offsetStr } is invalid for ${ dt } in ${ timeZoneString } ` ) ;
1412
1406
}
@@ -1469,7 +1463,7 @@ export function ToTemporalZonedDateTime(item, options) {
1469
1463
ToTemporalOverflow ( options ) ; // validate and ignore
1470
1464
}
1471
1465
let offsetNs = 0 ;
1472
- if ( offsetBehaviour === 'option' ) offsetNs = ParseTimeZoneOffsetString ( offset ) ;
1466
+ if ( offsetBehaviour === 'option' ) offsetNs = ParseDateTimeUTCOffset ( offset ) . offsetNanoseconds ;
1473
1467
const epochNanoseconds = InterpretISODateTimeOffset (
1474
1468
year ,
1475
1469
month ,
@@ -2099,7 +2093,20 @@ export function ToTemporalTimeZoneSlotValue(temporalTimeZoneLike) {
2099
2093
return temporalTimeZoneLike ;
2100
2094
}
2101
2095
const identifier = ToString ( temporalTimeZoneLike ) ;
2102
- return ParseTemporalTimeZone ( identifier ) ;
2096
+ const { tzName, offset, z } = ParseTemporalTimeZoneString ( identifier ) ;
2097
+ if ( tzName ) {
2098
+ // tzName is any valid identifier string in brackets, and could be an offset identifier
2099
+ const { offsetNanoseconds } = ParseTimeZoneIdentifier ( tzName ) ;
2100
+ if ( offsetNanoseconds !== undefined ) return FormatOffsetTimeZoneIdentifier ( offsetNanoseconds ) ;
2101
+
2102
+ const record = GetAvailableNamedTimeZoneIdentifier ( tzName ) ;
2103
+ if ( ! record ) throw new RangeError ( `Unrecognized time zone ${ tzName } ` ) ;
2104
+ return record . primaryIdentifier ;
2105
+ }
2106
+ if ( z ) return 'UTC' ;
2107
+ // if !tzName && !z then offset must be present
2108
+ const { offsetNanoseconds } = ParseDateTimeUTCOffset ( offset ) ;
2109
+ return FormatOffsetTimeZoneIdentifier ( offsetNanoseconds ) ;
2103
2110
}
2104
2111
2105
2112
export function ToTemporalTimeZoneIdentifier ( slotValue ) {
@@ -2162,7 +2169,7 @@ export function GetOffsetNanosecondsFor(timeZone, instant, getOffsetNanosecondsF
2162
2169
2163
2170
export function GetOffsetStringFor ( timeZone , instant ) {
2164
2171
const offsetNs = GetOffsetNanosecondsFor ( timeZone , instant ) ;
2165
- return FormatTimeZoneOffsetString ( offsetNs ) ;
2172
+ return FormatOffsetTimeZoneIdentifier ( offsetNs ) ;
2166
2173
}
2167
2174
2168
2175
export function GetPlainDateTimeFor ( timeZone , instant , calendar ) {
@@ -2384,7 +2391,7 @@ export function TemporalInstantToString(instant, timeZone, precision) {
2384
2391
let timeZoneString = 'Z' ;
2385
2392
if ( timeZone !== undefined ) {
2386
2393
const offsetNs = GetOffsetNanosecondsFor ( outputTimeZone , instant ) ;
2387
- timeZoneString = FormatISOTimeZoneOffsetString ( offsetNs ) ;
2394
+ timeZoneString = FormatDateTimeUTCOffsetRounded ( offsetNs ) ;
2388
2395
}
2389
2396
return `${ year } -${ month } -${ day } T${ hour } :${ minute } ${ seconds } ${ timeZoneString } ` ;
2390
2397
}
@@ -2564,7 +2571,7 @@ export function TemporalZonedDateTimeToString(
2564
2571
let result = `${ year } -${ month } -${ day } T${ hour } :${ minute } ${ seconds } ` ;
2565
2572
if ( showOffset !== 'never' ) {
2566
2573
const offsetNs = GetOffsetNanosecondsFor ( tz , instant ) ;
2567
- result += FormatISOTimeZoneOffsetString ( offsetNs ) ;
2574
+ result += FormatDateTimeUTCOffsetRounded ( offsetNs ) ;
2568
2575
}
2569
2576
if ( showTimeZone !== 'never' ) {
2570
2577
const identifier = ToTemporalTimeZoneIdentifier ( tz ) ;
@@ -2575,11 +2582,11 @@ export function TemporalZonedDateTimeToString(
2575
2582
return result ;
2576
2583
}
2577
2584
2578
- export function IsTimeZoneOffsetString ( string ) {
2585
+ export function IsOffsetTimeZoneIdentifier ( string ) {
2579
2586
return OFFSET . test ( string ) ;
2580
2587
}
2581
2588
2582
- export function ParseTimeZoneOffsetString ( string ) {
2589
+ export function ParseDateTimeUTCOffset ( string ) {
2583
2590
const match = OFFSET . exec ( string ) ;
2584
2591
if ( ! match ) {
2585
2592
throw new RangeError ( `invalid time zone offset: ${ string } ` ) ;
@@ -2589,7 +2596,9 @@ export function ParseTimeZoneOffsetString(string) {
2589
2596
const minutes = + ( match [ 3 ] || 0 ) ;
2590
2597
const seconds = + ( match [ 4 ] || 0 ) ;
2591
2598
const nanoseconds = + ( ( match [ 5 ] || 0 ) + '000000000' ) . slice ( 0 , 9 ) ;
2592
- return sign * ( ( ( hours * 60 + minutes ) * 60 + seconds ) * 1e9 + nanoseconds ) ;
2599
+ const offsetNanoseconds = sign * ( ( ( hours * 60 + minutes ) * 60 + seconds ) * 1e9 + nanoseconds ) ;
2600
+ const hasSubMinutePrecision = match [ 4 ] !== undefined || match [ 5 ] !== undefined ;
2601
+ return { offsetNanoseconds, hasSubMinutePrecision } ;
2593
2602
}
2594
2603
2595
2604
let canonicalTimeZoneIdsCache = undefined ;
@@ -2702,17 +2711,16 @@ export function GetNamedTimeZoneOffsetNanoseconds(id, epochNanoseconds) {
2702
2711
return + utc . minus ( epochNanoseconds ) ;
2703
2712
}
2704
2713
2705
- export function FormatTimeZoneOffsetString ( offsetNanoseconds ) {
2714
+ export function FormatOffsetTimeZoneIdentifier ( offsetNanoseconds ) {
2706
2715
const sign = offsetNanoseconds < 0 ? '-' : '+' ;
2707
2716
offsetNanoseconds = MathAbs ( offsetNanoseconds ) ;
2708
- const nanoseconds = offsetNanoseconds % 1e9 ;
2709
- const seconds = MathFloor ( offsetNanoseconds / 1e9 ) % 60 ;
2710
- const minutes = MathFloor ( offsetNanoseconds / 60e9 ) % 60 ;
2711
2717
const hours = MathFloor ( offsetNanoseconds / 3600e9 ) ;
2712
-
2713
2718
const hourString = ISODateTimePartString ( hours ) ;
2719
+ const minutes = MathFloor ( offsetNanoseconds / 60e9 ) % 60 ;
2714
2720
const minuteString = ISODateTimePartString ( minutes ) ;
2721
+ const seconds = MathFloor ( offsetNanoseconds / 1e9 ) % 60 ;
2715
2722
const secondString = ISODateTimePartString ( seconds ) ;
2723
+ const nanoseconds = offsetNanoseconds % 1e9 ;
2716
2724
let post = '' ;
2717
2725
if ( nanoseconds ) {
2718
2726
let fraction = `${ nanoseconds } ` . padStart ( 9 , '0' ) ;
@@ -2724,16 +2732,9 @@ export function FormatTimeZoneOffsetString(offsetNanoseconds) {
2724
2732
return `${ sign } ${ hourString } :${ minuteString } ${ post } ` ;
2725
2733
}
2726
2734
2727
- export function FormatISOTimeZoneOffsetString ( offsetNanoseconds ) {
2735
+ export function FormatDateTimeUTCOffsetRounded ( offsetNanoseconds ) {
2728
2736
offsetNanoseconds = RoundNumberToIncrement ( bigInt ( offsetNanoseconds ) , 60e9 , 'halfExpand' ) . toJSNumber ( ) ;
2729
- const sign = offsetNanoseconds < 0 ? '-' : '+' ;
2730
- offsetNanoseconds = MathAbs ( offsetNanoseconds ) ;
2731
- const minutes = ( offsetNanoseconds / 60e9 ) % 60 ;
2732
- const hours = MathFloor ( offsetNanoseconds / 3600e9 ) ;
2733
-
2734
- const hourString = ISODateTimePartString ( hours ) ;
2735
- const minuteString = ISODateTimePartString ( minutes ) ;
2736
- return `${ sign } ${ hourString } :${ minuteString } ` ;
2737
+ return FormatOffsetTimeZoneIdentifier ( offsetNanoseconds ) ;
2737
2738
}
2738
2739
2739
2740
export function GetUTCEpochNanoseconds ( year , month , day , hour , minute , second , millisecond , microsecond , nanosecond ) {
0 commit comments