diff --git a/docs/monthday.md b/docs/monthday.md index 776ab1dac9..ef47133fbb 100644 --- a/docs/monthday.md +++ b/docs/monthday.md @@ -13,20 +13,22 @@ A `Temporal.MonthDay` can be converted into a `Temporal.Date` by combining it wi ## Constructor -### **new Temporal.MonthDay**(_isoMonth_: number, _isoDay_: number) : Temporal.MonthDay +### **new Temporal.MonthDay**(_isoMonth_: number, _isoDay_: number, _refIsoYear_: number = 1972) : Temporal.MonthDay **Parameters:** - `isoMonth` (number): A month, ranging between 1 and 12 inclusive. - `isoDay` (number): A day of the month, ranging between 1 and 31 inclusive. +- `refIsoYear` (optional number): A reference year, used for disambiguation when implementing other calendar systems. + This parameter can usually be omitted. **Returns:** a new `Temporal.MonthDay` object. -Use this constructor if you have the correct parameters for the date already as individual number values. +Use this constructor if you have the correct parameters for the date already as individual number values, or you are implementing a custom calendar. Otherwise, `Temporal.MonthDay.from()`, which accepts more kinds of input and allows disambiguation behaviour, is probably more convenient. All values are given as reckoned in the [ISO 8601 calendar](https://en.wikipedia.org/wiki/ISO_8601#Dates). -Together, `isoMonth` and `isoDay` must represent a valid date in at least one year of that calendar. -For example, February 29 (Leap day in the ISO 8601 calendar) is a valid value for `Temporal.MonthDay`, even though that date does not occur every year. +Together, `refIsoYear`, `isoMonth` and `isoDay` must represent a valid date in that calendar. +For example, February 29 (Leap day in the ISO 8601 calendar) is a valid value for `Temporal.MonthDay`, even though that date does not occur every year, because the default value of `refIsoYear` is 1972 which is a leap year. > **NOTE**: The `isoMonth` argument ranges from 1 to 12, which is different from legacy `Date` where months are represented by zero-based indices (0 to 11). diff --git a/docs/yearmonth.md b/docs/yearmonth.md index b67358bdf5..171b7e8fbc 100644 --- a/docs/yearmonth.md +++ b/docs/yearmonth.md @@ -13,19 +13,21 @@ A `Temporal.YearMonth` can be converted into a `Temporal.Date` by combining it w ## Constructor -### **new Temporal.YearMonth**(_isoYear_: number, _isoMonth_: number) : Temporal.YearMonth +### **new Temporal.YearMonth**(_isoYear_: number, _isoMonth_: number, _refIsoDay_: number = 1) : Temporal.YearMonth **Parameters:** - `isoYear` (number): A year. - `isoMonth` (number): A month, ranging between 1 and 12 inclusive. +- `refIsoDay` (optional number): A reference day, used for disambiguation when implementing other calendar systems. + This parameter can usually be omitted. **Returns:** a new `Temporal.YearMonth` object. -Use this constructor if you have the correct parameters already as individual number values. +Use this constructor if you have the correct parameters already as individual number values, or you are implementing a custom calendar. Otherwise, `Temporal.YearMonth.from()`, which accepts more kinds of input and allows disambiguation behaviour, is probably more convenient. All values are given as reckoned in the [ISO 8601 calendar](https://en.wikipedia.org/wiki/ISO_8601#Dates). -Together, `isoYear` and `isoMonth` must represent a valid month in that calendar. +Together, `isoYear`, `isoMonth`, and `refIsoDay` must represent a valid date in that calendar. The range of allowed values for this type is exactly enough that calling [`getYearMonth()`](./date.html#getYearMonth) on any valid `Temporal.Date` will succeed. If `isoYear` and `isoMonth` are outside of this range, then `constrain` mode will clamp the date to the limit of the allowed range. diff --git a/polyfill/lib/ecmascript.mjs b/polyfill/lib/ecmascript.mjs index 7b626af7f9..c57957930b 100644 --- a/polyfill/lib/ecmascript.mjs +++ b/polyfill/lib/ecmascript.mjs @@ -20,6 +20,8 @@ import { MILLISECOND, MICROSECOND, NANOSECOND, + REF_ISO_YEAR, + REF_ISO_DAY, YEARS, MONTHS, DAYS, @@ -52,8 +54,8 @@ export const ES = ObjectAssign({}, ES2019, { !HasSlot(item, ISO_YEAR, ISO_MONTH, ISO_DAY), IsTemporalDateTime: (item) => HasSlot(item, ISO_YEAR, ISO_MONTH, ISO_DAY, HOUR, MINUTE, SECOND, MILLISECOND, MICROSECOND, NANOSECOND), - IsTemporalYearMonth: (item) => HasSlot(item, ISO_YEAR, ISO_MONTH) && !HasSlot(item, ISO_DAY), - IsTemporalMonthDay: (item) => HasSlot(item, ISO_MONTH, ISO_DAY) && !HasSlot(item, ISO_YEAR), + IsTemporalYearMonth: (item) => HasSlot(item, ISO_YEAR, ISO_MONTH, REF_ISO_DAY), + IsTemporalMonthDay: (item) => HasSlot(item, ISO_MONTH, ISO_DAY, REF_ISO_YEAR), ToTemporalTimeZone: (item) => { if (ES.IsTemporalTimeZone(item)) return item; const TimeZone = GetIntrinsic('%Temporal.TimeZone%'); @@ -281,9 +283,10 @@ export const ES = ObjectAssign({}, ES2019, { return { hour, minute, second, millisecond, microsecond, nanosecond }; }, RegulateYearMonth: (year, month, disambiguation) => { + const refIsoDay = 1; switch (disambiguation) { case 'reject': - ES.RejectYearMonth(year, month); + ES.RejectYearMonth(year, month, refIsoDay); break; case 'constrain': ({ year, month } = ES.ConstrainYearMonth(year, month)); @@ -291,22 +294,22 @@ export const ES = ObjectAssign({}, ES2019, { case 'balance': ({ year, month } = ES.BalanceYearMonth(year, month)); // Still rejected if balanced YearMonth is outside valid range - ES.RejectYearMonth(year, month); + ES.RejectYearMonth(year, month, refIsoDay); break; } return { year, month }; }, RegulateMonthDay: (month, day, disambiguation) => { - const leapYear = 1972; + const refIsoYear = 1972; switch (disambiguation) { case 'reject': - ES.RejectDate(leapYear, month, day); + ES.RejectDate(refIsoYear, month, day); break; case 'constrain': - ({ month, day } = ES.ConstrainDate(leapYear, month, day)); + ({ month, day } = ES.ConstrainDate(refIsoYear, month, day)); break; case 'balance': - ({ month, day } = ES.BalanceDate(leapYear, month, day)); + ({ month, day } = ES.BalanceDate(refIsoYear, month, day)); break; } return { month, day }; @@ -1053,7 +1056,7 @@ export const ES = ObjectAssign({}, ES2019, { throw new RangeError('Absolute outside of supported range'); } }, - RejectYearMonth: (year, month) => { + RejectYearMonth: (year, month, refIsoDay) => { ES.RejectToRange(year, YEAR_MIN, YEAR_MAX); if (year === YEAR_MIN) { ES.RejectToRange(month, 4, 12); @@ -1062,6 +1065,7 @@ export const ES = ObjectAssign({}, ES2019, { } else { ES.RejectToRange(month, 1, 12); } + ES.RejectToRange(refIsoDay, 1, ES.DaysInMonth(year, month)); }, DifferenceDate: (smaller, larger, largestUnit = 'days') => { diff --git a/polyfill/lib/monthday.mjs b/polyfill/lib/monthday.mjs index 42e3b343ed..fb3b8d5d78 100644 --- a/polyfill/lib/monthday.mjs +++ b/polyfill/lib/monthday.mjs @@ -1,17 +1,18 @@ import { ES } from './ecmascript.mjs'; import { GetIntrinsic, MakeIntrinsicClass } from './intrinsicclass.mjs'; -import { ISO_MONTH, ISO_DAY, CreateSlots, GetSlot, SetSlot } from './slots.mjs'; +import { ISO_MONTH, ISO_DAY, REF_ISO_YEAR, CreateSlots, GetSlot, SetSlot } from './slots.mjs'; export class MonthDay { - constructor(isoMonth, isoDay) { + constructor(isoMonth, isoDay, refIsoYear = 1972) { isoMonth = ES.ToInteger(isoMonth); isoDay = ES.ToInteger(isoDay); - const leapYear = 1972; // XXX #261 leap year - ES.RejectDate(leapYear, isoMonth, isoDay); + refIsoYear = ES.ToInteger(refIsoYear); + ES.RejectDate(refIsoYear, isoMonth, isoDay); CreateSlots(this); SetSlot(this, ISO_MONTH, isoMonth); SetSlot(this, ISO_DAY, isoDay); + SetSlot(this, REF_ISO_YEAR, refIsoYear); } get month() { @@ -85,20 +86,23 @@ export class MonthDay { } static from(item, options = undefined) { const disambiguation = ES.ToTemporalDisambiguation(options); - let month, day; + let month, day, refIsoYear; if (typeof item === 'object' && item) { if (ES.IsTemporalMonthDay(item)) { month = GetSlot(item, ISO_MONTH); day = GetSlot(item, ISO_DAY); + refIsoYear = GetSlot(item, REF_ISO_YEAR); } else { // Intentionally alphabetical ({ month, day } = ES.ToRecord(item, [['day'], ['month']])); + refIsoYear = 1972; } } else { ({ month, day } = ES.ParseTemporalMonthDayString(ES.ToString(item))); + refIsoYear = 1972; } ({ month, day } = ES.RegulateMonthDay(month, day, disambiguation)); - const result = new this(month, day); + const result = new this(month, day, refIsoYear); if (!ES.IsTemporalMonthDay(result)) throw new TypeError('invalid result'); return result; } diff --git a/polyfill/lib/slots.mjs b/polyfill/lib/slots.mjs index b2869d97cb..8664885876 100644 --- a/polyfill/lib/slots.mjs +++ b/polyfill/lib/slots.mjs @@ -14,6 +14,8 @@ export const SECOND = 'slot-second'; export const MILLISECOND = 'slot-millisecond'; export const MICROSECOND = 'slot-microsecond'; export const NANOSECOND = 'slot-nanosecond'; +export const REF_ISO_YEAR = 'slot-ref-iso-year'; +export const REF_ISO_DAY = 'slot-ref-iso-day'; // Duration export const YEARS = 'slot-years'; diff --git a/polyfill/lib/yearmonth.mjs b/polyfill/lib/yearmonth.mjs index 70695b0e80..fa9470a05b 100644 --- a/polyfill/lib/yearmonth.mjs +++ b/polyfill/lib/yearmonth.mjs @@ -1,15 +1,17 @@ import { ES } from './ecmascript.mjs'; import { GetIntrinsic, MakeIntrinsicClass } from './intrinsicclass.mjs'; -import { ISO_YEAR, ISO_MONTH, CreateSlots, GetSlot, SetSlot } from './slots.mjs'; +import { ISO_YEAR, ISO_MONTH, REF_ISO_DAY, CreateSlots, GetSlot, SetSlot } from './slots.mjs'; export class YearMonth { - constructor(isoYear, isoMonth) { + constructor(isoYear, isoMonth, refIsoDay = 1) { isoYear = ES.ToInteger(isoYear); isoMonth = ES.ToInteger(isoMonth); - ES.RejectYearMonth(isoYear, isoMonth); + refIsoDay = ES.ToInteger(refIsoDay); + ES.RejectYearMonth(isoYear, isoMonth, refIsoDay); CreateSlots(this); SetSlot(this, ISO_YEAR, isoYear); SetSlot(this, ISO_MONTH, isoMonth); + SetSlot(this, REF_ISO_DAY, refIsoDay); } get year() { if (!ES.IsTemporalYearMonth(this)) throw new TypeError('invalid receiver'); @@ -154,20 +156,23 @@ export class YearMonth { } static from(item, options = undefined) { const disambiguation = ES.ToTemporalDisambiguation(options); - let year, month; + let year, month, refIsoDay; if (typeof item === 'object' && item) { if (ES.IsTemporalYearMonth(item)) { year = GetSlot(item, ISO_YEAR); month = GetSlot(item, ISO_MONTH); + refIsoDay = GetSlot(item, REF_ISO_DAY); } else { // Intentionally alphabetical ({ year, month } = ES.ToRecord(item, [['month'], ['year']])); + refIsoDay = 1; } } else { ({ year, month } = ES.ParseTemporalYearMonthString(ES.ToString(item))); + refIsoDay = 1; } ({ year, month } = ES.RegulateYearMonth(year, month, disambiguation)); - const result = new this(year, month); + const result = new this(year, month, refIsoDay); if (!ES.IsTemporalYearMonth(result)) throw new TypeError('invalid result'); return result; }