Skip to content

Commit

Permalink
Editorial: Refactor simple case of CalendarFields + PrepareTemporalFi…
Browse files Browse the repository at this point in the history
…elds

This adds a new operation PrepareCalendarFields which consists of calling
the `fields` method of a calendar with a list of calendar property names,
appending a list of non-calendar property names to the result, followed by
calling PrepareTemporalFields to read all of the given properties from the
given object.

See: #2630
  • Loading branch information
ptomato authored and Ms2ger committed Apr 26, 2024
1 parent a6aefdc commit d8e1946
Show file tree
Hide file tree
Showing 10 changed files with 101 additions and 72 deletions.
79 changes: 46 additions & 33 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,27 @@ export function PrepareTemporalFields(
return result;
}

export function PrepareCalendarFields(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);
return PrepareTemporalFields(bag, fieldNames, requiredFieldNames);
}

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 +1281,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 +1343,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 +1433,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 +1504,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 +1618,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
6 changes: 2 additions & 4 deletions polyfill/lib/plaindate.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -221,15 +221,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
6 changes: 2 additions & 4 deletions polyfill/lib/plaindatetime.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -408,15 +408,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
6 changes: 2 additions & 4 deletions polyfill/lib/zoneddatetime.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -604,16 +604,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
41 changes: 38 additions & 3 deletions spec/abstractops.html
Original file line number Diff line number Diff line change
Expand Up @@ -741,9 +741,7 @@ <h1>
1. Return the Record { [[PlainRelativeTo]]: _plainDate_, [[ZonedRelativeTo]]: *undefined*, [[TimeZoneRec]]: *undefined* }.
1. Let _calendar_ be ? GetTemporalCalendarSlotValueWithISODefault(_value_).
1. Let _calendarRec_ be ? CreateCalendarMethodsRecord(_calendar_, « ~date-from-fields~, ~fields~ »).
1. Let _fieldNames_ be ? CalendarFields(_calendarRec_, « *"day"*, *"month"*, *"monthCode"*, *"year"* »).
1. Append *"hour"*, *"microsecond"*, *"millisecond"*, *"minute"*, *"nanosecond"*, *"offset"*, *"second"*, and *"timeZone"* to _fieldNames_.
1. Let _fields_ be ? PrepareTemporalFields(_value_, _fieldNames_, «»).
1. Let _fields_ be ? PrepareCalendarFields(_calendarRec_, _value_, « *"day"*, *"month"*, *"monthCode"*, *"year"* », « *"hour"*, *"microsecond"*, *"millisecond"*, *"minute"*, *"nanosecond"*, *"offset"*, *"second"*, *"timeZone"* », «»).
1. Let _dateOptions_ be OrdinaryObjectCreate(*null*).
1. Perform ! CreateDataPropertyOrThrow(_dateOptions_, *"overflow"*, *"constrain"*).
1. Let _result_ be ? InterpretTemporalDateTimeFields(_calendarRec_, _fields_, _dateOptions_).
Expand Down Expand Up @@ -2115,6 +2113,43 @@ <h1>Calendar Field Descriptor Record</h1>
</emu-clause>
</emu-clause>

<emu-clause id="sec-temporal-preparecalendarfields" type="abstract operation">
<h1>
PrepareCalendarFields (
_calendarRec_: a Calendar Methods Record,
_fields_: an Object,
_calendarFieldNames_: a List of property names,
_nonCalendarFieldNames_: a List of property names,
_requiredFieldNames_: a List of property names,
): either a normal completion containing an Object, or a throw completion
</h1>
<dl class="header">
<dt>description</dt>
<dd>
It returns the result of reading from _fields_ all of the property names given by _calendarFieldNames_ and _nonCalendarFieldNames_, plus any extra property names returned by the `fields` method of the given calendar when passed _calendarFieldNames_.
The returned Object has a null prototype, and an own data property for each property name that corresponds with a non-*undefined* property of the same name on _fields_ used as the input for relevant conversion.
</dd>
</dl>
<emu-alg>
1. Assert: _calendarFieldNames_ contains zero or one of each of the Strings *"day"*, *"month"*, *"monthCode"*, and *"year"*, in that order.
1. Assert: _nonCalendarFieldNames_ contains zero or one of each of the Strings *"hour"*, *"microsecond"*, *"millisecond"*, *"minute"*, *"nanosecond"*, *"offset"*, *"second"*, and *"timeZone"*, in that order.
1. Assert: _requiredFieldNames_ contains zero or one of each of the elements of _calendarFieldNames_ and _nonCalendarFieldNames_.
1. If CalendarMethodsRecordIsBuiltin(_calendarRec_) is *true*, then
1. NOTE: %Temporal.Calendar.prototype.fields% is not called in this clause in order to avoid an unnecessary observable Array iteration.
1. Let _fieldNames_ be _calendarFieldNames_.
1. If _calendarRec_.[[Receiver]] is not *"iso8601"*, then
1. Let _extraFieldDescriptors_ be CalendarFieldDescriptors(_calendarRec_.[[Receiver]], _calendarFieldNames_).
1. For each Calendar Field Descriptor Record _desc_ of _extraFieldDescriptors_, do
1. Append _desc_.[[Property]] to _fieldNames_.
1. Else,
1. Let _calendarFieldsArray_ be ? CalendarMethodsRecordCall(_calendarRec_, ~fields~, « CreateArrayFromList(_calendarFieldNames_) »).
1. Let _iteratorRecord_ be ? GetIterator(_calendarFieldsArray_, ~sync~).
1. Let _fieldNames_ be ? IteratorToListOfType(_iteratorRecord_, « String »).
1. Set _fieldNames_ to the list-concatenation of _fieldNames_ and _nonCalendarFieldNames_.
1. Return ? PrepareTemporalFields(_fields_, _fieldNames_, _requiredFieldNames_).
</emu-alg>
</emu-clause>

<emu-clause id="sec-temporal-getdifferencesettings" type="abstract operation">
<h1>
GetDifferenceSettings (
Expand Down
9 changes: 3 additions & 6 deletions spec/plaindate.html
Original file line number Diff line number Diff line change
Expand Up @@ -315,8 +315,7 @@ <h1>Temporal.PlainDate.prototype.toPlainYearMonth ( )</h1>
1. Let _temporalDate_ be the *this* value.
1. Perform ? RequireInternalSlot(_temporalDate_, [[InitializedTemporalDate]]).
1. Let _calendarRec_ be ? CreateCalendarMethodsRecord(_temporalDate_.[[Calendar]], « ~fields~, ~year-month-from-fields~ »).
1. Let _fieldNames_ be ? CalendarFields(_calendarRec_, « *"monthCode"*, *"year"* »).
1. Let _fields_ be ? PrepareTemporalFields(_temporalDate_, _fieldNames_, «»).
1. Let _fields_ be ? PrepareCalendarFields(_calendarRec_, _temporalDate_, « *"monthCode"*, *"year"* », «», «»).
1. Return ? CalendarYearMonthFromFields(_calendarRec_, _fields_).
1. NOTE: The call to CalendarYearMonthFromFields is necessary in order to create a PlainYearMonth object with the [[ISOYear]], [[ISOMonth]], and [[ISODay]] internal slots set correctly.
</emu-alg>
Expand All @@ -331,8 +330,7 @@ <h1>Temporal.PlainDate.prototype.toPlainMonthDay ( )</h1>
1. Let _temporalDate_ be the *this* value.
1. Perform ? RequireInternalSlot(_temporalDate_, [[InitializedTemporalDate]]).
1. Let _calendarRec_ be ? CreateCalendarMethodsRecord(_temporalDate_.[[Calendar]], « ~fields~, ~month-day-from-fields~ »).
1. Let _fieldNames_ be ? CalendarFields(_calendarRec_, « *"day"*, *"monthCode"* »).
1. Let _fields_ be ? PrepareTemporalFields(_temporalDate_, _fieldNames_, «»).
1. Let _fields_ be ? PrepareCalendarFields(_calendarRec_, _temporalDate_, « *"day"*, *"monthCode"* », «», «»).
1. Return ? CalendarMonthDayFromFields(_calendarRec_, _fields_).
1. NOTE: The call to CalendarMonthDayFromFields is necessary in order to create a PlainMonthDay object with the [[ISOYear]], [[ISOMonth]], and [[ISODay]] internal slots set correctly.
</emu-alg>
Expand Down Expand Up @@ -763,8 +761,7 @@ <h1>
1. Return ! CreateTemporalDate(_item_.[[ISOYear]], _item_.[[ISOMonth]], _item_.[[ISODay]], _item_.[[Calendar]]).
1. Let _calendar_ be ? GetTemporalCalendarSlotValueWithISODefault(_item_).
1. Let _calendarRec_ be ? CreateCalendarMethodsRecord(_calendar_, « ~date-from-fields~, ~fields~ »).
1. Let _fieldNames_ be ? CalendarFields(_calendarRec_, « *"day"*, *"month"*, *"monthCode"*, *"year"* »).
1. Let _fields_ be ? PrepareTemporalFields(_item_, _fieldNames_, «»).
1. Let _fields_ be ? PrepareCalendarFields(_calendarRec_, _item_, « *"day"*, *"month"*, *"monthCode"*, *"year"* », «», «»).
1. Return ? CalendarDateFromFields(_calendarRec_, _fields_, _options_).
1. If _item_ is not a String, throw a *TypeError* exception.
1. Let _result_ be ? ParseTemporalDateString(_item_).
Expand Down
10 changes: 3 additions & 7 deletions spec/plaindatetime.html
Original file line number Diff line number Diff line change
Expand Up @@ -662,8 +662,7 @@ <h1>Temporal.PlainDateTime.prototype.toPlainYearMonth ( )</h1>
1. Let _dateTime_ be the *this* value.
1. Perform ? RequireInternalSlot(_dateTime_, [[InitializedTemporalDateTime]]).
1. Let _calendarRec_ be ? CreateCalendarMethodsRecord(_dateTime_.[[Calendar]], « ~fields~, ~year-month-from-fields~ »).
1. Let _fieldNames_ be ? CalendarFields(_calendarRec_, « *"monthCode"*, *"year"* »).
1. Let _fields_ be ? PrepareTemporalFields(_dateTime_, _fieldNames_, «»).
1. Let _fields_ be ? PrepareCalendarFields(_calendarRec_, _dateTime_, « *"monthCode"*, *"year"* », «», «»).
1. Return ? CalendarYearMonthFromFields(_calendarRec_, _fields_).
1. NOTE: The call to CalendarYearMonthFromFields is necessary in order to create a PlainYearMonth object with the [[ISOYear]], [[ISOMonth]], and [[ISODay]] internal slots set correctly.
</emu-alg>
Expand All @@ -678,8 +677,7 @@ <h1>Temporal.PlainDateTime.prototype.toPlainMonthDay ( )</h1>
1. Let _dateTime_ be the *this* value.
1. Perform ? RequireInternalSlot(_dateTime_, [[InitializedTemporalDateTime]]).
1. Let _calendarRec_ be ? CreateCalendarMethodsRecord(_dateTime_.[[Calendar]], « ~fields~, ~month-day-from-fields~ »).
1. Let _fieldNames_ be ? CalendarFields(_calendarRec_, « *"day"*, *"monthCode"* »).
1. Let _fields_ be ? PrepareTemporalFields(_dateTime_, _fieldNames_, «»).
1. Let _fields_ be ? PrepareCalendarFields(_calendarRec_, _dateTime_, « *"day"*, *"monthCode"* », «», «»).
1. Return ? CalendarMonthDayFromFields(_calendarRec_, _fields_).
1. NOTE: The call to CalendarMonthDayFromFields is necessary in order to create a PlainMonthDay object with the [[ISOYear]], [[ISOMonth]], and [[ISODay]] internal slots set correctly.
</emu-alg>
Expand Down Expand Up @@ -1016,9 +1014,7 @@ <h1>
1. Return ? CreateTemporalDateTime(_item_.[[ISOYear]], _item_.[[ISOMonth]], _item_.[[ISODay]], 0, 0, 0, 0, 0, 0, _item_.[[Calendar]]).
1. Let _calendar_ be ? GetTemporalCalendarSlotValueWithISODefault(_item_).
1. Let _calendarRec_ be ? CreateCalendarMethodsRecord(_calendar_, « ~date-from-fields~, ~fields~ »).
1. Let _fieldNames_ be ? CalendarFields(_calendarRec_, « *"day"*, *"month"*, *"monthCode"*, *"year"* »).
1. Append *"hour"*, *"microsecond"*, *"millisecond"*, *"minute"*, *"nanosecond"*, and *"second"* to _fieldNames_.
1. Let _fields_ be ? PrepareTemporalFields(_item_, _fieldNames_, «»).
1. Let _fields_ be ? PrepareCalendarFields(_calendarRec_, _item_, « *"day"*, *"month"*, *"monthCode"*, *"year"* », « *"hour"*, *"microsecond"*, *"millisecond"*, *"minute"*, *"nanosecond"*, *"second"* », «»).
1. Let _result_ be ? InterpretTemporalDateTimeFields(_calendarRec_, _fields_, _resolvedOptions_).
1. Else,
1. If _item_ is not a String, throw a *TypeError* exception.
Expand Down
Loading

0 comments on commit d8e1946

Please sign in to comment.