Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Editorial: Refactor CalendarFields/PrepareTemporalFields to ensure correct use #2823

Merged
merged 2 commits into from
Apr 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
123 changes: 69 additions & 54 deletions polyfill/lib/ecmascript.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
const ArrayIncludes = Array.prototype.includes;
const ArrayPrototypeMap = Array.prototype.map;
const ArrayPrototypePush = Array.prototype.push;
const ArrayPrototypeSlice = Array.prototype.slice;
const ArrayPrototypeSort = Array.prototype.sort;
const ArrayPrototypeFind = Array.prototype.find;
const IntlDateTimeFormat = globalThis.Intl.DateTimeFormat;
Expand Down Expand Up @@ -1036,18 +1037,13 @@ export function ToRelativeTemporalObject(options) {
if (IsTemporalDateTime(relativeTo)) return { plainRelativeTo: TemporalDateTimeToDate(relativeTo) };
calendar = GetTemporalCalendarSlotValueWithISODefault(relativeTo);
const calendarRec = new CalendarMethodRecord(calendar, ['dateFromFields', 'fields']);
const fieldNames = CalendarFields(calendarRec, ['day', 'month', 'monthCode', 'year']);
Call(ArrayPrototypePush, fieldNames, [
'hour',
'microsecond',
'millisecond',
'minute',
'nanosecond',
'offset',
'second',
'timeZone'
]);
const fields = PrepareTemporalFields(relativeTo, fieldNames, []);
const fields = PrepareCalendarFields(
calendarRec,
relativeTo,
['day', 'month', 'monthCode', 'year'],
['hour', 'microsecond', 'millisecond', 'minute', 'nanosecond', 'offset', 'second', 'timeZone'],
[]
);
const dateOptions = ObjectCreate(null);
dateOptions.overflow = 'constrain';
({ year, month, day, hour, minute, second, millisecond, microsecond, nanosecond } = InterpretTemporalDateTimeFields(
Expand Down Expand Up @@ -1218,6 +1214,45 @@ export function PrepareTemporalFields(
return result;
}

export function PrepareCalendarFieldsAndFieldNames(
calendarRec,
bag,
calendarFieldNames,
nonCalendarFieldNames = [],
requiredFieldNames = []
) {
// Special-case built-in method, because we should skip the observable array
// iteration in Calendar.prototype.fields
let fieldNames;
if (calendarRec.isBuiltIn()) {
if (calendarRec.receiver !== 'iso8601') {
fieldNames = GetIntrinsic('%calendarFieldsImpl%')(calendarRec.receiver, calendarFieldNames);
} else {
fieldNames = Call(ArrayPrototypeSlice, calendarFieldNames, []);
}
} else {
fieldNames = [];
for (const name of calendarRec.fields(calendarFieldNames)) {
if (Type(name) !== 'String') throw new TypeError('bad return from calendar.fields()');
Call(ArrayPrototypePush, fieldNames, [name]);
}
}
Call(ArrayPrototypePush, fieldNames, nonCalendarFieldNames);
const fields = PrepareTemporalFields(bag, fieldNames, requiredFieldNames);
return { fields, fieldNames };
}

export function PrepareCalendarFields(calendarRec, bag, calendarFieldNames, nonCalendarFieldNames, requiredFieldNames) {
const { fields } = PrepareCalendarFieldsAndFieldNames(
calendarRec,
bag,
calendarFieldNames,
nonCalendarFieldNames,
requiredFieldNames
);
return fields;
}

export function ToTemporalTimeRecord(bag, completeness = 'complete') {
const fields = ['hour', 'microsecond', 'millisecond', 'minute', 'nanosecond', 'second'];
const partial = PrepareTemporalFields(bag, fields, 'partial', undefined, undefined, {
Expand Down Expand Up @@ -1264,8 +1299,7 @@ export function ToTemporalDate(item, options) {
'dateFromFields',
'fields'
]);
const fieldNames = CalendarFields(calendarRec, ['day', 'month', 'monthCode', 'year']);
const fields = PrepareTemporalFields(item, fieldNames, []);
const fields = PrepareCalendarFields(calendarRec, item, ['day', 'month', 'monthCode', 'year'], [], []);
return CalendarDateFromFields(calendarRec, fields, options);
}
let { year, month, day, calendar, z } = ParseTemporalDateString(RequireString(item));
Expand Down Expand Up @@ -1327,9 +1361,13 @@ export function ToTemporalDateTime(item, options) {

calendar = GetTemporalCalendarSlotValueWithISODefault(item);
const calendarRec = new CalendarMethodRecord(calendar, ['dateFromFields', 'fields']);
const fieldNames = CalendarFields(calendarRec, ['day', 'month', 'monthCode', 'year']);
Call(ArrayPrototypePush, fieldNames, ['hour', 'microsecond', 'millisecond', 'minute', 'nanosecond', 'second']);
const fields = PrepareTemporalFields(item, fieldNames, []);
const fields = PrepareCalendarFields(
calendarRec,
item,
['day', 'month', 'monthCode', 'year'],
['hour', 'microsecond', 'millisecond', 'minute', 'nanosecond', 'second'],
[]
);
({ year, month, day, hour, minute, second, millisecond, microsecond, nanosecond } = InterpretTemporalDateTimeFields(
calendarRec,
fields,
Expand Down Expand Up @@ -1413,8 +1451,7 @@ export function ToTemporalMonthDay(item, options) {
calendar = ToTemporalCalendarSlotValue(calendar);
}
const calendarRec = new CalendarMethodRecord(calendar, ['fields', 'monthDayFromFields']);
const fieldNames = CalendarFields(calendarRec, ['day', 'month', 'monthCode', 'year']);
const fields = PrepareTemporalFields(item, fieldNames, []);
const fields = PrepareCalendarFields(calendarRec, item, ['day', 'month', 'monthCode', 'year'], [], []);
return CalendarMonthDayFromFields(calendarRec, fields, options);
}

Expand Down Expand Up @@ -1485,8 +1522,7 @@ export function ToTemporalYearMonth(item, options) {
if (IsTemporalYearMonth(item)) return item;
const calendar = GetTemporalCalendarSlotValueWithISODefault(item);
const calendarRec = new CalendarMethodRecord(calendar, ['fields', 'yearMonthFromFields']);
const fieldNames = CalendarFields(calendarRec, ['month', 'monthCode', 'year']);
const fields = PrepareTemporalFields(item, fieldNames, []);
const fields = PrepareCalendarFields(calendarRec, item, ['month', 'monthCode', 'year'], [], []);
return CalendarYearMonthFromFields(calendarRec, fields, options);
}

Expand Down Expand Up @@ -1600,18 +1636,13 @@ export function ToTemporalZonedDateTime(item, options) {
if (IsTemporalZonedDateTime(item)) return item;
calendar = GetTemporalCalendarSlotValueWithISODefault(item);
const calendarRec = new CalendarMethodRecord(calendar, ['dateFromFields', 'fields']);
const fieldNames = CalendarFields(calendarRec, ['day', 'month', 'monthCode', 'year']);
Call(ArrayPrototypePush, fieldNames, [
'hour',
'microsecond',
'millisecond',
'minute',
'nanosecond',
'offset',
'second',
'timeZone'
]);
const fields = PrepareTemporalFields(item, fieldNames, ['timeZone']);
const fields = PrepareCalendarFields(
calendarRec,
item,
['day', 'month', 'monthCode', 'year'],
['hour', 'microsecond', 'millisecond', 'minute', 'nanosecond', 'offset', 'second', 'timeZone'],
['timeZone']
);
timeZone = ToTemporalTimeZoneSlotValue(fields.timeZone);
offset = fields.offset;
if (offset === undefined) {
Expand Down Expand Up @@ -1871,23 +1902,6 @@ export function CreateTemporalZonedDateTime(epochNanoseconds, timeZone, calendar
return result;
}

export function CalendarFields(calendarRec, fieldNames) {
// Special-case built-in method, because we should skip the observable array
// iteration in Calendar.prototype.fields
if (calendarRec.isBuiltIn()) {
if (calendarRec.receiver === 'iso8601') return fieldNames;
return GetIntrinsic('%calendarFieldsImpl%')(calendarRec.receiver, fieldNames);
}

fieldNames = calendarRec.fields(fieldNames);
const result = [];
for (const name of fieldNames) {
if (Type(name) !== 'String') throw new TypeError('bad return from calendar.fields()');
Call(ArrayPrototypePush, result, [name]);
}
return result;
}

export function CalendarMergeFields(calendarRec, fields, additionalFields) {
const result = calendarRec.mergeFields(fields, additionalFields);
if (!calendarRec.isBuiltIn() && Type(result) !== 'Object') {
Expand Down Expand Up @@ -4321,8 +4335,10 @@ export function DifferenceTemporalPlainYearMonth(operation, yearMonth, other, op

const calendarRec = new CalendarMethodRecord(calendar, ['dateAdd', 'dateFromFields', 'dateUntil', 'fields']);

const fieldNames = CalendarFields(calendarRec, ['monthCode', 'year']);
const thisFields = PrepareTemporalFields(yearMonth, fieldNames, []);
const { fields: thisFields, fieldNames } = PrepareCalendarFieldsAndFieldNames(calendarRec, yearMonth, [
'monthCode',
'year'
]);
thisFields.day = 1;
const thisDate = CalendarDateFromFields(calendarRec, thisFields);
const otherFields = PrepareTemporalFields(other, fieldNames, []);
Expand Down Expand Up @@ -5001,8 +5017,7 @@ export function AddDurationToOrSubtractDurationFromPlainYearMonth(operation, yea
'yearMonthFromFields'
]);

const fieldNames = CalendarFields(calendarRec, ['monthCode', 'year']);
const fields = PrepareTemporalFields(yearMonth, fieldNames, []);
const { fields, fieldNames } = PrepareCalendarFieldsAndFieldNames(calendarRec, yearMonth, ['monthCode', 'year']);
const fieldsCopy = SnapshotOwnProperties(fields, null);
fields.day = 1;
let startDate = CalendarDateFromFields(calendarRec, fields);
Expand Down
14 changes: 8 additions & 6 deletions polyfill/lib/plaindate.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -100,8 +100,12 @@ export class PlainDate {
const resolvedOptions = ES.SnapshotOwnProperties(ES.GetOptionsObject(options), null);

const calendarRec = new CalendarMethodRecord(GetSlot(this, CALENDAR), ['dateFromFields', 'fields', 'mergeFields']);
const fieldNames = ES.CalendarFields(calendarRec, ['day', 'month', 'monthCode', 'year']);
let fields = ES.PrepareTemporalFields(this, fieldNames, []);
let { fields, fieldNames } = ES.PrepareCalendarFieldsAndFieldNames(calendarRec, this, [
'day',
'month',
'monthCode',
'year'
]);
const partialDate = ES.PrepareTemporalFields(temporalDateLike, fieldNames, 'partial');
fields = ES.CalendarMergeFields(calendarRec, fields, partialDate);
fields = ES.PrepareTemporalFields(fields, fieldNames, []);
Expand Down Expand Up @@ -221,15 +225,13 @@ export class PlainDate {
toPlainYearMonth() {
if (!ES.IsTemporalDate(this)) throw new TypeError('invalid receiver');
const calendarRec = new CalendarMethodRecord(GetSlot(this, CALENDAR), ['fields', 'yearMonthFromFields']);
const fieldNames = ES.CalendarFields(calendarRec, ['monthCode', 'year']);
const fields = ES.PrepareTemporalFields(this, fieldNames, []);
const fields = ES.PrepareCalendarFields(calendarRec, this, ['monthCode', 'year'], [], []);
return ES.CalendarYearMonthFromFields(calendarRec, fields);
}
toPlainMonthDay() {
if (!ES.IsTemporalDate(this)) throw new TypeError('invalid receiver');
const calendarRec = new CalendarMethodRecord(GetSlot(this, CALENDAR), ['fields', 'monthDayFromFields']);
const fieldNames = ES.CalendarFields(calendarRec, ['day', 'monthCode']);
const fields = ES.PrepareTemporalFields(this, fieldNames, []);
const fields = ES.PrepareCalendarFields(calendarRec, this, ['day', 'monthCode'], [], []);
return ES.CalendarMonthDayFromFields(calendarRec, fields);
}
getISOFields() {
Expand Down
14 changes: 8 additions & 6 deletions polyfill/lib/plaindatetime.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -157,8 +157,12 @@ export class PlainDateTime {

const resolvedOptions = ES.SnapshotOwnProperties(ES.GetOptionsObject(options), null);
const calendarRec = new CalendarMethodRecord(GetSlot(this, CALENDAR), ['dateFromFields', 'fields', 'mergeFields']);
const fieldNames = ES.CalendarFields(calendarRec, ['day', 'month', 'monthCode', 'year']);
let fields = ES.PrepareTemporalFields(this, fieldNames, []);
let { fields, fieldNames } = ES.PrepareCalendarFieldsAndFieldNames(calendarRec, this, [
'day',
'month',
'monthCode',
'year'
]);
fields.hour = GetSlot(this, ISO_HOUR);
fields.minute = GetSlot(this, ISO_MINUTE);
fields.second = GetSlot(this, ISO_SECOND);
Expand Down Expand Up @@ -408,15 +412,13 @@ export class PlainDateTime {
toPlainYearMonth() {
if (!ES.IsTemporalDateTime(this)) throw new TypeError('invalid receiver');
const calendarRec = new CalendarMethodRecord(GetSlot(this, CALENDAR), ['fields', 'yearMonthFromFields']);
const fieldNames = ES.CalendarFields(calendarRec, ['monthCode', 'year']);
const fields = ES.PrepareTemporalFields(this, fieldNames, []);
const fields = ES.PrepareCalendarFields(calendarRec, this, ['monthCode', 'year'], [], []);
return ES.CalendarYearMonthFromFields(calendarRec, fields);
}
toPlainMonthDay() {
if (!ES.IsTemporalDateTime(this)) throw new TypeError('invalid receiver');
const calendarRec = new CalendarMethodRecord(GetSlot(this, CALENDAR), ['fields', 'monthDayFromFields']);
const fieldNames = ES.CalendarFields(calendarRec, ['day', 'monthCode']);
const fields = ES.PrepareTemporalFields(this, fieldNames, []);
const fields = ES.PrepareCalendarFields(calendarRec, this, ['day', 'monthCode'], [], []);
return ES.CalendarMonthDayFromFields(calendarRec, fields);
}
toPlainTime() {
Expand Down
22 changes: 15 additions & 7 deletions polyfill/lib/plainmonthday.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,12 @@ export class PlainMonthDay {
'mergeFields',
'monthDayFromFields'
]);
const fieldNames = ES.CalendarFields(calendarRec, ['day', 'month', 'monthCode', 'year']);
let fields = ES.PrepareTemporalFields(this, fieldNames, []);
let { fields, fieldNames } = ES.PrepareCalendarFieldsAndFieldNames(calendarRec, this, [
'day',
'month',
'monthCode',
'year'
]);
const partialMonthDay = ES.PrepareTemporalFields(temporalMonthDayLike, fieldNames, 'partial');
fields = ES.CalendarMergeFields(calendarRec, fields, partialMonthDay);
fields = ES.PrepareTemporalFields(fields, fieldNames, []);
Expand Down Expand Up @@ -81,11 +85,15 @@ export class PlainMonthDay {
if (ES.Type(item) !== 'Object') throw new TypeError('argument should be an object');
const calendarRec = new CalendarMethodRecord(GetSlot(this, CALENDAR), ['dateFromFields', 'fields', 'mergeFields']);

const receiverFieldNames = ES.CalendarFields(calendarRec, ['day', 'monthCode']);
let fields = ES.PrepareTemporalFields(this, receiverFieldNames, []);

const inputFieldNames = ES.CalendarFields(calendarRec, ['year']);
const inputFields = ES.PrepareTemporalFields(item, inputFieldNames, []);
const { fields, fieldNames: receiverFieldNames } = ES.PrepareCalendarFieldsAndFieldNames(calendarRec, this, [
'day',
'monthCode'
]);
const { fields: inputFields, fieldNames: inputFieldNames } = ES.PrepareCalendarFieldsAndFieldNames(
calendarRec,
item,
['year']
);
let mergedFields = ES.CalendarMergeFields(calendarRec, fields, inputFields);
const concatenatedFieldNames = ES.Call(ArrayPrototypeConcat, receiverFieldNames, inputFieldNames);
mergedFields = ES.PrepareTemporalFields(mergedFields, concatenatedFieldNames, [], [], 'ignore');
Expand Down
21 changes: 14 additions & 7 deletions polyfill/lib/plainyearmonth.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -68,8 +68,11 @@ export class PlainYearMonth {
'mergeFields',
'yearMonthFromFields'
]);
const fieldNames = ES.CalendarFields(calendarRec, ['month', 'monthCode', 'year']);
let fields = ES.PrepareTemporalFields(this, fieldNames, []);
let { fields, fieldNames } = ES.PrepareCalendarFieldsAndFieldNames(calendarRec, this, [
'month',
'monthCode',
'year'
]);
const partialYearMonth = ES.PrepareTemporalFields(temporalYearMonthLike, fieldNames, 'partial');
fields = ES.CalendarMergeFields(calendarRec, fields, partialYearMonth);
fields = ES.PrepareTemporalFields(fields, fieldNames, []);
Expand Down Expand Up @@ -122,11 +125,15 @@ export class PlainYearMonth {
if (ES.Type(item) !== 'Object') throw new TypeError('argument should be an object');
const calendarRec = new CalendarMethodRecord(GetSlot(this, CALENDAR), ['dateFromFields', 'fields', 'mergeFields']);

const receiverFieldNames = ES.CalendarFields(calendarRec, ['monthCode', 'year']);
let fields = ES.PrepareTemporalFields(this, receiverFieldNames, []);

const inputFieldNames = ES.CalendarFields(calendarRec, ['day']);
const inputFields = ES.PrepareTemporalFields(item, inputFieldNames, []);
const { fields, fieldNames: receiverFieldNames } = ES.PrepareCalendarFieldsAndFieldNames(calendarRec, this, [
'monthCode',
'year'
]);
const { fields: inputFields, fieldNames: inputFieldNames } = ES.PrepareCalendarFieldsAndFieldNames(
calendarRec,
item,
['day']
);
let mergedFields = ES.CalendarMergeFields(calendarRec, fields, inputFields);
const concatenatedFieldNames = ES.Call(ArrayPrototypeConcat, receiverFieldNames, inputFieldNames);
mergedFields = ES.PrepareTemporalFields(mergedFields, concatenatedFieldNames, [], [], 'ignore');
Expand Down
14 changes: 8 additions & 6 deletions polyfill/lib/zoneddatetime.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -206,8 +206,12 @@ export class ZonedDateTime {
]);
const offsetNs = ES.GetOffsetNanosecondsFor(timeZoneRec, GetSlot(this, INSTANT));
const dt = ES.GetPlainDateTimeFor(timeZoneRec, GetSlot(this, INSTANT), GetSlot(this, CALENDAR), offsetNs);
const fieldNames = ES.CalendarFields(calendarRec, ['day', 'month', 'monthCode', 'year']);
let fields = ES.PrepareTemporalFields(dt, fieldNames, []);
let { fields, fieldNames } = ES.PrepareCalendarFieldsAndFieldNames(calendarRec, dt, [
'day',
'month',
'monthCode',
'year'
]);
fields.hour = GetSlot(dt, ISO_HOUR);
fields.minute = GetSlot(dt, ISO_MINUTE);
fields.second = GetSlot(dt, ISO_SECOND);
Expand Down Expand Up @@ -604,16 +608,14 @@ export class ZonedDateTime {
if (!ES.IsTemporalZonedDateTime(this)) throw new TypeError('invalid receiver');
const calendarRec = new CalendarMethodRecord(GetSlot(this, CALENDAR), ['fields', 'yearMonthFromFields']);
const dt = dateTime(this);
const fieldNames = ES.CalendarFields(calendarRec, ['monthCode', 'year']);
const fields = ES.PrepareTemporalFields(dt, fieldNames, []);
const fields = ES.PrepareCalendarFields(calendarRec, dt, ['monthCode', 'year'], [], []);
return ES.CalendarYearMonthFromFields(calendarRec, fields);
}
toPlainMonthDay() {
if (!ES.IsTemporalZonedDateTime(this)) throw new TypeError('invalid receiver');
const calendarRec = new CalendarMethodRecord(GetSlot(this, CALENDAR), ['fields', 'monthDayFromFields']);
const dt = dateTime(this);
const fieldNames = ES.CalendarFields(calendarRec, ['day', 'monthCode']);
const fields = ES.PrepareTemporalFields(dt, fieldNames, []);
const fields = ES.PrepareCalendarFields(calendarRec, dt, ['day', 'monthCode'], [], []);
return ES.CalendarMonthDayFromFields(calendarRec, fields);
}
getISOFields() {
Expand Down
Loading
Loading