diff --git a/polyfill/lib/calendar.mjs b/polyfill/lib/calendar.mjs index b9886c535..0d9bc0892 100644 --- a/polyfill/lib/calendar.mjs +++ b/polyfill/lib/calendar.mjs @@ -29,22 +29,16 @@ import { MathFloor, MathTrunc, MathMax, - NumberIsNaN, MathSign, ObjectAssign, ObjectEntries, RegExpPrototypeExec, - RegExpPrototypeTest, SetPrototypeAdd, SetPrototypeValues, - StringPrototypeEndsWith, StringPrototypeIndexOf, StringPrototypeNormalize, - StringPrototypePadStart, StringPrototypeReplace, - StringPrototypeSlice, StringPrototypeSplit, - StringPrototypeStartsWith, StringPrototypeToLowerCase, SymbolIterator, WeakMapPrototypeGet, @@ -61,6 +55,7 @@ import Type from 'es-abstract/2024/Type.js'; import * as ES from './ecmascript.mjs'; import { DefineIntrinsic } from './intrinsicclass.mjs'; +import { CreateMonthCode, ParseMonthCode } from './monthcode.mjs'; function arrayFromSet(src) { const valuesIterator = Call(SetPrototypeValues, src, []); @@ -256,7 +251,7 @@ impl['iso8601'] = { daysInWeek: 7, monthsInYear: 12 }; - if (requestedFields.monthCode) date.monthCode = buildMonthCode(month); + if (requestedFields.monthCode) date.monthCode = CreateMonthCode(month, false); if (requestedFields.dayOfWeek) { // https://en.wikipedia.org/wiki/Determination_of_the_day_of_the_week#Disparate_variation const shiftedMonth = month + (month < 3 ? 10 : -2); @@ -294,21 +289,6 @@ impl['iso8601'] = { // proposal for ECMA-262. These calendars will be standardized as part of // ECMA-402. -function monthCodeNumberPart(monthCode) { - if (!Call(StringPrototypeStartsWith, monthCode, ['M'])) { - throw new RangeErrorCtor(`Invalid month code: ${monthCode}. Month codes must start with M.`); - } - const month = +Call(StringPrototypeSlice, monthCode, [1]); - if (NumberIsNaN(month)) throw new RangeErrorCtor(`Invalid month code: ${monthCode}`); - return month; -} - -function buildMonthCode(month, leap = false) { - const digitPart = Call(StringPrototypePadStart, `${month}`, [2, '0']); - const leapMarker = leap ? 'L' : ''; - return `M${digitPart}${leapMarker}`; -} - /** * Safely merge a month, monthCode pair into an integer month. * If both are present, make sure they match. @@ -323,16 +303,19 @@ function resolveNonLunisolarMonth(calendarDate, overflow = undefined, monthsPerY // rely on this function to constrain/reject out-of-range `month` values. if (overflow === 'reject') ES.RejectToRange(month, 1, monthsPerYear); if (overflow === 'constrain') month = ES.ConstrainToRange(month, 1, monthsPerYear); - monthCode = buildMonthCode(month); + monthCode = CreateMonthCode(month, false); } else { - const numberPart = monthCodeNumberPart(monthCode); - if (monthCode !== buildMonthCode(numberPart)) { + const { monthNumber, isLeapMonth } = ParseMonthCode(monthCode); + if (isLeapMonth) { + throw new RangeErrorCtor(`Invalid monthCode: ${monthCode}. Leap months do not exist in this calendar`); + } + if (monthCode !== CreateMonthCode(monthNumber, false)) { throw new RangeErrorCtor(`Invalid month code: ${monthCode}`); } - if (month !== undefined && month !== numberPart) { + if (month !== undefined && month !== monthNumber) { throw new RangeErrorCtor(`monthCode ${monthCode} and month ${month} must match if both are present`); } - month = numberPart; + month = monthNumber; if (month < 1 || month > monthsPerYear) throw new RangeErrorCtor(`Invalid monthCode: ${monthCode}`); } return { ...calendarDate, month, monthCode }; @@ -607,9 +590,8 @@ const nonIsoHelperBase = { `monthCode must be a string, not ${ES.Call(StringPrototypeToLowerCase, Type(monthCode), [])}` ); } - if (!ES.Call(RegExpPrototypeTest, /^M([01]?\d)(L?)$/, [monthCode])) { - throw new RangeErrorCtor(`Invalid monthCode: ${monthCode}`); - } + const { monthNumber } = ParseMonthCode(monthCode); + if (monthNumber < 1 || monthNumber > 13) throw new RangeErrorCtor(`Invalid monthCode: ${monthCode}`); } if (this.hasEra) { if ((calendarDate['era'] === undefined) !== (calendarDate['eraYear'] === undefined)) { @@ -1086,9 +1068,9 @@ const helperHebrew = ObjectAssign({}, nonIsoHelperBase, { }, getMonthCode(year, month) { if (this.inLeapYear({ year })) { - return month === 6 ? buildMonthCode(5, true) : buildMonthCode(month < 6 ? month : month - 1); + return month === 6 ? CreateMonthCode(5, true) : CreateMonthCode(month < 6 ? month : month - 1, false); } else { - return buildMonthCode(month); + return CreateMonthCode(month, false); } }, adjustCalendarDate(calendarDate, cache, overflow = 'constrain', fromLegacyDate = false) { @@ -1114,8 +1096,9 @@ const helperHebrew = ObjectAssign({}, nonIsoHelperBase, { // that all fields are present. this.validateCalendarDate(calendarDate); if (month === undefined) { - if (ES.Call(StringPrototypeEndsWith, monthCode, ['L'])) { - if (monthCode !== 'M05L') { + const { monthNumber, isLeapMonth } = ParseMonthCode(monthCode); + if (isLeapMonth) { + if (monthNumber !== 5) { throw new RangeErrorCtor(`Hebrew leap month must have monthCode M05L, not ${monthCode}`); } month = 6; @@ -1129,7 +1112,7 @@ const helperHebrew = ObjectAssign({}, nonIsoHelperBase, { } } } else { - month = monthCodeNumberPart(monthCode); + month = monthNumber; // if leap month is before this one, the month index is one more than the month code if (this.inLeapYear({ year }) && month >= 6) month++; const largestMonth = this.monthsInYear({ year }); @@ -1207,8 +1190,7 @@ const helperPersian = ObjectAssign({}, nonIsoHelperBase, { return month <= 6 ? 31 : 30; }, maxLengthOfMonthCodeInAnyYear(monthCode) { - const month = +ES.Call(StringPrototypeSlice, monthCode, [1]); - return month <= 6 ? 31 : 30; + return ParseMonthCode(monthCode).monthNumber <= 6 ? 31 : 30; }, estimateIsoDate(calendarDate) { const { year } = this.adjustCalendarDate(calendarDate); @@ -1237,8 +1219,7 @@ const helperIndian = ObjectAssign({}, nonIsoHelperBase, { return this.getMonthInfo(calendarDate).length; }, maxLengthOfMonthCodeInAnyYear(monthCode) { - const month = +ES.Call(StringPrototypeSlice, monthCode, [1]); - let monthInfo = this.months[month]; + let monthInfo = this.months[ParseMonthCode(monthCode).monthNumber]; monthInfo = monthInfo.leap ?? monthInfo; return monthInfo.length; }, @@ -1462,7 +1443,7 @@ function makeHelperGregorianFixedEpoch(id) { return this.minimumMonthLength(calendarDate); }, maxLengthOfMonthCodeInAnyYear(monthCode) { - const month = +ES.Call(StringPrototypeSlice, monthCode, [1]); + const month = ParseMonthCode(monthCode).monthNumber; return [undefined, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31][month]; }, estimateIsoDate(calendarDate) { @@ -1502,7 +1483,7 @@ const makeHelperGregorian = (id, originalEras) => { return this.minimumMonthLength(calendarDate); }, maxLengthOfMonthCodeInAnyYear(monthCode) { - const month = +ES.Call(StringPrototypeSlice, monthCode, [1]); + const month = ParseMonthCode(monthCode).monthNumber; return [undefined, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31][month]; }, /** Fill in missing parts of the (year, era, eraYear) tuple */ @@ -1581,7 +1562,7 @@ const makeHelperGregorian = (id, originalEras) => { adjustCalendarDate(calendarDate, cache, overflow /*, fromLegacyDate = false */) { // Because this is not a lunisolar calendar, it's safe to convert monthCode to a number const { month, monthCode } = calendarDate; - if (month === undefined) calendarDate = { ...calendarDate, month: monthCodeNumberPart(monthCode) }; + if (month === undefined) calendarDate = { ...calendarDate, month: ParseMonthCode(monthCode).monthNumber }; this.validateCalendarDate(calendarDate); calendarDate = this.completeEraYear(calendarDate); calendarDate = Call(nonIsoHelperBase.adjustCalendarDate, this, [calendarDate, cache, overflow]); @@ -1611,7 +1592,7 @@ const makeHelperSameMonthDayAsGregorian = (id, originalEras) => { // Month and day are same as ISO, so bypass Intl.DateTimeFormat and // calculate the year, era, and eraYear here. const { year: isoYear, month, day } = isoDate; - const monthCode = buildMonthCode(month); + const monthCode = CreateMonthCode(month, false); const year = isoYear - this.anchorEra.isoEpoch.year + 1; return this.completeEraYear({ year, month, monthCode, day }); } @@ -1893,7 +1874,7 @@ const helperChinese = ObjectAssign({}, nonIsoHelperBase, { // "bis" suffix used only by the Chinese/Dangi calendar to indicate a leap // month. Below we'll normalize the output. if (monthExtra && monthExtra !== 'bis') throw new RangeErrorCtor(`Unexpected leap month suffix: ${monthExtra}`); - const monthCode = buildMonthCode(month, monthExtra !== undefined); + const monthCode = CreateMonthCode(month, monthExtra !== undefined); const monthString = `${month}${monthExtra || ''}`; const months = this.getMonthList(year, cache); const monthInfo = months[monthString]; @@ -1906,23 +1887,17 @@ const helperChinese = ObjectAssign({}, nonIsoHelperBase, { this.validateCalendarDate(calendarDate); if (month === undefined) { const months = this.getMonthList(year, cache); - let numberPart = ES.Call(StringPrototypeReplace, monthCode, [/^M|L$/g, (ch) => (ch === 'L' ? 'bis' : '')]); - if (numberPart[0] === '0') numberPart = ES.Call(StringPrototypeSlice, numberPart, [1]); + const { monthNumber, isLeapMonth } = ParseMonthCode(monthCode); + const numberPart = `${monthNumber}${isLeapMonth ? 'bis' : ''}`; let monthInfo = months[numberPart]; month = monthInfo && monthInfo.monthIndex; // If this leap month isn't present in this year, constrain to the same // day of the previous month. - if ( - month === undefined && - ES.Call(StringPrototypeEndsWith, monthCode, ['L']) && - monthCode != 'M13L' && - overflow === 'constrain' - ) { - const withoutML = ES.Call(StringPrototypeReplace, monthCode, [/^M0?|L$/g, '']); - monthInfo = months[withoutML]; + if (month === undefined && isLeapMonth && monthNumber !== 13 && overflow === 'constrain') { + monthInfo = months[monthNumber]; if (monthInfo) { month = monthInfo.monthIndex; - monthCode = buildMonthCode(withoutML); + monthCode = CreateMonthCode(monthNumber, false); } } if (month === undefined) { @@ -1945,15 +1920,15 @@ const helperChinese = ObjectAssign({}, nonIsoHelperBase, { if (matchingMonthEntry === undefined) { throw new RangeErrorCtor(`Invalid month ${month} in Chinese year ${year}`); } - monthCode = buildMonthCode( + monthCode = CreateMonthCode( ES.Call(StringPrototypeReplace, matchingMonthEntry[0], ['bis', '']), ES.Call(StringPrototypeIndexOf, matchingMonthEntry[0], ['bis']) !== -1 ); } else { // Both month and monthCode are present. Make sure they don't conflict. const months = this.getMonthList(year, cache); - let numberPart = ES.Call(StringPrototypeReplace, monthCode, [/^M|L$/g, (ch) => (ch === 'L' ? 'bis' : '')]); - if (numberPart[0] === '0') numberPart = ES.Call(StringPrototypeSlice, numberPart, [1]); + const { monthNumber, isLeapMonth } = ParseMonthCode(monthCode); + const numberPart = `${monthNumber}${isLeapMonth ? 'bis' : ''}`; const monthInfo = months[numberPart]; if (!monthInfo) throw new RangeErrorCtor(`Unmatched monthCode ${monthCode} in Chinese year ${year}`); if (month !== monthInfo.monthIndex) { diff --git a/polyfill/lib/ecmascript.mjs b/polyfill/lib/ecmascript.mjs index ad191df34..b0ebe3b09 100644 --- a/polyfill/lib/ecmascript.mjs +++ b/polyfill/lib/ecmascript.mjs @@ -67,7 +67,6 @@ import { SetPrototypeHas, StringFromCharCode, StringPrototypeCharCodeAt, - StringPrototypeIndexOf, StringPrototypeMatch, StringPrototypePadStart, StringPrototypeReplace, @@ -96,6 +95,7 @@ import { GetUnsignedRoundingMode, TruncatingDivModByPowerOf10 } from './math.mjs'; +import { CreateMonthCode, ParseMonthCode } from './monthcode.mjs'; import { TimeDuration } from './timeduration.mjs'; import { CreateSlots, @@ -235,21 +235,9 @@ export function RequireString(value) { return value; } -function ToSyntacticallyValidMonthCode(value) { - value = ToPrimitive(value, StringCtor); - RequireString(value); - if ( - value.length < 3 || - value.length > 4 || - value[0] !== 'M' || - Call(StringPrototypeIndexOf, '0123456789', [value[1]]) === -1 || - Call(StringPrototypeIndexOf, '0123456789', [value[2]]) === -1 || - (value[1] + value[2] === '00' && value[3] !== 'L') || - (value[3] !== 'L' && value[3] !== undefined) - ) { - throw new RangeError(`bad month code ${value}; must match M01-M99 or M00L-M99L`); - } - return value; +function ToMonthCode(value) { + const { monthNumber, isLeapMonth } = ParseMonthCode(value); + return CreateMonthCode(monthNumber, isLeapMonth); } function ToOffsetString(value) { @@ -281,7 +269,7 @@ const BUILTIN_CASTS = new MapCtor([ ['eraYear', ToIntegerWithTruncation], ['year', ToIntegerWithTruncation], ['month', ToPositiveIntegerWithTruncation], - ['monthCode', ToSyntacticallyValidMonthCode], + ['monthCode', ToMonthCode], ['day', ToPositiveIntegerWithTruncation], ['hour', ToIntegerWithTruncation], ['minute', ToIntegerWithTruncation], diff --git a/polyfill/lib/monthcode.mjs b/polyfill/lib/monthcode.mjs new file mode 100644 index 000000000..e426593fd --- /dev/null +++ b/polyfill/lib/monthcode.mjs @@ -0,0 +1,28 @@ +import { + String as StringCtor, + RangeError as RangeErrorCtor, + TypeError as TypeErrorCtor, + StringPrototypePadStart, + RegExpPrototypeExec +} from './primordials.mjs'; + +import Call from 'es-abstract/2024/Call.js'; +import ToPrimitive from 'es-abstract/2024/ToPrimitive.js'; + +import { monthCode as MONTH_CODE_REGEX } from './regex.mjs'; + +export function ParseMonthCode(argument) { + const value = ToPrimitive(argument, StringCtor); + if (typeof value !== 'string') throw new TypeErrorCtor('month code must be a string'); + const match = Call(RegExpPrototypeExec, MONTH_CODE_REGEX, [value]); + if (!match) throw new RangeErrorCtor(`bad month code ${value}; must match M01-M99 or M00L-M99L`); + return { + monthNumber: +(match[1] ?? match[3] ?? match[5]), + isLeapMonth: (match[2] ?? match[4] ?? match[6]) === 'L' + }; +} + +export function CreateMonthCode(monthNumber, isLeapMonth) { + const numberPart = Call(StringPrototypePadStart, `${monthNumber}`, [2, '0']); + return isLeapMonth ? `M${numberPart}L` : `M${numberPart}`; +} diff --git a/polyfill/lib/regex.mjs b/polyfill/lib/regex.mjs index 8e35329aa..7f721be28 100644 --- a/polyfill/lib/regex.mjs +++ b/polyfill/lib/regex.mjs @@ -69,3 +69,5 @@ const fraction = /(\d+)(?:[.,](\d{1,9}))?/; const durationDate = /(?:(\d+)Y)?(?:(\d+)M)?(?:(\d+)W)?(?:(\d+)D)?/; const durationTime = new RegExpCtor(`(?:${fraction.source}H)?(?:${fraction.source}M)?(?:${fraction.source}S)?`); export const duration = new RegExpCtor(`^([+-])?P${durationDate.source}(?:T(?!$)${durationTime.source})?$`, 'i'); + +export const monthCode = /^M(?:(00)(L)|(0[1-9])(L)?|([1-9][0-9])(L)?)$/; diff --git a/spec/abstractops.html b/spec/abstractops.html index 226198acf..154c7c467 100644 --- a/spec/abstractops.html +++ b/spec/abstractops.html @@ -1774,35 +1774,6 @@

- -

- ToMonthCode ( - _argument_: an ECMAScript language value, - ): either a normal completion containing a String or a throw completion -

-
-
description
-
- It converts _argument_ to a String, or throws a *TypeError* if that is not possible. - It also requires that the String is a syntactically valid month code, or throws a *RangeError* if it is not. - The month code is not guaranteed to be correct in the context of any particular calendar; for example, some calendars do not have leap months. -
-
- - 1. Let _monthCode_ be ? ToPrimitive(_argument_, ~string~). - 1. If _monthCode_ is not a String, throw a *TypeError* exception. - 1. If the length of _monthCode_ is not 3 or 4, throw a *RangeError* exception. - 1. If the first code unit of _monthCode_ is not 0x004D (LATIN CAPITAL LETTER M), throw a *RangeError* exception. - 1. If the second code unit of _monthCode_ is not in the inclusive interval from 0x0030 (DIGIT ZERO) to 0x0039 (DIGIT NINE), throw a *RangeError* exception. - 1. If the third code unit of _monthCode_ is not in the inclusive interval from 0x0030 (DIGIT ZERO) to 0x0039 (DIGIT NINE), throw a *RangeError* exception. - 1. If the length of _monthCode_ is 4 and the fourth code unit of _monthCode_ is not 0x004C (LATIN CAPITAL LETTER L), throw a *RangeError* exception. - 1. Let _monthCodeDigits_ be the substring of monthCode from 1 to 3. - 1. Let _monthCodeInteger_ be ℝ(StringToNumber(_monthCodeDigits_)). - 1. If _monthCodeInteger_ is 0 and the length of _monthCode_ is not 4, throw a *RangeError* exception. - 1. Return _monthCode_. - -
-

ToOffsetString ( diff --git a/spec/calendar.html b/spec/calendar.html index 702fc4ef9..dd864f250 100644 --- a/spec/calendar.html +++ b/spec/calendar.html @@ -43,6 +43,88 @@

AvailableCalendars ( ): a List of calendar types

+ +

Month Codes

+ +

+ Lunisolar calendars may insert leap months into certain years, in order to reconcile the discrepancy between lunar cycles and the solar year. + For this reason, a particular month may not have the same ordinal number every year if a leap month is inserted before it. +

+

+ A month code is a String that refers uniquely to a particular month, even one that is not present every year. + Month codes are lexicographically ordered according to the notional order of months in the year, even though not all may be present in any given year. + They conform to the following string format: +

+

+ The month code for a month that is not a leap month and whose 1-based ordinal position in a common year of the calendar (i.e., a year that is not a leap year) is _n_ is the string-concatenation of *"M"* and ToZeroPaddedDecimalString(_n_, 2). + The month code for a leap month inserted after a month whose 1-based ordinal position in a common year of the calendar is _p_, is the string-concatenation of *"M"*, ToZeroPaddedDecimalString(_p_, 2), and *"L"*. +

+

The month codes in the ISO 8601 calendar, which does not have leap months, are *"M01"* for January through *"M12"* for December.

+ + For example, in the Hebrew calendar, the month code of Adar (and Adar II, in leap years) is *"M06"* and the month code of Adar I (the leap month inserted before Adar II) is *"M05L"*. + Theoretically, in a calendar with a leap month at the start of some years, the month code of that month would be *"M00L"*. + + + +

+ ParseMonthCode ( + _argument_: an ECMAScript language value, + ): either a normal completion containing a Record with fields [[MonthNumber]] (an integer) and [[IsLeapMonth]] (a Boolean) or a throw completion +

+
+
description
+
+ It converts _argument_ to a month code and parses it into its parts, or throws a *TypeError* if conversion to String fails, or throws a *RangeError* if the result is not a syntactically valid month code. + The month code is not guaranteed to be correct in the context of any particular calendar; for example, some calendars do not have leap months. +
+
+

It performs the following steps when called:

+ + 1. Let _monthCode_ be ? ToPrimitive(_argument_, ~string~). + 1. If _monthCode_ is not a String, throw a *TypeError* exception. + 1. If ParseText(StringToCodePoints(_monthCode_), |MonthCode|) is a List of errors, throw a *RangeError* exception. + 1. Let _isLeapMonth_ be *false*. + 1. If the length of _monthCode_ is 4, then + 1. Assert: The fourth code unit of _monthCode_ is 0x004C (LATIN CAPITAL LETTER L). + 1. Set _isLeapMonth_ to *true*. + 1. Let _monthCodeDigits_ be the substring of _monthCode_ from 1 to 3. + 1. Let _monthNumber_ be ℝ(StringToNumber(_monthCodeDigits_)). + 1. If _monthNumber_ is 0 and _isLeapMonth_ is *false*, throw a *RangeError* exception. + 1. Return the Record { [[MonthNumber]]: _monthNumber_, [[IsLeapMonth]]: _isLeapMonth_ }. + + + MonthCode ::: + `M00L` + `M0` NonZeroDigit `L`? + `M` NonZeroDigit DecimalDigit `L`? + +
+ + +

+ CreateMonthCode ( + _monthNumber_: an integer in the inclusive interval from 0 to 99, + _isLeapMonth_: a Boolean, + ): a month code +

+
+
description
+
+ It creates a month code with the given month number and leap month flag. +
+
+

It performs the following steps when called:

+ + 1. Assert: If _isLeapMonth_ is *false*, _monthNumber_ > 0. + 1. Let _numberPart_ be ToZeroPaddedDecimalString(_monthNumber_, 2). + 1. If _isLeapMonth_ is *true*, then + 1. Return the string-concatenation of the code unit 0x004D (LATIN CAPITAL LETTER M), _numberPart_, and the code unit 0x004C (LATIN CAPITAL LETTER L). + 1. Else, + 1. Return the string-concatenation of the code unit 0x004D (LATIN CAPITAL LETTER M) and _numberPart_. + +
+
+

Abstract Operations

@@ -97,12 +179,9 @@

Calendar Date Records

[[MonthCode]] - a String + a month code - The month code of the date's month. The month code for a month that is not a leap month and whose 1-based ordinal position in a common year of the calendar (i.e., a year that is not a leap year) is _n_ should be the string-concatenation of *"M"* and ToZeroPaddedDecimalString(_n_, 2), and the month code for a month that is a leap month inserted after a month whose 1-based ordinal position in a common year of the calendar is _p_ should be the string-concatenation of *"M"*, ToZeroPaddedDecimalString(_p_, 2), and *"L"*. - - For example, in the Hebrew calendar, the month code of Adar (and Adar II, in leap years) is *"M06"* and the month code of Adar I (the leap month inserted before Adar II) is *"M05L"*. Theoretically, in a calendar with a leap month at the start of some years, the month code of that month would be *"M00L"*. - + The month code of the date's month. @@ -248,7 +327,7 @@

Calendar Fields Records

[[MonthCode]] - a String or ~unset~ + a month code or ~unset~ ~unset~ *"monthCode"* ~month-code~ @@ -405,7 +484,8 @@

1. Else if _Conversion_ is ~to-temporal-time-zone-identifier~, then 1. Set _value_ to ? ToTemporalTimeZoneIdentifier(_value_). 1. Else if _Conversion_ is ~to-month-code~, then - 1. Set _value_ to ? ToMonthCode(_value_). + 1. Let _parsed_ be ? ParseMonthCode(_value_). + 1. Set _value_ to CreateMonthCode(_parsed_.[[MonthNumber]], _parsed_.[[IsLeapMonth]]). 1. Else, 1. Assert: _Conversion_ is ~to-offset-string~. 1. Set _value_ to ? ToOffsetString(_value_). @@ -973,15 +1053,13 @@

1. If _calendar_ is *"iso8601"*, then - 1. Let _monthNumberPart_ be ToZeroPaddedDecimalString(_isoDate_.[[Month]], 2). - 1. Let _monthCode_ be the string-concatenation of *"M"* and _monthNumberPart_. 1. If MathematicalInLeapYear(EpochTimeForYear(_isoDate_.[[Year]])) = 1, let _inLeapYear_ be *true*; else let _inLeapYear_ be *false*. 1. Return Calendar Date Record { [[Era]]: *undefined*, [[EraYear]]: *undefined*, [[Year]]: _isoDate_.[[Year]], [[Month]]: _isoDate_.[[Month]], - [[MonthCode]]: _monthCode_, + [[MonthCode]]: CreateMonthCode(_isoDate_.[[Month]], *false*), [[Day]]: _isoDate_.[[Day]], [[DayOfWeek]]: ISODayOfWeek(_isoDate_), [[DayOfYear]]: ISODayOfYear(_isoDate_), @@ -1174,14 +1252,11 @@

1. If _month_ is ~unset~, throw a *TypeError* exception. 1. Return ~unused~. 1. Assert: _monthCode_ is a String. - 1. NOTE: The ISO 8601 calendar does not include leap months. - 1. If the length of _monthCode_ is not 3, throw a *RangeError* exception. - 1. If the first code unit of _monthCode_ is not 0x004D (LATIN CAPITAL LETTER M), throw a *RangeError* exception. - 1. Let _monthCodeDigits_ be the substring of _monthCode_ from 1. - 1. If ParseText(StringToCodePoints(_monthCodeDigits_), |DateMonth|) is a List of errors, throw a *RangeError* exception. - 1. Let _monthCodeInteger_ be ℝ(StringToNumber(_monthCodeDigits_)). - 1. If _month_ is not ~unset~ and _month_ ≠ _monthCodeInteger_, throw a *RangeError* exception. - 1. Set _fields_.[[Month]] to _monthCodeInteger_. + 1. Let _parsedMonthCode_ be ? ParseMonthCode(_monthCode_). + 1. If _parsedMonthCode_.[[IsLeapMonth]] is *true*, throw a *RangeError* exception. + 1. If _parsedMonthCode_.[[MonthNumber]] > 12, throw a *RangeError* exception. + 1. If _month_ is not ~unset~ and _month_ ≠ _parsedMonthCode_.[[MonthNumber]], throw a *RangeError* exception. + 1. Set _fields_.[[Month]] to _parsedMonthCode_.[[MonthNumber]]. 1. Else, 1. Perform ? NonISOResolveFields(_calendar_, _fields_, _type_). 1. Return ~unused~.