@@ -268,6 +268,17 @@ export function IsTemporalMonthDay(item: unknown): item is Temporal.PlainMonthDa
268268export function IsTemporalZonedDateTime ( item : unknown ) : item is Temporal . ZonedDateTime {
269269 return HasSlot ( item , EPOCHNANOSECONDS , TIME_ZONE , CALENDAR ) ;
270270}
271+ export function RejectObjectWithCalendarOrTimeZone ( item : AnyTemporalLikeType ) {
272+ if ( HasSlot ( item , CALENDAR ) || HasSlot ( item , TIME_ZONE ) ) {
273+ throw new TypeError ( 'with() does not support a calendar or timeZone property' ) ;
274+ }
275+ if ( ( item as any ) . calendar !== undefined ) {
276+ throw new TypeError ( 'with() does not support a calendar property' ) ;
277+ }
278+ if ( ( item as any ) . timeZone !== undefined ) {
279+ throw new TypeError ( 'with() does not support a timeZone property' ) ;
280+ }
281+ }
271282function TemporalTimeZoneFromString ( stringIdent : string ) {
272283 // TODO: why aren't these three variables destructured to include `undefined` as possible types?
273284 let { ianaName, offset, z } = ParseTemporalTimeZoneString ( stringIdent ) ;
@@ -917,6 +928,7 @@ export function ToRelativeTemporalObject(options: {
917928 if ( relativeTo === undefined ) return relativeTo as undefined ;
918929
919930 let offsetBehaviour : OffsetBehaviour = 'option' ;
931+ let matchMinutes = false ;
920932 let year , month , day , hour , minute , second , millisecond , microsecond , nanosecond , calendar , timeZone , offset ;
921933 if ( IsObject ( relativeTo ) ) {
922934 if ( IsTemporalZonedDateTime ( relativeTo ) || IsTemporalDateTime ( relativeTo ) ) return relativeTo ;
@@ -935,7 +947,18 @@ export function ToRelativeTemporalObject(options: {
935947 ) ;
936948 }
937949 calendar = GetTemporalCalendarWithISODefault ( relativeTo ) ;
938- const fieldNames = CalendarFields ( calendar , [ 'day' , 'month' , 'monthCode' , 'year' ] as const ) ;
950+ const fieldNames = CalendarFields ( calendar , [
951+ 'day' ,
952+ 'hour' ,
953+ 'microsecond' ,
954+ 'millisecond' ,
955+ 'minute' ,
956+ 'month' ,
957+ 'monthCode' ,
958+ 'nanosecond' ,
959+ 'second' ,
960+ 'year'
961+ ] as const ) ;
939962 const fields = ToTemporalDateTimeFields ( relativeTo , fieldNames ) ;
940963 const dateOptions = ObjectCreate ( null ) ;
941964 dateOptions . overflow = 'constrain' ;
@@ -962,6 +985,7 @@ export function ToRelativeTemporalObject(options: {
962985 }
963986 if ( ! calendar ) calendar = GetISO8601Calendar ( ) ;
964987 calendar = ToTemporalCalendar ( calendar ) ;
988+ matchMinutes = true ;
965989 }
966990 if ( timeZone ) {
967991 timeZone = ToTemporalTimeZone ( timeZone ) ;
@@ -981,7 +1005,8 @@ export function ToRelativeTemporalObject(options: {
9811005 offsetNs ,
9821006 timeZone ,
9831007 'compatible' ,
984- 'reject'
1008+ 'reject' ,
1009+ matchMinutes
9851010 ) ;
9861011 return CreateTemporalZonedDateTime ( epochNanoseconds , timeZone , calendar ) ;
9871012 }
@@ -1187,7 +1212,7 @@ export function ToTemporalYearMonthFields(
11871212 return PrepareTemporalFields ( bag , entries ) ;
11881213}
11891214
1190- export function ToTemporalZonedDateTimeFields (
1215+ function ToTemporalZonedDateTimeFields (
11911216 bag : Temporal . ZonedDateTimeLike ,
11921217 fieldNames : readonly ( keyof Temporal . ZonedDateTimeLike ) [ ]
11931218) {
@@ -1514,7 +1539,8 @@ export function InterpretISODateTimeOffset(
15141539 offsetNs : number ,
15151540 timeZone : Temporal . TimeZoneProtocol ,
15161541 disambiguation : Temporal . ToInstantOptions [ 'disambiguation' ] ,
1517- offsetOpt : Temporal . OffsetDisambiguationOptions [ 'offset' ]
1542+ offsetOpt : Temporal . OffsetDisambiguationOptions [ 'offset' ] ,
1543+ matchMinute : boolean
15181544) {
15191545 const DateTime = GetIntrinsic ( '%Temporal.PlainDateTime%' ) ;
15201546 const dt = new DateTime ( year , month , day , hour , minute , second , millisecond , microsecond , nanosecond ) ;
@@ -1540,7 +1566,10 @@ export function InterpretISODateTimeOffset(
15401566 const possibleInstants = GetPossibleInstantsFor ( timeZone , dt ) ;
15411567 for ( const candidate of possibleInstants ) {
15421568 const candidateOffset = GetOffsetNanosecondsFor ( timeZone , candidate ) ;
1543- if ( candidateOffset === offsetNs ) return GetSlot ( candidate , EPOCHNANOSECONDS ) ;
1569+ const roundedCandidateOffset = RoundNumberToIncrement ( bigInt ( candidateOffset ) , 60e9 , 'halfExpand' ) . toJSNumber ( ) ;
1570+ if ( candidateOffset === offsetNs || ( matchMinute && roundedCandidateOffset === offsetNs ) ) {
1571+ return GetSlot ( candidate , EPOCHNANOSECONDS ) ;
1572+ }
15441573 }
15451574
15461575 // the user-provided offset doesn't match any instants for this time
@@ -1575,6 +1604,7 @@ export function ToTemporalZonedDateTime(
15751604 timeZone ,
15761605 offset : string ,
15771606 calendar : string | Temporal . CalendarProtocol ;
1607+ let matchMinute = false ;
15781608 let offsetBehaviour : OffsetBehaviour = 'option' ;
15791609 if ( IsObject ( item ) ) {
15801610 if ( IsTemporalZonedDateTime ( item ) ) return item ;
@@ -1619,6 +1649,7 @@ export function ToTemporalZonedDateTime(
16191649 timeZone = new TemporalTimeZone ( ianaName ) ;
16201650 if ( ! calendar ) calendar = GetISO8601Calendar ( ) ;
16211651 calendar = ToTemporalCalendar ( calendar ) ;
1652+ matchMinute = true ; // ISO strings may specify offset with less precision
16221653 }
16231654 let offsetNs = 0 ;
16241655 if ( offsetBehaviour === 'option' ) offsetNs = ParseOffsetString ( offset ) ;
@@ -1638,7 +1669,8 @@ export function ToTemporalZonedDateTime(
16381669 offsetNs ,
16391670 timeZone ,
16401671 disambiguation ,
1641- offsetOpt
1672+ offsetOpt ,
1673+ matchMinute
16421674 ) ;
16431675 return CreateTemporalZonedDateTime ( epochNanoseconds , timeZone , calendar ) ;
16441676}
@@ -2414,7 +2446,10 @@ export function TemporalInstantToString(
24142446 precision
24152447 ) ;
24162448 let timeZoneString = 'Z' ;
2417- if ( timeZone !== undefined ) timeZoneString = BuiltinTimeZoneGetOffsetStringFor ( outputTimeZone , instant ) ;
2449+ if ( timeZone !== undefined ) {
2450+ const offsetNs = GetOffsetNanosecondsFor ( outputTimeZone , instant ) ;
2451+ timeZoneString = FormatISOTimeZoneOffsetString ( offsetNs ) ;
2452+ }
24182453 return `${ year } -${ month } -${ day } T${ hour } :${ minute } ${ seconds } ${ timeZoneString } ` ;
24192454}
24202455
@@ -2629,7 +2664,10 @@ export function TemporalZonedDateTimeToString(
26292664 precision
26302665 ) ;
26312666 let result = `${ year } -${ month } -${ day } T${ hour } :${ minute } ${ seconds } ` ;
2632- if ( showOffset !== 'never' ) result += BuiltinTimeZoneGetOffsetStringFor ( tz , instant ) ;
2667+ if ( showOffset !== 'never' ) {
2668+ const offsetNs = GetOffsetNanosecondsFor ( tz , instant ) ;
2669+ result += FormatISOTimeZoneOffsetString ( offsetNs ) ;
2670+ }
26332671 if ( showTimeZone !== 'never' ) result += `[${ tz } ]` ;
26342672 const calendarID = ToString ( GetSlot ( zdt , CALENDAR ) ) ;
26352673 result += FormatCalendarAnnotation ( calendarID , showCalendar ) ;
@@ -2686,6 +2724,17 @@ function FormatTimeZoneOffsetString(offsetNanosecondsParam: number): string {
26862724 return `${ sign } ${ hourString } :${ minuteString } ${ post } ` ;
26872725}
26882726
2727+ function FormatISOTimeZoneOffsetString ( offsetNanosecondsParam : number ) : string {
2728+ let offsetNanoseconds = RoundNumberToIncrement ( bigInt ( offsetNanosecondsParam ) , 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+ }
26892738export function GetEpochFromISOParts (
26902739 year : number ,
26912740 month : number ,
0 commit comments