Skip to content

Commit 0b82d52

Browse files
catamorphismptomato
authored andcommitted
Handle non-ISO-calendar edge case with leap years and differencing
See #3159
1 parent 1962c8b commit 0b82d52

File tree

1 file changed

+39
-3
lines changed

1 file changed

+39
-3
lines changed

polyfill/lib/calendar.mjs

Lines changed: 39 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1054,21 +1054,40 @@ const nonIsoHelperBase = {
10541054
}
10551055
case 'month':
10561056
case 'year': {
1057+
// Sign is -1 if calendarTwo < calendarOne, 1 if calendarTwo > calendarOne
10571058
const sign = this.compareCalendarDates(calendarTwo, calendarOne);
1059+
// If dates are equal, return 0 date duration
10581060
if (!sign) {
10591061
return { years: 0, months: 0, weeks: 0, days: 0 };
10601062
}
1063+
// Take the difference between the years of the two dates
10611064
const diffYears = calendarTwo.year - calendarOne.year;
1065+
// Take the difference between the days of the two dates
10621066
const diffDays = calendarTwo.day - calendarOne.day;
1067+
// Get list of additional months, and possibly cycle info
10631068
const monthInfo = monthCodeInfo[this.id];
10641069
const cycleInfo = monthInfo ? monthInfo.cycleInfo : { years: 1, months: 12 };
1070+
// Compute years difference if necessary
10651071
if (diffYears && (largestUnit === 'year' || cycleInfo)) {
1072+
// diffInYearSign is the result of comparing the month-day of calendarOne
1073+
// and the month-day of calendarTwo, in the forwards direction
10661074
let diffInYearSign = 0;
1075+
// If calendarTwo's month is greater than calendarOne's month,
1076+
// then the years difference should be positive (or negative if sign < 0).
10671077
if (calendarTwo.monthCode > calendarOne.monthCode) diffInYearSign = 1;
1078+
// If calendarTwo's month is less than calendarOne's month,
1079+
// then the years difference should be negative (or positive if sign < 0).
10681080
if (calendarTwo.monthCode < calendarOne.monthCode) diffInYearSign = -1;
1081+
// If the two months are equal, the sign of the years difference should be
1082+
// the sign of the days difference.
10691083
if (!diffInYearSign) diffInYearSign = MathSign(diffDays);
1070-
const isOneFurtherInYear = diffInYearSign * sign < 0;
1071-
years = isOneFurtherInYear ? diffYears - sign : diffYears;
1084+
// isCalendarOneFurtherInYear is true iff the ordering of the years
1085+
// doesn't match the ordering of the month/days.
1086+
const isCalendarOneFurtherInYear = diffInYearSign * sign < 0;
1087+
// Add either 1 or -1 to years if isCalendarOneFurtherInYear is true.
1088+
// If monthday-two is later in the year than monthday-one, need
1089+
// to correct diffYears because it's gone one too far.
1090+
years = isCalendarOneFurtherInYear ? diffYears - sign : diffYears;
10721091
}
10731092
// Try to skip ahead as many months as possible for this calendar
10741093
// without adding month by month in a loop
@@ -1079,13 +1098,26 @@ const nonIsoHelperBase = {
10791098
}
10801099
years = 0;
10811100
}
1101+
1102+
// intermediate should be a date between calendarOne and calendarTwo,
1103+
// that is within a year of calendarTwo.
10821104
const intermediate =
10831105
years || months ? this.addCalendar(calendarOne, { years, months }, 'constrain', cache) : calendarOne;
1106+
1107+
// At this point, intermediate could fail to be in between calendarOne and calendarTwo
1108+
// due to leap years.
1109+
// In that case, add or subtract an extra year from years,
1110+
// so that the months can be totaled up correctly.
1111+
if (this.compareCalendarDates(intermediate, calendarTwo) * sign > 0) {
1112+
years -= sign;
1113+
}
1114+
10841115
// Now we have less than one cycle remaining. Add one month at a time
10851116
// until we go over the target, then back up one month and calculate
10861117
// remaining days.
10871118
let current;
1088-
let next = intermediate;
1119+
// Need to re-add years and months because years might have changed
1120+
let next = years || months ? this.addCalendar(calendarOne, { years, months }, 'constrain', cache) : calendarOne;
10891121
do {
10901122
months += sign;
10911123
current = next;
@@ -1099,6 +1131,10 @@ const nonIsoHelperBase = {
10991131
months -= sign; // correct for loop above which overshoots by 1
11001132
const remainingDays = this.calendarDaysUntil(current, calendarTwo, cache);
11011133
days = remainingDays;
1134+
1135+
// This may return a duration like <P12M11D> that appears to have unbalanced months.
1136+
// But that's fine, because subtracting <P12M11D> from a date may have different
1137+
// results than subtracting <P1Y11D> from the same date, in the presence of leap months.
11021138
break;
11031139
}
11041140
}

0 commit comments

Comments
 (0)