Skip to content

Commit

Permalink
Separate regulating invalid values from regulating allowed range
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
ptomato committed Jun 12, 2020
1 parent ae32350 commit 9dd2a42
Show file tree
Hide file tree
Showing 6 changed files with 128 additions and 51 deletions.
14 changes: 7 additions & 7 deletions polyfill/lib/absolute.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand Down Expand Up @@ -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));
Expand Down Expand Up @@ -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));
Expand Down Expand Up @@ -155,30 +155,30 @@ 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;
}
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;
}
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;
Expand Down
7 changes: 5 additions & 2 deletions polyfill/lib/date.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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');
Expand All @@ -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');
Expand All @@ -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');
Expand Down Expand Up @@ -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;
Expand Down
29 changes: 27 additions & 2 deletions polyfill/lib/datetime.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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');
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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;
Expand Down
119 changes: 82 additions & 37 deletions polyfill/lib/ecmascript.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -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':
Expand All @@ -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':
Expand All @@ -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 };
Expand Down Expand Up @@ -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) => {
Expand All @@ -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,
Expand All @@ -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 &&
Expand All @@ -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 };
},
Expand All @@ -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);
Expand All @@ -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 &&
Expand All @@ -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') => {
Expand Down Expand Up @@ -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));
Expand Down Expand Up @@ -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: (
Expand Down
1 change: 1 addition & 0 deletions polyfill/lib/monthday.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
Loading

0 comments on commit 9dd2a42

Please sign in to comment.