Skip to content

Commit 92bbd2e

Browse files
committed
Polyfill: Implement IsValidMonthCodeForCalendar as in spec
This makes the reference implementation slightly more like the spec text, which should help make sure the spec text is correct. The derivation of monthsPerYear from the table for non-lunisolar calendars would ideally be written differently, but this is temporary until we can refactor the implementation to be even more like the spec.
1 parent a1fd8ac commit 92bbd2e

File tree

1 file changed

+37
-14
lines changed

1 file changed

+37
-14
lines changed

polyfill/lib/calendar.mjs

Lines changed: 37 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -161,7 +161,7 @@ impl['iso8601'] = {
161161
if ((type === 'date' || type === 'month-day') && fields.day === undefined) {
162162
throw new TypeErrorCtor('day is required');
163163
}
164-
ObjectAssign(fields, resolveNonLunisolarMonth(fields));
164+
ObjectAssign(fields, resolveNonLunisolarMonth(fields, 'iso8601'));
165165
},
166166
dateToISO(fields, overflow) {
167167
return ES.RegulateISODate(fields.year, fields.month, fields.day, overflow);
@@ -287,34 +287,60 @@ impl['iso8601'] = {
287287
}
288288
};
289289

290+
const monthCodeInfo = {
291+
chinese: {
292+
additionalMonths: ['M01L', 'M02L', 'M03L', 'M04L', 'M05L', 'M06L', 'M07L', 'M08L', 'M09L', 'M10L', 'M11L', 'M12L']
293+
},
294+
coptic: {
295+
additionalMonths: ['M13']
296+
},
297+
dangi: {
298+
additionalMonths: ['M01L', 'M02L', 'M03L', 'M04L', 'M05L', 'M06L', 'M07L', 'M08L', 'M09L', 'M10L', 'M11L', 'M12L']
299+
},
300+
ethioaa: {
301+
additionalMonths: ['M13']
302+
},
303+
ethiopic: {
304+
additionalMonths: ['M13']
305+
},
306+
hebrew: {
307+
additionalMonths: ['M05L']
308+
}
309+
};
310+
311+
function IsValidMonthCodeForCalendar(calendar, monthCode) {
312+
const { monthNumber, isLeapMonth } = ParseMonthCode(monthCode);
313+
if (!isLeapMonth && monthNumber >= 1 && monthNumber <= 12) return true;
314+
if (!ObjectHasOwn(monthCodeInfo, calendar)) return false;
315+
return Call(ArrayPrototypeIncludes, monthCodeInfo[calendar].additionalMonths, [monthCode]);
316+
}
317+
290318
/**
291319
* Safely merge a month, monthCode pair into an integer month.
292320
* If both are present, make sure they match.
293321
* This logic doesn't work for lunisolar calendars!
294322
* */
295-
function resolveNonLunisolarMonth(calendarDate, overflow = undefined, monthsPerYear = 12) {
323+
function resolveNonLunisolarMonth(calendarDate, calendar, overflow = undefined) {
296324
let { month, monthCode } = calendarDate;
297325
if (monthCode === undefined) {
298326
if (month === undefined) throw new TypeErrorCtor('Either month or monthCode are required');
299327
// The ISO calendar uses the default (undefined) value because it does
300328
// constrain/reject after this method returns. Non-ISO calendars, however,
301329
// rely on this function to constrain/reject out-of-range `month` values.
330+
const monthsPerYear =
331+
12 + (ObjectHasOwn(monthCodeInfo, calendar) ? monthCodeInfo[calendar].additionalMonths.length : 0);
302332
if (overflow === 'reject') ES.RejectToRange(month, 1, monthsPerYear);
303333
if (overflow === 'constrain') month = ES.ConstrainToRange(month, 1, monthsPerYear);
304334
monthCode = CreateMonthCode(month, false);
305335
} else {
306-
const { monthNumber, isLeapMonth } = ParseMonthCode(monthCode);
307-
if (isLeapMonth) {
308-
throw new RangeErrorCtor(`Invalid monthCode: ${monthCode}. Leap months do not exist in this calendar`);
309-
}
310-
if (monthCode !== CreateMonthCode(monthNumber, false)) {
311-
throw new RangeErrorCtor(`Invalid month code: ${monthCode}`);
336+
if (!IsValidMonthCodeForCalendar(calendar, monthCode)) {
337+
throw new RangeErrorCtor(`Invalid monthCode: ${monthCode} does not exist in calendar ${calendar}`);
312338
}
339+
const { monthNumber } = ParseMonthCode(monthCode);
313340
if (month !== undefined && month !== monthNumber) {
314341
throw new RangeErrorCtor(`monthCode ${monthCode} and month ${month} must match if both are present`);
315342
}
316343
month = monthNumber;
317-
if (month < 1 || month > monthsPerYear) throw new RangeErrorCtor(`Invalid monthCode: ${monthCode}`);
318344
}
319345
return { ...calendarDate, month, monthCode };
320346
}
@@ -752,9 +778,8 @@ const nonIsoHelperBase = {
752778
adjustCalendarDate(calendarDate, cache, overflow /*, fromLegacyDate = false */) {
753779
if (this.calendarType === 'lunisolar') throw new RangeErrorCtor('Override required for lunisolar calendars');
754780
this.validateCalendarDate(calendarDate);
755-
const largestMonth = this.monthsInYear(calendarDate, cache);
756781
let { month, monthCode } = calendarDate;
757-
({ month, monthCode } = resolveNonLunisolarMonth(calendarDate, overflow, largestMonth));
782+
({ month, monthCode } = resolveNonLunisolarMonth(calendarDate, this.id, overflow));
758783
calendarDate = { ...calendarDate, month, monthCode };
759784
if (CalendarSupportsEra(this.id)) calendarDate = this.completeEraYear(calendarDate);
760785
return calendarDate;
@@ -1994,9 +2019,7 @@ const nonIsoGeneralImpl = {
19942019
},
19952020
resolveFields(fields /* , type */) {
19962021
if (this.helper.calendarType !== 'lunisolar') {
1997-
const cache = new OneObjectCache();
1998-
const largestMonth = this.helper.monthsInYear(fields, cache);
1999-
resolveNonLunisolarMonth(fields, undefined, largestMonth);
2022+
resolveNonLunisolarMonth(fields, this.helper.id);
20002023
}
20012024
},
20022025
dateToISO(fields, overflow) {

0 commit comments

Comments
 (0)