@@ -3169,6 +3169,34 @@ export function DifferenceZonedDateTime(ns1, ns2, timeZone, calendar, largestUni
31693169 return CombineDateAndTimeDuration ( dateDifference , timeDuration ) ;
31703170}
31713171
3172+ export function CompareDurations ( duration1 , duration2 , zonedRelativeTo , plainRelativeTo , largestUnit1 , largestUnit2 ) {
3173+ if (
3174+ zonedRelativeTo &&
3175+ ( TemporalUnitCategory ( largestUnit1 ) === 'date' || TemporalUnitCategory ( largestUnit2 ) === 'date' )
3176+ ) {
3177+ const timeZone = GetSlot ( zonedRelativeTo , TIME_ZONE ) ;
3178+ const calendar = GetSlot ( zonedRelativeTo , CALENDAR ) ;
3179+ const epochNs = GetSlot ( zonedRelativeTo , EPOCHNANOSECONDS ) ;
3180+
3181+ const after1 = AddZonedDateTime ( epochNs , timeZone , calendar , duration1 ) ;
3182+ const after2 = AddZonedDateTime ( epochNs , timeZone , calendar , duration2 ) ;
3183+ return ComparisonResult ( after1 . minus ( after2 ) . toJSNumber ( ) ) ;
3184+ }
3185+
3186+ let d1 = duration1 . date . days ;
3187+ let d2 = duration2 . date . days ;
3188+ if ( IsCalendarUnit ( largestUnit1 ) || IsCalendarUnit ( largestUnit2 ) ) {
3189+ if ( ! plainRelativeTo ) {
3190+ throw new RangeErrorCtor ( 'A starting point is required for years, months, or weeks comparison' ) ;
3191+ }
3192+ d1 = DateDurationDays ( duration1 . date , plainRelativeTo ) ;
3193+ d2 = DateDurationDays ( duration2 . date , plainRelativeTo ) ;
3194+ }
3195+ const timeDuration1 = duration1 . time . add24HourDays ( d1 ) ;
3196+ const timeDuration2 = duration2 . time . add24HourDays ( d2 ) ;
3197+ return timeDuration1 . cmp ( timeDuration2 ) ;
3198+ }
3199+
31723200// Epoch-nanosecond bounding technique where the start/end of the calendar-unit
31733201// interval are converted to epoch-nanosecond times and destEpochNs is nudged to
31743202// either one.
@@ -3190,13 +3218,29 @@ function NudgeToCalendarUnit(
31903218 // Create a duration with smallestUnit trunc'd towards zero
31913219 // Create a separate duration that incorporates roundingIncrement
31923220 let r1 , r2 , startDuration , endDuration ;
3221+ var didExpandCalendarUnit = false ;
3222+ const compare = ( d1 , d2 ) => CompareDurations ( d1 , d2 , undefined , ToTemporalDate ( isoDateTime . isoDate ) , unit , unit ) ;
3223+ var cmpResult = 0 ;
31933224 switch ( unit ) {
31943225 case 'year' : {
31953226 const years = RoundNumberToIncrement ( duration . date . years , increment , 'trunc' ) ;
31963227 r1 = years ;
31973228 r2 = years + increment * sign ;
31983229 startDuration = { years : r1 , months : 0 , weeks : 0 , days : 0 } ;
31993230 endDuration = { ...startDuration , years : r2 } ;
3231+ cmpResult = compare ( CombineDateAndTimeDuration ( endDuration , TimeDuration . ZERO ) , duration ) ;
3232+ if ( ( sign > 0 && cmpResult != 1 ) || ( sign < 0 && cmpResult != - 1 ) ) {
3233+ didExpandCalendarUnit = true ;
3234+ r1 = r2 ;
3235+ r2 = years + increment * 2 * sign ;
3236+ endDuration = { ...endDuration , years : r2 } ;
3237+ cmpResult = compare ( CombineDateAndTimeDuration ( endDuration , TimeDuration . ZERO ) , duration ) ;
3238+ assert (
3239+ ( sign > 0 && cmpResult == 1 ) || ( sign < 0 && cmpResult == - 1 ) ,
3240+ "nudgeToCalendarUnit: couldn't find larger duration"
3241+ ) ;
3242+ startDuration = { ...startDuration , years : r1 } ;
3243+ }
32003244 break ;
32013245 }
32023246 case 'month' : {
@@ -3205,6 +3249,19 @@ function NudgeToCalendarUnit(
32053249 r2 = months + increment * sign ;
32063250 startDuration = AdjustDateDurationRecord ( duration . date , 0 , 0 , r1 ) ;
32073251 endDuration = AdjustDateDurationRecord ( duration . date , 0 , 0 , r2 ) ;
3252+ cmpResult = compare ( CombineDateAndTimeDuration ( endDuration , TimeDuration . ZERO ) , duration ) ;
3253+ if ( ( sign > 0 && cmpResult != 1 ) || ( sign < 0 && cmpResult != - 1 ) ) {
3254+ didExpandCalendarUnit = true ;
3255+ r1 = r2 ;
3256+ r2 = months + increment * 2 * sign ;
3257+ endDuration = AdjustDateDurationRecord ( duration . date , 0 , 0 , r2 ) ;
3258+ cmpResult = compare ( CombineDateAndTimeDuration ( endDuration , TimeDuration . ZERO ) , duration ) ;
3259+ assert (
3260+ ( sign > 0 && cmpResult == 1 ) || ( sign < 0 && cmpResult == - 1 ) ,
3261+ "nudgeToCalendarUnit: couldn't find larger duration"
3262+ ) ;
3263+ startDuration = AdjustDateDurationRecord ( duration . date , 0 , 0 , r1 ) ;
3264+ }
32083265 break ;
32093266 }
32103267 case 'week' : {
@@ -3240,7 +3297,7 @@ function NudgeToCalendarUnit(
32403297 // If the start of the bound is the same as the "origin" (aka relativeTo),
32413298 // use the origin's epoch-nanoseconds as-is instead of relying on isoDateTime,
32423299 // which then gets zoned and converted back to epoch-nanoseconds,
3243- // which looses precision and creates a distorted bounding window.
3300+ // which loses precision and creates a distorted bounding window.
32443301 startEpochNs = originEpochNs ;
32453302 } else {
32463303 const start = CalendarDateAdd ( calendar , isoDateTime . isoDate , startDuration , 'constrain' ) ;
@@ -3278,8 +3335,8 @@ function NudgeToCalendarUnit(
32783335 assert ( MathAbs ( r1 ) <= MathAbs ( total ) && MathAbs ( total ) <= MathAbs ( r2 ) , 'r1 ≤ total ≤ r2' ) ;
32793336
32803337 // Determine whether expanded or contracted
3281- const didExpandCalendarUnit = roundedUnit === MathAbs ( r2 ) ;
3282- duration = { date : didExpandCalendarUnit ? endDuration : startDuration , time : TimeDuration . ZERO } ;
3338+ didExpandCalendarUnit | = roundedUnit === MathAbs ( r2 ) ;
3339+ duration = { date : roundedUnit == MathAbs ( r2 ) ? endDuration : startDuration , time : TimeDuration . ZERO } ;
32833340
32843341 const nudgeResult = {
32853342 duration,
0 commit comments