@@ -361,20 +361,26 @@ export function RejectTemporalLikeObject(item) {
361361}
362362
363363export function CanonicalizeTimeZoneOffsetString ( offsetString ) {
364- const offsetNs = ParseTimeZoneOffsetString ( offsetString ) ;
365- return FormatTimeZoneOffsetString ( offsetNs ) ;
364+ const offsetMinutes = ParseTimeZoneOffsetStringMinutes ( offsetString ) ;
365+ return FormatTimeZoneOffsetMinutes ( offsetMinutes ) ;
366366}
367367
368368export function ParseTemporalTimeZone ( stringIdent ) {
369369 const { tzName, offset, z } = ParseTemporalTimeZoneString ( stringIdent ) ;
370370 if ( tzName ) {
371371 if ( IsTimeZoneOffsetString ( tzName ) ) return CanonicalizeTimeZoneOffsetString ( tzName ) ;
372+ if ( IsTimeZoneOffsetStringNanosecondsPrecision ( tzName ) ) {
373+ throw new RangeError ( `seconds not allowed in offset string: ${ tzName } ` ) ;
374+ }
372375 const record = GetAvailableNamedTimeZoneIdentifier ( tzName ) ;
373376 if ( ! record ) throw new RangeError ( `Unrecognized time zone ${ tzName } ` ) ;
374377 return record . primaryIdentifier ;
375378 }
376379 if ( z ) return 'UTC' ;
377380 // if !tzName && !z then offset must be present
381+ if ( ! IsTimeZoneOffsetString ( tzName ) ) {
382+ throw new RangeError ( `seconds not allowed in offset string: ${ tzName } ` ) ;
383+ }
378384 return CanonicalizeTimeZoneOffsetString ( offset ) ;
379385}
380386
@@ -641,7 +647,7 @@ export function ParseTemporalInstant(isoString) {
641647 ParseTemporalInstantString ( isoString ) ;
642648
643649 if ( ! z && ! offset ) throw new RangeError ( 'Temporal.Instant requires a time zone offset' ) ;
644- const offsetNs = z ? 0 : ParseTimeZoneOffsetString ( offset ) ;
650+ const offsetNs = z ? 0 : ParseTimeZoneOffsetStringNanoseconds ( offset ) ;
645651 ( { year, month, day, hour, minute, second, millisecond, microsecond, nanosecond } = BalanceISODateTime (
646652 year ,
647653 month ,
@@ -1005,7 +1011,7 @@ export function ToRelativeTemporalObject(options) {
10051011 calendar = ASCIILowercase ( calendar ) ;
10061012 }
10071013 if ( timeZone === undefined ) return CreateTemporalDate ( year , month , day , calendar ) ;
1008- const offsetNs = offsetBehaviour === 'option' ? ParseTimeZoneOffsetString ( offset ) : 0 ;
1014+ const offsetNs = offsetBehaviour === 'option' ? ParseTimeZoneOffsetStringNanoseconds ( offset ) : 0 ;
10091015 const epochNanoseconds = InterpretISODateTimeOffset (
10101016 year ,
10111017 month ,
@@ -1406,7 +1412,7 @@ export function InterpretISODateTimeOffset(
14061412 // the user-provided offset doesn't match any instants for this time
14071413 // zone and date/time.
14081414 if ( offsetOpt === 'reject' ) {
1409- const offsetStr = FormatTimeZoneOffsetString ( offsetNs ) ;
1415+ const offsetStr = FormatTimeZoneOffsetNanoseconds ( offsetNs ) ;
14101416 const timeZoneString = IsTemporalTimeZone ( timeZone ) ? GetSlot ( timeZone , TIMEZONE_ID ) : 'time zone' ;
14111417 throw new RangeError ( `Offset ${ offsetStr } is invalid for ${ dt } in ${ timeZoneString } ` ) ;
14121418 }
@@ -1469,7 +1475,7 @@ export function ToTemporalZonedDateTime(item, options) {
14691475 ToTemporalOverflow ( options ) ; // validate and ignore
14701476 }
14711477 let offsetNs = 0 ;
1472- if ( offsetBehaviour === 'option' ) offsetNs = ParseTimeZoneOffsetString ( offset ) ;
1478+ if ( offsetBehaviour === 'option' ) offsetNs = ParseTimeZoneOffsetStringNanoseconds ( offset ) ;
14731479 const epochNanoseconds = InterpretISODateTimeOffset (
14741480 year ,
14751481 month ,
@@ -2162,7 +2168,7 @@ export function GetOffsetNanosecondsFor(timeZone, instant, getOffsetNanosecondsF
21622168
21632169export function GetOffsetStringFor ( timeZone , instant ) {
21642170 const offsetNs = GetOffsetNanosecondsFor ( timeZone , instant ) ;
2165- return FormatTimeZoneOffsetString ( offsetNs ) ;
2171+ return FormatTimeZoneOffsetNanoseconds ( offsetNs ) ;
21662172}
21672173
21682174export function GetPlainDateTimeFor ( timeZone , instant , calendar ) {
@@ -2384,7 +2390,7 @@ export function TemporalInstantToString(instant, timeZone, precision) {
23842390 let timeZoneString = 'Z' ;
23852391 if ( timeZone !== undefined ) {
23862392 const offsetNs = GetOffsetNanosecondsFor ( outputTimeZone , instant ) ;
2387- timeZoneString = FormatISOTimeZoneOffsetString ( offsetNs ) ;
2393+ timeZoneString = FormatTimeZoneOffsetRoundToMinutes ( offsetNs ) ;
23882394 }
23892395 return `${ year } -${ month } -${ day } T${ hour } :${ minute } ${ seconds } ${ timeZoneString } ` ;
23902396}
@@ -2564,7 +2570,7 @@ export function TemporalZonedDateTimeToString(
25642570 let result = `${ year } -${ month } -${ day } T${ hour } :${ minute } ${ seconds } ` ;
25652571 if ( showOffset !== 'never' ) {
25662572 const offsetNs = GetOffsetNanosecondsFor ( tz , instant ) ;
2567- result += FormatISOTimeZoneOffsetString ( offsetNs ) ;
2573+ result += FormatTimeZoneOffsetRoundToMinutes ( offsetNs ) ;
25682574 }
25692575 if ( showTimeZone !== 'never' ) {
25702576 const identifier = ToTemporalTimeZoneIdentifier ( tz ) ;
@@ -2579,7 +2585,26 @@ export function IsTimeZoneOffsetString(string) {
25792585 return OFFSET . test ( string ) ;
25802586}
25812587
2582- export function ParseTimeZoneOffsetString ( string ) {
2588+ export function IsTimeZoneOffsetStringNanosecondsPrecision ( string ) {
2589+ const match = OFFSET . exec ( string ) ;
2590+ return match && ! match [ 4 ] && ! match [ 5 ] ;
2591+ }
2592+
2593+ export function ParseTimeZoneOffsetStringMinutes ( string ) {
2594+ const match = OFFSET . exec ( string ) ;
2595+ if ( ! match ) {
2596+ throw new RangeError ( `invalid time zone offset: ${ string } ` ) ;
2597+ }
2598+ if ( match [ 4 ] || match [ 5 ] ) {
2599+ throw new RangeError ( `seconds are not allowed in time zone offset: ${ string } ` ) ;
2600+ }
2601+ const sign = match [ 1 ] === '-' || match [ 1 ] === '\u2212' ? - 1 : + 1 ;
2602+ const hours = + match [ 2 ] ;
2603+ const minutes = + ( match [ 3 ] || 0 ) ;
2604+ return sign * ( hours * 60 + minutes ) ;
2605+ }
2606+
2607+ export function ParseTimeZoneOffsetStringNanoseconds ( string ) {
25832608 const match = OFFSET . exec ( string ) ;
25842609 if ( ! match ) {
25852610 throw new RangeError ( `invalid time zone offset: ${ string } ` ) ;
@@ -2702,7 +2727,7 @@ export function GetNamedTimeZoneOffsetNanoseconds(id, epochNanoseconds) {
27022727 return + utc . minus ( epochNanoseconds ) ;
27032728}
27042729
2705- export function FormatTimeZoneOffsetString ( offsetNanoseconds ) {
2730+ export function FormatTimeZoneOffsetNanoseconds ( offsetNanoseconds ) {
27062731 const sign = offsetNanoseconds < 0 ? '-' : '+' ;
27072732 offsetNanoseconds = MathAbs ( offsetNanoseconds ) ;
27082733 const nanoseconds = offsetNanoseconds % 1e9 ;
@@ -2724,16 +2749,13 @@ export function FormatTimeZoneOffsetString(offsetNanoseconds) {
27242749 return `${ sign } ${ hourString } :${ minuteString } ${ post } ` ;
27252750}
27262751
2727- export function FormatISOTimeZoneOffsetString ( offsetNanoseconds ) {
2728- 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 ) ;
2752+ export function FormatTimeZoneOffsetMinutes ( offsetMinutes ) {
2753+ return FormatTimeZoneOffsetNanoseconds ( offsetMinutes * 6e10 ) ;
2754+ }
27332755
2734- const hourString = ISODateTimePartString ( hours ) ;
2735- const minuteString = ISODateTimePartString ( minutes ) ;
2736- return ` ${ sign } ${ hourString } : ${ minuteString } ` ;
2756+ export function FormatTimeZoneOffsetRoundToMinutes ( offsetNanoseconds ) {
2757+ offsetNanoseconds = RoundNumberToIncrement ( bigInt ( offsetNanoseconds ) , 60e9 , 'halfExpand' ) . toJSNumber ( ) ;
2758+ return FormatTimeZoneOffsetNanoseconds ( offsetNanoseconds ) ;
27372759}
27382760
27392761export function GetUTCEpochNanoseconds ( year , month , day , hour , minute , second , millisecond , microsecond , nanosecond ) {
0 commit comments