From 9dd2a42b37752112524abe167fc14ce245bf3ec6 Mon Sep 17 00:00:00 2001 From: Philip Chimento Date: Mon, 8 Jun 2020 15:42:49 -0700 Subject: [PATCH] Separate regulating invalid values from regulating allowed range In order for calendars to work properly, we need to split the Regulate operations into two parts: one to reject or constrain individual values (such as days == 32), which is calendar-sensitive, and one to reject or constrain the entire range of the type (such as -271821-04-18), which is only calendar-sensitive for Date and YearMonth, not for DateTime. --- polyfill/lib/absolute.mjs | 14 ++--- polyfill/lib/date.mjs | 7 ++- polyfill/lib/datetime.mjs | 29 ++++++++- polyfill/lib/ecmascript.mjs | 119 +++++++++++++++++++++++++----------- polyfill/lib/monthday.mjs | 1 + polyfill/lib/yearmonth.mjs | 9 ++- 6 files changed, 128 insertions(+), 51 deletions(-) diff --git a/polyfill/lib/absolute.mjs b/polyfill/lib/absolute.mjs index 63c8a75161..79e09b014a 100644 --- a/polyfill/lib/absolute.mjs +++ b/polyfill/lib/absolute.mjs @@ -7,7 +7,7 @@ import bigInt from 'big-integer'; export class Absolute { constructor(epochNanoseconds) { const ns = ES.ToBigInt(epochNanoseconds); - ES.RejectAbsolute(ns); + ES.RejectAbsoluteRange(ns); CreateSlots(this); SetSlot(this, EPOCHNANOSECONDS, ns); } @@ -54,7 +54,7 @@ export class Absolute { add = add.plus(bigInt(days).multiply(24 * 60 * 60 * 1e9)); const ns = bigInt(GetSlot(this, EPOCHNANOSECONDS)).plus(add); - ES.RejectAbsolute(ns); + ES.RejectAbsoluteRange(ns); const Construct = ES.SpeciesConstructor(this, Absolute); const result = new Construct(bigIntIfAvailable(ns)); @@ -83,7 +83,7 @@ export class Absolute { add = add.plus(bigInt(days).multiply(24 * 60 * 60 * 1e9)); const ns = bigInt(GetSlot(this, EPOCHNANOSECONDS)).minus(add); - ES.RejectAbsolute(ns); + ES.RejectAbsoluteRange(ns); const Construct = ES.SpeciesConstructor(this, Absolute); const result = new Construct(bigIntIfAvailable(ns)); @@ -155,7 +155,7 @@ export class Absolute { static fromEpochSeconds(epochSeconds) { epochSeconds = ES.ToNumber(epochSeconds); const epochNanoseconds = bigInt(epochSeconds).multiply(1e9); - ES.RejectAbsolute(epochNanoseconds); + ES.RejectAbsoluteRange(epochNanoseconds); const result = new this(bigIntIfAvailable(epochNanoseconds)); if (!ES.IsTemporalAbsolute(result)) throw new TypeError('invalid result'); return result; @@ -163,7 +163,7 @@ export class Absolute { static fromEpochMilliseconds(epochMilliseconds) { epochMilliseconds = ES.ToNumber(epochMilliseconds); const epochNanoseconds = bigInt(epochMilliseconds).multiply(1e6); - ES.RejectAbsolute(epochNanoseconds); + ES.RejectAbsoluteRange(epochNanoseconds); const result = new this(bigIntIfAvailable(epochNanoseconds)); if (!ES.IsTemporalAbsolute(result)) throw new TypeError('invalid result'); return result; @@ -171,14 +171,14 @@ export class Absolute { static fromEpochMicroseconds(epochMicroseconds) { epochMicroseconds = ES.ToBigInt(epochMicroseconds); const epochNanoseconds = epochMicroseconds.multiply(1e3); - ES.RejectAbsolute(epochNanoseconds); + ES.RejectAbsoluteRange(epochNanoseconds); const result = new this(bigIntIfAvailable(epochNanoseconds)); if (!ES.IsTemporalAbsolute(result)) throw new TypeError('invalid result'); return result; } static fromEpochNanoseconds(epochNanoseconds) { epochNanoseconds = ES.ToBigInt(epochNanoseconds); - ES.RejectAbsolute(epochNanoseconds); + ES.RejectAbsoluteRange(epochNanoseconds); const result = new this(bigIntIfAvailable(epochNanoseconds)); if (!ES.IsTemporalAbsolute(result)) throw new TypeError('invalid result'); return result; diff --git a/polyfill/lib/date.mjs b/polyfill/lib/date.mjs index 3d4aeba612..c11a206034 100644 --- a/polyfill/lib/date.mjs +++ b/polyfill/lib/date.mjs @@ -21,6 +21,7 @@ export class Date { isoMonth = ES.ToInteger(isoMonth); isoDay = ES.ToInteger(isoDay); ES.RejectDate(isoYear, isoMonth, isoDay); + ES.RejectDateRange(isoYear, isoMonth, isoDay); CreateSlots(this); SetSlot(this, ISO_YEAR, isoYear); SetSlot(this, ISO_MONTH, isoMonth); @@ -71,6 +72,7 @@ export class Date { } let { year = GetSlot(this, ISO_YEAR), month = GetSlot(this, ISO_MONTH), day = GetSlot(this, ISO_DAY) } = props; ({ year, month, day } = ES.RegulateDate(year, month, day, disambiguation)); + ({ year, month, day } = ES.RegulateDateRange(year, month, day, disambiguation)); const Construct = ES.SpeciesConstructor(this, Date); const result = new Construct(year, month, day); if (!ES.IsTemporalDate(result)) throw new TypeError('invalid result'); @@ -93,7 +95,7 @@ export class Date { 'days' ); ({ year, month, day } = ES.AddDate(year, month, day, years, months, weeks, days, disambiguation)); - ({ year, month, day } = ES.RegulateDate(year, month, day, disambiguation)); + ({ year, month, day } = ES.RegulateDateRange(year, month, day, disambiguation)); const Construct = ES.SpeciesConstructor(this, Date); const result = new Construct(year, month, day); if (!ES.IsTemporalDate(result)) throw new TypeError('invalid result'); @@ -116,7 +118,7 @@ export class Date { 'days' ); ({ year, month, day } = ES.SubtractDate(year, month, day, years, months, weeks, days, disambiguation)); - ({ year, month, day } = ES.RegulateDate(year, month, day, disambiguation)); + ({ year, month, day } = ES.RegulateDateRange(year, month, day, disambiguation)); const Construct = ES.SpeciesConstructor(this, Date); const result = new Construct(year, month, day); if (!ES.IsTemporalDate(result)) throw new TypeError('invalid result'); @@ -210,6 +212,7 @@ export class Date { ({ year, month, day } = ES.ParseTemporalDateString(ES.ToString(item))); } ({ year, month, day } = ES.RegulateDate(year, month, day, disambiguation)); + ({ year, month, day } = ES.RegulateDateRange(year, month, day, disambiguation)); const result = new this(year, month, day); if (!ES.IsTemporalDate(result)) throw new TypeError('invalid result'); return result; diff --git a/polyfill/lib/datetime.mjs b/polyfill/lib/datetime.mjs index 15992b89fa..0165687946 100644 --- a/polyfill/lib/datetime.mjs +++ b/polyfill/lib/datetime.mjs @@ -38,6 +38,7 @@ export class DateTime { microsecond = ES.ToInteger(microsecond); nanosecond = ES.ToInteger(nanosecond); ES.RejectDateTime(isoYear, isoMonth, isoDay, hour, minute, second, millisecond, microsecond, nanosecond); + ES.RejectDateTimeRange(isoYear, isoMonth, isoDay, hour, minute, second, millisecond, microsecond, nanosecond); CreateSlots(this); SetSlot(this, ISO_YEAR, isoYear); SetSlot(this, ISO_MONTH, isoMonth); @@ -149,6 +150,18 @@ export class DateTime { nanosecond, disambiguation )); + ({ year, month, day, hour, minute, second, millisecond, microsecond, nanosecond } = ES.RegulateDateTimeRange( + year, + month, + day, + hour, + minute, + second, + millisecond, + microsecond, + nanosecond, + disambiguation + )); const Construct = ES.SpeciesConstructor(this, DateTime); const result = new Construct(year, month, day, hour, minute, second, millisecond, microsecond, nanosecond); if (!ES.IsTemporalDateTime(result)) throw new TypeError('invalid result'); @@ -178,7 +191,7 @@ export class DateTime { )); day += deltaDays; ({ year, month, day } = ES.BalanceDate(year, month, day)); - ({ year, month, day, hour, minute, second, millisecond, microsecond, nanosecond } = ES.RegulateDateTime( + ({ year, month, day, hour, minute, second, millisecond, microsecond, nanosecond } = ES.RegulateDateTimeRange( year, month, day, @@ -218,7 +231,7 @@ export class DateTime { )); days -= deltaDays; ({ year, month, day } = ES.SubtractDate(year, month, day, years, months, weeks, days, disambiguation)); - ({ year, month, day, hour, minute, second, millisecond, microsecond, nanosecond } = ES.RegulateDateTime( + ({ year, month, day, hour, minute, second, millisecond, microsecond, nanosecond } = ES.RegulateDateTimeRange( year, month, day, @@ -420,6 +433,18 @@ export class DateTime { nanosecond, disambiguation )); + ({ year, month, day, hour, minute, second, millisecond, microsecond, nanosecond } = ES.RegulateDateTimeRange( + year, + month, + day, + hour, + minute, + second, + millisecond, + microsecond, + nanosecond, + disambiguation + )); const result = new this(year, month, day, hour, minute, second, millisecond, microsecond, nanosecond); if (!ES.IsTemporalDateTime(result)) throw new TypeError('invalid result'); return result; diff --git a/polyfill/lib/ecmascript.mjs b/polyfill/lib/ecmascript.mjs index 2d85729887..6c87e0fb3f 100644 --- a/polyfill/lib/ecmascript.mjs +++ b/polyfill/lib/ecmascript.mjs @@ -237,6 +237,38 @@ export const ES = ObjectAssign({}, ES2019, { } return { year, month, day, hour, minute, second, millisecond, microsecond, nanosecond }; }, + RegulateDateTimeRange: ( + year, + month, + day, + hour, + minute, + second, + millisecond, + microsecond, + nanosecond, + disambiguation + ) => { + switch (disambiguation) { + case 'reject': + ES.RejectDateTimeRange(year, month, day, hour, minute, second, millisecond, microsecond, nanosecond); + break; + case 'constrain': + ({ year, month, day, hour, minute, second, millisecond, microsecond, nanosecond } = ES.ConstrainDateTimeRange( + year, + month, + day, + hour, + minute, + second, + millisecond, + microsecond, + nanosecond + )); + break; + } + return { year, month, day, hour, minute, second, millisecond, microsecond, nanosecond }; + }, RegulateDate: (year, month, day, disambiguation) => { switch (disambiguation) { case 'reject': @@ -248,6 +280,17 @@ export const ES = ObjectAssign({}, ES2019, { } return { year, month, day }; }, + RegulateDateRange: (year, month, day, disambiguation) => { + switch (disambiguation) { + case 'reject': + ES.RejectDateRange(year, month, day); + break; + case 'constrain': + ({ year, month, day } = ES.ConstrainDateRange(year, month, day)); + break; + } + return { year, month, day }; + }, RegulateTime: (hour, minute, second, millisecond, microsecond, nanosecond, disambiguation) => { switch (disambiguation) { case 'reject': @@ -270,10 +313,22 @@ export const ES = ObjectAssign({}, ES2019, { const refISODay = 1; switch (disambiguation) { case 'reject': - ES.RejectYearMonth(year, month, refISODay); + ES.RejectDate(year, month, refISODay); + break; + case 'constrain': + ({ year, month } = ES.ConstrainDate(year, month)); + break; + } + return { year, month }; + }, + RegulateYearMonthRange: (year, month, disambiguation) => { + const refISODay = 1; + switch (disambiguation) { + case 'reject': + ES.RejectYearMonthRange(year, month, refISODay); break; case 'constrain': - ({ year, month } = ES.ConstrainYearMonth(year, month)); + ({ year, month } = ES.ConstrainYearMonthRange(year, month)); break; } return { year, month }; @@ -943,8 +998,13 @@ export const ES = ObjectAssign({}, ES2019, { ConstrainToRange: (value, min, max) => Math.min(max, Math.max(min, value)), ConstrainDate: (year, month, day) => { + month = ES.ConstrainToRange(month, 1, 12); + day = ES.ConstrainToRange(day, 1, ES.DaysInMonth(year, month)); + return { year, month, day }; + }, + ConstrainDateRange: (year, month, day) => { // Noon avoids trouble at edges of DateTime range (excludes midnight) - ({ year, month, day } = ES.ConstrainDateTime(year, month, day, 12, 0, 0, 0, 0, 0)); + ({ year, month, day } = ES.ConstrainDateTimeRange(year, month, day, 12, 0, 0, 0, 0, 0)); return { year, month, day }; }, ConstrainTime: (hour, minute, second, millisecond, microsecond, nanosecond) => { @@ -957,9 +1017,7 @@ export const ES = ObjectAssign({}, ES2019, { return { hour, minute, second, millisecond, microsecond, nanosecond }; }, ConstrainDateTime: (year, month, day, hour, minute, second, millisecond, microsecond, nanosecond) => { - year = ES.ConstrainToRange(year, YEAR_MIN, YEAR_MAX); - month = ES.ConstrainToRange(month, 1, 12); - day = ES.ConstrainToRange(day, 1, ES.DaysInMonth(year, month)); + ({ year, month, day } = ES.ConstrainDate(year, month, day)); ({ hour, minute, second, millisecond, microsecond, nanosecond } = ES.ConstrainTime( hour, minute, @@ -968,6 +1026,10 @@ export const ES = ObjectAssign({}, ES2019, { microsecond, nanosecond )); + return { year, month, day, hour, minute, second, millisecond, microsecond, nanosecond }; + }, + ConstrainDateTimeRange: (year, month, day, hour, minute, second, millisecond, microsecond, nanosecond) => { + year = ES.ConstrainToRange(year, YEAR_MIN, YEAR_MAX); // Constrain to within 24 hours outside the Absolute range if ( year === YEAR_MIN && @@ -991,14 +1053,12 @@ export const ES = ObjectAssign({}, ES2019, { } return { year, month, day, hour, minute, second, millisecond, microsecond, nanosecond }; }, - ConstrainYearMonth: (year, month) => { + ConstrainYearMonthRange: (year, month) => { year = ES.ConstrainToRange(year, YEAR_MIN, YEAR_MAX); if (year === YEAR_MIN) { month = ES.ConstrainToRange(month, 4, 12); } else if (year === YEAR_MAX) { month = ES.ConstrainToRange(month, 1, 9); - } else { - month = ES.ConstrainToRange(month, 1, 12); } return { year, month }; }, @@ -1007,8 +1067,12 @@ export const ES = ObjectAssign({}, ES2019, { if (value < min || value > max) throw new RangeError(`value out of range: ${min} <= ${value} <= ${max}`); }, RejectDate: (year, month, day) => { + ES.RejectToRange(month, 1, 12); + ES.RejectToRange(day, 1, ES.DaysInMonth(year, month)); + }, + RejectDateRange: (year, month, day) => { // Noon avoids trouble at edges of DateTime range (excludes midnight) - ES.RejectDateTime(year, month, day, 12, 0, 0, 0, 0, 0); + ES.RejectDateTimeRange(year, month, day, 12, 0, 0, 0, 0, 0); }, RejectTime: (hour, minute, second, millisecond, microsecond, nanosecond) => { ES.RejectToRange(hour, 0, 23); @@ -1019,10 +1083,11 @@ export const ES = ObjectAssign({}, ES2019, { ES.RejectToRange(nanosecond, 0, 999); }, RejectDateTime: (year, month, day, hour, minute, second, millisecond, microsecond, nanosecond) => { - ES.RejectToRange(year, YEAR_MIN, YEAR_MAX); - ES.RejectToRange(month, 1, 12); - ES.RejectToRange(day, 1, ES.DaysInMonth(year, month)); + ES.RejectDate(year, month, day); ES.RejectTime(hour, minute, second, millisecond, microsecond, nanosecond); + }, + RejectDateTimeRange: (year, month, day, hour, minute, second, millisecond, microsecond, nanosecond) => { + ES.RejectToRange(year, YEAR_MIN, YEAR_MAX); // Reject any DateTime 24 hours or more outside the Absolute range if ( (year === YEAR_MIN && @@ -1035,21 +1100,18 @@ export const ES = ObjectAssign({}, ES2019, { throw new RangeError('DateTime outside of supported range'); } }, - RejectAbsolute: (epochNanoseconds) => { + RejectAbsoluteRange: (epochNanoseconds) => { if (epochNanoseconds.lesser(NS_MIN) || epochNanoseconds.greater(NS_MAX)) { throw new RangeError('Absolute outside of supported range'); } }, - RejectYearMonth: (year, month, refISODay) => { + RejectYearMonthRange: (year, month) => { ES.RejectToRange(year, YEAR_MIN, YEAR_MAX); if (year === YEAR_MIN) { ES.RejectToRange(month, 4, 12); } else if (year === YEAR_MAX) { ES.RejectToRange(month, 1, 9); - } else { - ES.RejectToRange(month, 1, 12); } - ES.RejectToRange(refISODay, 1, ES.DaysInMonth(year, month)); }, DifferenceDate: (smaller, larger, largestUnit = 'days') => { @@ -1130,16 +1192,7 @@ export const ES = ObjectAssign({}, ES2019, { year += years; month += months; ({ year, month } = ES.BalanceYearMonth(year, month)); - - switch (disambiguation) { - case 'reject': - ES.RejectDate(year, month, day); - break; - case 'constrain': - ({ year, month, day } = ES.ConstrainDate(year, month, day)); - break; - } - + ({ year, month, day } = ES.RegulateDate(year, month, day, disambiguation)); days += 7 * weeks; day += days; ({ year, month, day } = ES.BalanceDate(year, month, day)); @@ -1183,15 +1236,7 @@ export const ES = ObjectAssign({}, ES2019, { month -= months; year -= years; ({ year, month } = ES.BalanceYearMonth(year, month)); - - switch (disambiguation) { - case 'reject': - ES.RejectDate(year, month, day); - break; - case 'constrain': - ({ year, month, day } = ES.ConstrainDate(year, month, day)); - break; - } + ({ year, month, day } = ES.RegulateDate(year, month, day, disambiguation)); return { year, month, day }; }, SubtractTime: ( diff --git a/polyfill/lib/monthday.mjs b/polyfill/lib/monthday.mjs index 57fc16befb..2ad1d161a7 100644 --- a/polyfill/lib/monthday.mjs +++ b/polyfill/lib/monthday.mjs @@ -8,6 +8,7 @@ export class MonthDay { isoDay = ES.ToInteger(isoDay); refISOYear = ES.ToInteger(refISOYear); ES.RejectDate(refISOYear, isoMonth, isoDay); + ES.RejectDateRange(refISOYear, isoMonth, isoDay); CreateSlots(this); SetSlot(this, ISO_MONTH, isoMonth); diff --git a/polyfill/lib/yearmonth.mjs b/polyfill/lib/yearmonth.mjs index 4c14c7fd7c..5d6d021486 100644 --- a/polyfill/lib/yearmonth.mjs +++ b/polyfill/lib/yearmonth.mjs @@ -7,7 +7,8 @@ export class YearMonth { isoYear = ES.ToInteger(isoYear); isoMonth = ES.ToInteger(isoMonth); refISODay = ES.ToInteger(refISODay); - ES.RejectYearMonth(isoYear, isoMonth, refISODay); + ES.RejectDate(isoYear, isoMonth, refISODay); + ES.RejectYearMonthRange(isoYear, isoMonth); CreateSlots(this); SetSlot(this, ISO_YEAR, isoYear); SetSlot(this, ISO_MONTH, isoMonth); @@ -42,6 +43,7 @@ export class YearMonth { } let { year = GetSlot(this, ISO_YEAR), month = GetSlot(this, ISO_MONTH) } = props; ({ year, month } = ES.RegulateYearMonth(year, month, disambiguation)); + ({ year, month } = ES.RegulateYearMonthRange(year, month, disambiguation)); const Construct = ES.SpeciesConstructor(this, YearMonth); const result = new Construct(year, month); if (!ES.IsTemporalYearMonth(result)) throw new TypeError('invalid result'); @@ -65,7 +67,7 @@ export class YearMonth { ); ({ year, month } = ES.AddDate(year, month, 1, years, months, weeks, days, disambiguation)); ({ year, month } = ES.BalanceYearMonth(year, month)); - ({ year, month } = ES.RegulateYearMonth(year, month, disambiguation)); + ({ year, month } = ES.RegulateYearMonthRange(year, month, disambiguation)); const Construct = ES.SpeciesConstructor(this, YearMonth); const result = new Construct(year, month); if (!ES.IsTemporalYearMonth(result)) throw new TypeError('invalid result'); @@ -90,7 +92,7 @@ export class YearMonth { const lastDay = ES.DaysInMonth(year, month); ({ year, month } = ES.SubtractDate(year, month, lastDay, years, months, weeks, days, disambiguation)); ({ year, month } = ES.BalanceYearMonth(year, month)); - ({ year, month } = ES.RegulateYearMonth(year, month, disambiguation)); + ({ year, month } = ES.RegulateYearMonthRange(year, month, disambiguation)); const Construct = ES.SpeciesConstructor(this, YearMonth); const result = new Construct(year, month); if (!ES.IsTemporalYearMonth(result)) throw new TypeError('invalid result'); @@ -176,6 +178,7 @@ export class YearMonth { refISODay = 1; } ({ year, month } = ES.RegulateYearMonth(year, month, disambiguation)); + ({ year, month } = ES.RegulateYearMonthRange(year, month, disambiguation)); const result = new this(year, month, refISODay); if (!ES.IsTemporalYearMonth(result)) throw new TypeError('invalid result'); return result;