Skip to content

Commit

Permalink
Editorial: Simplify operations (#2248)
Browse files Browse the repository at this point in the history
* Replace ToLargestTemporalUnit with a more general GetTemporalUnit
* Replace ToSmallestTemporalUnit with GetTemporalUnit
* Replace ToTemporalDurationTotalUnit with GetTemporalUnit
* Use the units table to support more operations
  • Loading branch information
gibson042 committed Jun 8, 2022
1 parent c6571f4 commit 1b3d018
Show file tree
Hide file tree
Showing 17 changed files with 204 additions and 241 deletions.
8 changes: 2 additions & 6 deletions polyfill/lib/calendar.mjs
Expand Up @@ -137,12 +137,8 @@ export class Calendar {
one = ES.ToTemporalDate(one);
two = ES.ToTemporalDate(two);
options = ES.GetOptionsObject(options);
const largestUnit = ES.ToLargestTemporalUnit(
options,
'auto',
['hour', 'minute', 'second', 'millisecond', 'microsecond', 'nanosecond'],
'day'
);
let largestUnit = ES.GetTemporalUnit(options, 'largestUnit', 'date', 'auto');
if (largestUnit === 'auto') largestUnit = 'day';
const { years, months, weeks, days } = impl[GetSlot(this, CALENDAR_ID)].dateUntil(one, two, largestUnit);
const Duration = GetIntrinsic('%Temporal.Duration%');
return new Duration(years, months, weeks, days, 0, 0, 0, 0, 0, 0);
Expand Down
11 changes: 6 additions & 5 deletions polyfill/lib/duration.mjs
Expand Up @@ -230,14 +230,14 @@ export class Duration {
} else {
roundTo = ES.GetOptionsObject(roundTo);
}
let smallestUnit = ES.ToSmallestTemporalUnit(roundTo, undefined);
let smallestUnit = ES.GetTemporalUnit(roundTo, 'smallestUnit', 'datetime', undefined);
let smallestUnitPresent = true;
if (!smallestUnit) {
smallestUnitPresent = false;
smallestUnit = 'nanosecond';
}
defaultLargestUnit = ES.LargerOfTwoTemporalUnits(defaultLargestUnit, smallestUnit);
let largestUnit = ES.ToLargestTemporalUnit(roundTo, undefined);
let largestUnit = ES.GetTemporalUnit(roundTo, 'largestUnit', 'datetime', undefined, ['auto']);
let largestUnitPresent = true;
if (!largestUnit) {
largestUnitPresent = false;
Expand All @@ -247,7 +247,9 @@ export class Duration {
if (!smallestUnitPresent && !largestUnitPresent) {
throw new RangeError('at least one of smallestUnit or largestUnit is required');
}
ES.ValidateTemporalUnitRange(largestUnit, smallestUnit);
if (ES.LargerOfTwoTemporalUnits(largestUnit, smallestUnit) !== largestUnit) {
throw new RangeError(`largestUnit ${largestUnit} cannot be smaller than smallestUnit ${smallestUnit}`);
}
const roundingMode = ES.ToTemporalRoundingMode(roundTo, 'halfExpand');
const roundingIncrement = ES.ToTemporalDateTimeRoundingIncrement(roundTo, smallestUnit);
let relativeTo = ES.ToRelativeTemporalObject(roundTo);
Expand Down Expand Up @@ -333,8 +335,7 @@ export class Duration {
} else {
totalOf = ES.GetOptionsObject(totalOf);
}
const unit = ES.ToTemporalDurationTotalUnit(totalOf, undefined);
if (unit === undefined) throw new RangeError('unit option is required');
const unit = ES.GetTemporalUnit(totalOf, 'unit', 'datetime', ES.REQUIRED);
const relativeTo = ES.ToRelativeTemporalObject(totalOf);

// Convert larger units down to days
Expand Down
175 changes: 94 additions & 81 deletions polyfill/lib/ecmascript.mjs
Expand Up @@ -155,30 +155,22 @@ const BUILTIN_CASTS = new Map([
['offset', ToString]
]);

const ALLOWED_UNITS = [
'year',
'month',
'week',
'day',
'hour',
'minute',
'second',
'millisecond',
'microsecond',
'nanosecond'
];
// each item is [plural, singular, category]
const SINGULAR_PLURAL_UNITS = [
['years', 'year'],
['months', 'month'],
['weeks', 'week'],
['days', 'day'],
['hours', 'hour'],
['minutes', 'minute'],
['seconds', 'second'],
['milliseconds', 'millisecond'],
['microseconds', 'microsecond'],
['nanoseconds', 'nanosecond']
['years', 'year', 'date'],
['months', 'month', 'date'],
['weeks', 'week', 'date'],
['days', 'day', 'date'],
['hours', 'hour', 'time'],
['minutes', 'minute', 'time'],
['seconds', 'second', 'time'],
['milliseconds', 'millisecond', 'time'],
['microseconds', 'microsecond', 'time'],
['nanoseconds', 'nanosecond', 'time']
];
const SINGULAR_FOR = new Map(SINGULAR_PLURAL_UNITS);
const PLURAL_FOR = new Map(SINGULAR_PLURAL_UNITS.map(([p, s]) => [s, p]));
const UNITS_DESCENDING = SINGULAR_PLURAL_UNITS.map(([, s]) => s);

import * as PARSE from './regex.mjs';

Expand Down Expand Up @@ -691,7 +683,14 @@ export const ES = ObjectAssign({}, ES2020, {
return ES.ToTemporalRoundingIncrement(options, maximumIncrements[smallestUnit], false);
},
ToSecondsStringPrecision: (options) => {
let smallestUnit = ES.ToSmallestTemporalUnit(options, undefined, ['year', 'month', 'week', 'day', 'hour']);
const smallestUnit = ES.GetTemporalUnit(options, 'smallestUnit', 'time', undefined);
if (smallestUnit === 'hour') {
const ALLOWED_UNITS = SINGULAR_PLURAL_UNITS.reduce((allowed, [p, s, c]) => {
if (c === 'time' && s !== 'hour') allowed.push(s, p);
return allowed;
}, []);
throw new RangeError(`smallestUnit must be one of ${ALLOWED_UNITS.join(', ')}, not ${smallestUnit}`);
}
switch (smallestUnit) {
case 'minute':
return { precision: 'minute', unit: 'minute', increment: 1 };
Expand Down Expand Up @@ -733,37 +732,33 @@ export const ES = ObjectAssign({}, ES2020, {
return { precision, unit: 'nanosecond', increment: 10 ** (9 - precision) };
}
},
ToLargestTemporalUnit: (options, fallback, disallowedStrings = [], autoValue) => {
const singular = new Map(SINGULAR_PLURAL_UNITS.filter(([, sing]) => !disallowedStrings.includes(sing)));
const allowed = new Set(ALLOWED_UNITS);
for (const s of disallowedStrings) {
allowed.delete(s);
REQUIRED: Symbol('~required~'),
GetTemporalUnit: (options, key, unitGroup, requiredOrDefault, extraValues = []) => {
const allowedSingular = [];
for (const [, singular, category] of SINGULAR_PLURAL_UNITS) {
if (unitGroup === 'datetime' || unitGroup === category) {
allowedSingular.push(singular);
}
}
allowedSingular.push(...extraValues);
let defaultVal = requiredOrDefault;
if (defaultVal === ES.REQUIRED) {
defaultVal = undefined;
} else if (defaultVal !== undefined) {
allowedSingular.push(defaultVal);
}
const allowedValues = [...allowedSingular];
for (const singular of allowedSingular) {
const plural = PLURAL_FOR.get(singular);
if (plural !== undefined) allowedValues.push(plural);
}
let retval = ES.GetOption(options, key, allowedValues, defaultVal);
if (retval === undefined && requiredOrDefault === ES.REQUIRED) {
throw new RangeError(`${key} is required`);
}
const retval = ES.GetOption(options, 'largestUnit', ['auto', ...allowed, ...singular.keys()], fallback);
if (retval === 'auto' && autoValue !== undefined) return autoValue;
if (singular.has(retval)) return singular.get(retval);
if (SINGULAR_FOR.has(retval)) retval = SINGULAR_FOR.get(retval);
return retval;
},
ToSmallestTemporalUnit: (options, fallback, disallowedStrings = []) => {
const singular = new Map(SINGULAR_PLURAL_UNITS.filter(([, sing]) => !disallowedStrings.includes(sing)));
const allowed = new Set(ALLOWED_UNITS);
for (const s of disallowedStrings) {
allowed.delete(s);
}
const value = ES.GetOption(options, 'smallestUnit', [...allowed, ...singular.keys()], fallback);
if (singular.has(value)) return singular.get(value);
return value;
},
ToTemporalDurationTotalUnit: (options) => {
// This AO is identical to ToSmallestTemporalUnit, except:
// - default is always `undefined` (caller will throw if omitted)
// - option is named `unit` (not `smallestUnit`)
// - all units are valid (no `disallowedStrings`)
const singular = new Map(SINGULAR_PLURAL_UNITS);
const value = ES.GetOption(options, 'unit', [...singular.values(), ...singular.keys()], undefined);
if (singular.has(value)) return singular.get(value);
return value;
},
ToRelativeTemporalObject: (options) => {
const relativeTo = options.relativeTo;
if (relativeTo === undefined) return relativeTo;
Expand Down Expand Up @@ -834,11 +829,6 @@ export const ES = ObjectAssign({}, ES2020, {
}
return ES.CreateTemporalDate(year, month, day, calendar);
},
ValidateTemporalUnitRange: (largestUnit, smallestUnit) => {
if (ALLOWED_UNITS.indexOf(largestUnit) > ALLOWED_UNITS.indexOf(smallestUnit)) {
throw new RangeError(`largestUnit ${largestUnit} cannot be smaller than smallestUnit ${smallestUnit}`);
}
},
DefaultTemporalLargestUnit: (
years,
months,
Expand All @@ -851,7 +841,6 @@ export const ES = ObjectAssign({}, ES2020, {
microseconds,
nanoseconds
) => {
const singular = new Map(SINGULAR_PLURAL_UNITS);
for (const [prop, v] of ObjectEntries({
years,
months,
Expand All @@ -864,12 +853,12 @@ export const ES = ObjectAssign({}, ES2020, {
microseconds,
nanoseconds
})) {
if (v !== 0) return singular.get(prop);
if (v !== 0) return SINGULAR_FOR.get(prop);
}
return 'nanosecond';
},
LargerOfTwoTemporalUnits: (unit1, unit2) => {
if (ALLOWED_UNITS.indexOf(unit1) > ALLOWED_UNITS.indexOf(unit2)) return unit2;
if (UNITS_DESCENDING.indexOf(unit1) > UNITS_DESCENDING.indexOf(unit2)) return unit2;
return unit1;
},
MergeLargestUnitOption: (options, largestUnit) => {
Expand Down Expand Up @@ -3309,11 +3298,13 @@ export const ES = ObjectAssign({}, ES2020, {
[first, second] = [other, instant];
}
options = ES.GetOptionsObject(options);
const DISALLOWED_UNITS = ['year', 'month', 'week', 'day'];
const smallestUnit = ES.ToSmallestTemporalUnit(options, 'nanosecond', DISALLOWED_UNITS);
const smallestUnit = ES.GetTemporalUnit(options, 'smallestUnit', 'time', 'nanosecond');
const defaultLargestUnit = ES.LargerOfTwoTemporalUnits('second', smallestUnit);
const largestUnit = ES.ToLargestTemporalUnit(options, 'auto', DISALLOWED_UNITS, defaultLargestUnit);
ES.ValidateTemporalUnitRange(largestUnit, smallestUnit);
let largestUnit = ES.GetTemporalUnit(options, 'largestUnit', 'time', 'auto');
if (largestUnit === 'auto') largestUnit = defaultLargestUnit;
if (ES.LargerOfTwoTemporalUnits(largestUnit, smallestUnit) !== largestUnit) {
throw new RangeError(`largestUnit ${largestUnit} cannot be smaller than smallestUnit ${smallestUnit}`);
}
const roundingMode = ES.ToTemporalRoundingMode(options, 'trunc');
const MAX_DIFFERENCE_INCREMENTS = {
hour: 24,
Expand Down Expand Up @@ -3359,11 +3350,13 @@ export const ES = ObjectAssign({}, ES2020, {
}

options = ES.GetOptionsObject(options);
const DISALLOWED_UNITS = ['hour', 'minute', 'second', 'millisecond', 'microsecond', 'nanosecond'];
const smallestUnit = ES.ToSmallestTemporalUnit(options, 'day', DISALLOWED_UNITS);
const smallestUnit = ES.GetTemporalUnit(options, 'smallestUnit', 'date', 'day');
const defaultLargestUnit = ES.LargerOfTwoTemporalUnits('day', smallestUnit);
const largestUnit = ES.ToLargestTemporalUnit(options, 'auto', DISALLOWED_UNITS, defaultLargestUnit);
ES.ValidateTemporalUnitRange(largestUnit, smallestUnit);
let largestUnit = ES.GetTemporalUnit(options, 'largestUnit', 'date', 'auto');
if (largestUnit === 'auto') largestUnit = defaultLargestUnit;
if (ES.LargerOfTwoTemporalUnits(largestUnit, smallestUnit) !== largestUnit) {
throw new RangeError(`largestUnit ${largestUnit} cannot be smaller than smallestUnit ${smallestUnit}`);
}
let roundingMode = ES.ToTemporalRoundingMode(options, 'trunc');
if (operation === 'since') roundingMode = ES.NegateTemporalRoundingMode(roundingMode);
const roundingIncrement = ES.ToTemporalRoundingIncrement(options, undefined, false);
Expand Down Expand Up @@ -3404,10 +3397,13 @@ export const ES = ObjectAssign({}, ES2020, {
throw new RangeError(`cannot compute difference between dates of ${calendarId} and ${otherCalendarId} calendars`);
}
options = ES.GetOptionsObject(options);
const smallestUnit = ES.ToSmallestTemporalUnit(options, 'nanosecond');
const smallestUnit = ES.GetTemporalUnit(options, 'smallestUnit', 'datetime', 'nanosecond');
const defaultLargestUnit = ES.LargerOfTwoTemporalUnits('day', smallestUnit);
const largestUnit = ES.ToLargestTemporalUnit(options, 'auto', [], defaultLargestUnit);
ES.ValidateTemporalUnitRange(largestUnit, smallestUnit);
let largestUnit = ES.GetTemporalUnit(options, 'largestUnit', 'datetime', 'auto');
if (largestUnit === 'auto') largestUnit = defaultLargestUnit;
if (ES.LargerOfTwoTemporalUnits(largestUnit, smallestUnit) !== largestUnit) {
throw new RangeError(`largestUnit ${largestUnit} cannot be smaller than smallestUnit ${smallestUnit}`);
}
let roundingMode = ES.ToTemporalRoundingMode(options, 'trunc');
if (operation === 'since') roundingMode = ES.NegateTemporalRoundingMode(roundingMode);
const roundingIncrement = ES.ToTemporalDateTimeRoundingIncrement(options, smallestUnit);
Expand Down Expand Up @@ -3484,10 +3480,12 @@ export const ES = ObjectAssign({}, ES2020, {
const sign = operation === 'since' ? -1 : 1;
other = ES.ToTemporalTime(other);
options = ES.GetOptionsObject(options);
const DISALLOWED_UNITS = ['year', 'month', 'week', 'day'];
const largestUnit = ES.ToLargestTemporalUnit(options, 'auto', DISALLOWED_UNITS, 'hour');
const smallestUnit = ES.ToSmallestTemporalUnit(options, 'nanosecond', DISALLOWED_UNITS);
ES.ValidateTemporalUnitRange(largestUnit, smallestUnit);
let largestUnit = ES.GetTemporalUnit(options, 'largestUnit', 'time', 'auto');
if (largestUnit === 'auto') largestUnit = 'hour';
const smallestUnit = ES.GetTemporalUnit(options, 'smallestUnit', 'time', 'nanosecond');
if (ES.LargerOfTwoTemporalUnits(largestUnit, smallestUnit) !== largestUnit) {
throw new RangeError(`largestUnit ${largestUnit} cannot be smaller than smallestUnit ${smallestUnit}`);
}
let roundingMode = ES.ToTemporalRoundingMode(options, 'trunc');
if (operation === 'since') roundingMode = ES.NegateTemporalRoundingMode(roundingMode);
const MAX_INCREMENTS = {
Expand Down Expand Up @@ -3565,10 +3563,22 @@ export const ES = ObjectAssign({}, ES2020, {
);
}
options = ES.GetOptionsObject(options);
const DISALLOWED_UNITS = ['week', 'day', 'hour', 'minute', 'second', 'millisecond', 'microsecond', 'nanosecond'];
const smallestUnit = ES.ToSmallestTemporalUnit(options, 'month', DISALLOWED_UNITS);
const largestUnit = ES.ToLargestTemporalUnit(options, 'auto', DISALLOWED_UNITS, 'year');
ES.ValidateTemporalUnitRange(largestUnit, smallestUnit);
const ALLOWED_UNITS = SINGULAR_PLURAL_UNITS.reduce((allowed, [p, s, c]) => {
if (c === 'date' && s !== 'week' && s !== 'day') allowed.push(s, p);
return allowed;
}, []);
const smallestUnit = ES.GetTemporalUnit(options, 'smallestUnit', 'date', 'month');
if (smallestUnit === 'week' || smallestUnit === 'day') {
throw new RangeError(`smallestUnit must be one of ${ALLOWED_UNITS.join(', ')}, not ${smallestUnit}`);
}
let largestUnit = ES.GetTemporalUnit(options, 'largestUnit', 'date', 'auto');
if (largestUnit === 'week' || largestUnit === 'day') {
throw new RangeError(`largestUnit must be one of ${ALLOWED_UNITS.join(', ')}, not ${largestUnit}`);
}
if (largestUnit === 'auto') largestUnit = 'year';
if (ES.LargerOfTwoTemporalUnits(largestUnit, smallestUnit) !== largestUnit) {
throw new RangeError(`largestUnit ${largestUnit} cannot be smaller than smallestUnit ${smallestUnit}`);
}
let roundingMode = ES.ToTemporalRoundingMode(options, 'trunc');
if (operation === 'since') roundingMode = ES.NegateTemporalRoundingMode(roundingMode);
const roundingIncrement = ES.ToTemporalRoundingIncrement(options, undefined, false);
Expand Down Expand Up @@ -3615,10 +3625,13 @@ export const ES = ObjectAssign({}, ES2020, {
throw new RangeError(`cannot compute difference between dates of ${calendarId} and ${otherCalendarId} calendars`);
}
options = ES.GetOptionsObject(options);
const smallestUnit = ES.ToSmallestTemporalUnit(options, 'nanosecond');
const smallestUnit = ES.GetTemporalUnit(options, 'smallestUnit', 'datetime', 'nanosecond');
const defaultLargestUnit = ES.LargerOfTwoTemporalUnits('hour', smallestUnit);
const largestUnit = ES.ToLargestTemporalUnit(options, 'auto', [], defaultLargestUnit);
ES.ValidateTemporalUnitRange(largestUnit, smallestUnit);
let largestUnit = ES.GetTemporalUnit(options, 'largestUnit', 'datetime', 'auto');
if (largestUnit === 'auto') largestUnit = defaultLargestUnit;
if (ES.LargerOfTwoTemporalUnits(largestUnit, smallestUnit) !== largestUnit) {
throw new RangeError(`largestUnit ${largestUnit} cannot be smaller than smallestUnit ${smallestUnit}`);
}
let roundingMode = ES.ToTemporalRoundingMode(options, 'trunc');
if (operation === 'since') roundingMode = ES.NegateTemporalRoundingMode(roundingMode);
const roundingIncrement = ES.ToTemporalDateTimeRoundingIncrement(options, smallestUnit);
Expand Down
4 changes: 1 addition & 3 deletions polyfill/lib/instant.mjs
Expand Up @@ -79,9 +79,7 @@ export class Instant {
} else {
roundTo = ES.GetOptionsObject(roundTo);
}
const DISALLOWED_UNITS = ['year', 'month', 'week', 'day'];
const smallestUnit = ES.ToSmallestTemporalUnit(roundTo, undefined, DISALLOWED_UNITS);
if (smallestUnit === undefined) throw new RangeError('smallestUnit is required');
const smallestUnit = ES.GetTemporalUnit(roundTo, 'smallestUnit', 'time', ES.REQUIRED);
const roundingMode = ES.ToTemporalRoundingMode(roundTo, 'halfExpand');
const maximumIncrements = {
hour: 24,
Expand Down
3 changes: 1 addition & 2 deletions polyfill/lib/plaindatetime.mjs
Expand Up @@ -295,8 +295,7 @@ export class PlainDateTime {
} else {
roundTo = ES.GetOptionsObject(roundTo);
}
const smallestUnit = ES.ToSmallestTemporalUnit(roundTo, undefined, ['year', 'month', 'week']);
if (smallestUnit === undefined) throw new RangeError('smallestUnit is required');
const smallestUnit = ES.GetTemporalUnit(roundTo, 'smallestUnit', 'time', ES.REQUIRED, ['day']);
const roundingMode = ES.ToTemporalRoundingMode(roundTo, 'halfExpand');
const maximumIncrements = {
day: 1,
Expand Down
4 changes: 1 addition & 3 deletions polyfill/lib/plaintime.mjs
Expand Up @@ -171,9 +171,7 @@ export class PlainTime {
} else {
roundTo = ES.GetOptionsObject(roundTo);
}
const DISALLOWED_UNITS = ['year', 'month', 'week', 'day'];
const smallestUnit = ES.ToSmallestTemporalUnit(roundTo, undefined, DISALLOWED_UNITS);
if (smallestUnit === undefined) throw new RangeError('smallestUnit is required');
const smallestUnit = ES.GetTemporalUnit(roundTo, 'smallestUnit', 'time', ES.REQUIRED);
const roundingMode = ES.ToTemporalRoundingMode(roundTo, 'halfExpand');
const MAX_INCREMENTS = {
hour: 24,
Expand Down
3 changes: 1 addition & 2 deletions polyfill/lib/zoneddatetime.mjs
Expand Up @@ -350,8 +350,7 @@ export class ZonedDateTime {
} else {
roundTo = ES.GetOptionsObject(roundTo);
}
const smallestUnit = ES.ToSmallestTemporalUnit(roundTo, undefined, ['year', 'month', 'week']);
if (smallestUnit === undefined) throw new RangeError('smallestUnit is required');
const smallestUnit = ES.GetTemporalUnit(roundTo, 'smallestUnit', 'time', ES.REQUIRED, ['day']);
const roundingMode = ES.ToTemporalRoundingMode(roundTo, 'halfExpand');
const maximumIncrements = {
day: 1,
Expand Down

0 comments on commit 1b3d018

Please sign in to comment.