Skip to content

Commit 47ad4c4

Browse files
committed
WIP: Change NudgeToCalendarUnit to use relative date when comparing durations
See #3168
1 parent c8571ae commit 47ad4c4

File tree

2 files changed

+68
-30
lines changed

2 files changed

+68
-30
lines changed

polyfill/lib/duration.mjs

Lines changed: 8 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -416,34 +416,15 @@ export class Duration {
416416

417417
const largestUnit1 = ES.DefaultTemporalLargestUnit(one);
418418
const largestUnit2 = ES.DefaultTemporalLargestUnit(two);
419-
const duration1 = ES.ToInternalDurationRecord(one);
420-
const duration2 = ES.ToInternalDurationRecord(two);
421419

422-
if (
423-
zonedRelativeTo &&
424-
(ES.TemporalUnitCategory(largestUnit1) === 'date' || ES.TemporalUnitCategory(largestUnit2) === 'date')
425-
) {
426-
const timeZone = GetSlot(zonedRelativeTo, TIME_ZONE);
427-
const calendar = GetSlot(zonedRelativeTo, CALENDAR);
428-
const epochNs = GetSlot(zonedRelativeTo, EPOCHNANOSECONDS);
429-
430-
const after1 = ES.AddZonedDateTime(epochNs, timeZone, calendar, duration1);
431-
const after2 = ES.AddZonedDateTime(epochNs, timeZone, calendar, duration2);
432-
return ES.ComparisonResult(after1.minus(after2).toJSNumber());
433-
}
434-
435-
let d1 = duration1.date.days;
436-
let d2 = duration2.date.days;
437-
if (ES.IsCalendarUnit(largestUnit1) || ES.IsCalendarUnit(largestUnit2)) {
438-
if (!plainRelativeTo) {
439-
throw new RangeErrorCtor('A starting point is required for years, months, or weeks comparison');
440-
}
441-
d1 = ES.DateDurationDays(duration1.date, plainRelativeTo);
442-
d2 = ES.DateDurationDays(duration2.date, plainRelativeTo);
443-
}
444-
const timeDuration1 = duration1.time.add24HourDays(d1);
445-
const timeDuration2 = duration2.time.add24HourDays(d2);
446-
return timeDuration1.cmp(timeDuration2);
420+
return ES.CompareDurations(
421+
ES.ToInternalDurationRecord(one),
422+
ES.ToInternalDurationRecord(two),
423+
zonedRelativeTo,
424+
plainRelativeTo,
425+
largestUnit1,
426+
largestUnit2
427+
);
447428
}
448429
}
449430

polyfill/lib/ecmascript.mjs

Lines changed: 60 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)