From d0f60dc58d38c82c680a4c5686a9af045c484ae7 Mon Sep 17 00:00:00 2001 From: Richard Gibson Date: Thu, 2 Feb 2023 22:34:32 -0500 Subject: [PATCH 1/7] Normative: Isolate ISO 8601 month consistency validation Fixes #2497 --- spec/calendar.html | 43 +++++++++++++++++++++---------------------- spec/intl.html | 3 +++ 2 files changed, 24 insertions(+), 22 deletions(-) diff --git a/spec/calendar.html b/spec/calendar.html index 6df30f76e7..ef7991ae8d 100644 --- a/spec/calendar.html +++ b/spec/calendar.html @@ -834,22 +834,22 @@

ISOResolveMonth ( _fields_: an Object, - ): either a normal completion containing an integer or a throw completion + ): either a normal completion containing ~unused~ or a throw completion

description
- It validates that the *"month"* and *"monthCode"* properties of the given _fields_ are not inconsistent and merges them into an integer month which is returned, or throws an exception if both are *undefined*. + It validates that the *"month"* and *"monthCode"* properties of the given _fields_ are not inconsistent and merges them into a *"month"* property with an integral Number value, or throws an exception if both are *undefined*.
- 1. Assert: _fields_ is an ordinary object with no more and no less than the own data properties listed in . + 1. Assert: _fields_ is an ordinary Object that is not directly observable from ECMAScript code and for which the value of the [[Prototype]] internal slot is *null* and every property is a data property whose value is a primitive value. 1. Let _month_ be ! Get(_fields_, *"month"*). 1. Assert: _month_ is *undefined* or _month_ is a Number. 1. Let _monthCode_ be ! Get(_fields_, *"monthCode"*). 1. If _monthCode_ is *undefined*, then 1. If _month_ is *undefined*, throw a *TypeError* exception. - 1. Return ℝ(_month_). + 1. Return ~unused~. 1. Assert: _monthCode_ is a String. 1. NOTE: The ISO 8601 calendar does not include leap months. 1. If the length of _monthCode_ is not 3, throw a *RangeError* exception. @@ -859,7 +859,8 @@

1. Let _monthCodeInteger_ be ! ToIntegerOrInfinity(_monthCodeDigits_). 1. Assert: SameValue(_monthCode_, ISOMonthCode(_monthCodeInteger_)) is *true*. 1. If _month_ is not *undefined* and ℝ(_month_) ≠ _monthCodeInteger_, throw a *RangeError* exception. - 1. Return _monthCodeInteger_. + 1. Perform ! Set(_fields_, *"month"*, 𝔽(_monthCodeInteger_), *false*). + 1. Return ~unused~. @@ -872,11 +873,10 @@

ISODateFromFields ( _fields_, _overflow_ )

1. Assert: Type(_fields_) is Object. 1. Assert: _overflow_ is either *"constrain"* or *"reject"*. 1. Let _year_ be ! Get(_fields_, *"year"*). - 1. Assert: Type(_year_) is Number. - 1. Let _month_ be ? ISOResolveMonth(_fields_). + 1. Let _month_ be ! Get(_fields_, *"month"*). 1. Let _day_ be ! Get(_fields_, *"day"*). - 1. Assert: Type(_day_) is Number. - 1. Return ? RegulateISODate(ℝ(_year_), _month_, ℝ(_day_), _overflow_). + 1. Assert: _year_, _month_, and _day_ are all Numbers. + 1. Return ? RegulateISODate(ℝ(_year_), ℝ(_month_), ℝ(_day_), _overflow_).
@@ -890,9 +890,9 @@

ISOYearMonthFromFields ( _fields_, _overflow_ )

1. Assert: Type(_fields_) is Object. 1. Assert: _overflow_ is either *"constrain"* or *"reject"*. 1. Let _year_ be ! Get(_fields_, *"year"*). - 1. Assert: Type(_year_) is Number. - 1. Let _month_ be ? ISOResolveMonth(_fields_). - 1. Let _result_ be ? RegulateISOYearMonth(ℝ(_year_), _month_, _overflow_). + 1. Let _month_ be ! Get(_fields_, *"month"*). + 1. Assert: _year_ and _month_ are Numbers. + 1. Let _result_ be ? RegulateISOYearMonth(ℝ(_year_), ℝ(_month_), _overflow_). 1. Return the Record { [[Year]]: _result_.[[Year]], [[Month]]: _result_.[[Month]], @@ -910,19 +910,15 @@

ISOMonthDayFromFields ( _fields_, _overflow_ )

1. Assert: Type(_fields_) is Object. 1. Assert: _overflow_ is either *"constrain"* or *"reject"*. 1. Let _month_ be ! Get(_fields_, *"month"*). - 1. Let _monthCode_ be ! Get(_fields_, *"monthCode"*). - 1. Let _year_ be ! Get(_fields_, *"year"*). - 1. If _month_ is not *undefined*, and _monthCode_ and _year_ are both *undefined*, then - 1. Throw a *TypeError* exception. - 1. Set _month_ to ? ISOResolveMonth(_fields_). 1. Let _day_ be ! Get(_fields_, *"day"*). - 1. Assert: Type(_day_) is Number. + 1. Assert: _month_ and _day_ are Numbers. + 1. Let _year_ be ! Get(_fields_, *"year"*). 1. Let _referenceISOYear_ be 1972 (the first leap year after the epoch). - 1. If _monthCode_ is *undefined*, then - 1. Assert: Type(_year_) is Number. - 1. Let _result_ be ? RegulateISODate(ℝ(_year_), _month_, ℝ(_day_), _overflow_). + 1. If _year_ is *undefined*, then + 1. Let _result_ be ? RegulateISODate(_referenceISOYear_, ℝ(_month_), ℝ(_day_), _overflow_). 1. Else, - 1. Let _result_ be ? RegulateISODate(_referenceISOYear_, _month_, ℝ(_day_), _overflow_). + 1. Assert: _year_ is a Number. + 1. Let _result_ be ? RegulateISODate(ℝ(_year_), ℝ(_month_), ℝ(_day_), _overflow_). 1. Return the Record { [[Month]]: _result_.[[Month]], [[Day]]: _result_.[[Day]], @@ -1113,6 +1109,7 @@

Temporal.Calendar.prototype.dateFromFields ( _fields_ [ , _options_ ] )

1. Set _options_ to ? GetOptionsObject(_options_). 1. Set _fields_ to ? PrepareTemporalFields(_fields_, « *"day"*, *"month"*, *"monthCode"*, *"year"* », « *"year"*, *"day"* »). 1. Let _overflow_ be ? ToTemporalOverflow(_options_). + 1. Perform ? ISOResolveMonth(_fields_). 1. Let _result_ be ? ISODateFromFields(_fields_, _overflow_). 1. Return ? CreateTemporalDate(_result_.[[Year]], _result_.[[Month]], _result_.[[Day]], *"iso8601"*). @@ -1135,6 +1132,7 @@

Temporal.Calendar.prototype.yearMonthFromFields ( _fields_ [ , _options_ ] ) 1. Set _options_ to ? GetOptionsObject(_options_). 1. Set _fields_ to ? PrepareTemporalFields(_fields_, « *"month"*, *"monthCode"*, *"year"* », « *"year"* »). 1. Let _overflow_ be ? ToTemporalOverflow(_options_). + 1. Perform ? ISOResolveMonth(_fields_). 1. Let _result_ be ? ISOYearMonthFromFields(_fields_, _overflow_). 1. Return ? CreateTemporalYearMonth(_result_.[[Year]], _result_.[[Month]], *"iso8601"*, _result_.[[ReferenceISODay]]). @@ -1157,6 +1155,7 @@

Temporal.Calendar.prototype.monthDayFromFields ( _fields_ [ , _options_ ] )< 1. Set _options_ to ? GetOptionsObject(_options_). 1. Set _fields_ to ? PrepareTemporalFields(_fields_, « *"day"*, *"month"*, *"monthCode"*, *"year"* », « *"day"* »). 1. Let _overflow_ be ? ToTemporalOverflow(_options_). + 1. Perform ? ISOResolveMonth(_fields_). 1. Let _result_ be ? ISOMonthDayFromFields(_fields_, _overflow_). 1. Return ? CreateTemporalMonthDay(_result_.[[Month]], _result_.[[Day]], *"iso8601"*, _result_.[[ReferenceISOYear]]). diff --git a/spec/intl.html b/spec/intl.html index 3c62068c29..71a47ba653 100644 --- a/spec/intl.html +++ b/spec/intl.html @@ -1887,6 +1887,7 @@

Temporal.Calendar.prototype.dateFromFields ( _fields_ [ , _options_ ] )

1. Set _fields_ to ? PrepareTemporalFields(_fields_, _fieldNames_, « »). 1. Let _overflow_ be ? ToTemporalOverflow(_options_). 1. If _calendar_.[[Identifier]] is *"iso8601"*, then + 1. Perform ? ISOResolveMonth(_fields_). 1. Let _result_ be ? ISODateFromFields(_fields_, _overflow_). 1. Else, 1. Perform ? CalendarResolveFields(_calendar_.[[Identifier]], _fields_, ~date~). @@ -1916,6 +1917,7 @@

Temporal.Calendar.prototype.yearMonthFromFields ( _fields_ [ , _options_ ] ) 1. Perform ! CreateDataPropertyOrThrow(_fields_, *"day"*, 𝔽(_firstDayIndex_)). 1. Let _overflow_ be ? ToTemporalOverflow(_options_). 1. If _calendar_.[[Identifier]] is *"iso8601"*, then + 1. Perform ? ISOResolveMonth(_fields_). 1. Let _result_ be ? ISOYearMonthFromFields(_fields_, _overflow_). 1. Else, 1. Perform ? CalendarResolveFields(_calendar_.[[Identifier]], _fields_, ~year-month~). @@ -1944,6 +1946,7 @@

Temporal.Calendar.prototype.monthDayFromFields ( _fields_ [ , _options_ ] )< 1. Set _fields_ to ? PrepareTemporalFields(_fields_, _fieldNames_, « »). 1. Let _overflow_ be ? ToTemporalOverflow(_options_). 1. If _calendar_.[[Identifier]] is *"iso8601"*, then + 1. Perform ? ISOResolveMonth(_fields_). 1. Let _result_ be ? ISOMonthDayFromFields(_fields_, _overflow_). 1. Else, 1. Perform ? CalendarResolveFields(_calendar_.[[Identifier]], _fields_, ~month-day~). From 170b35e8f70ab956b4a2804df2c70c250a674dcf Mon Sep 17 00:00:00 2001 From: Richard Gibson Date: Fri, 3 Feb 2023 10:03:04 -0500 Subject: [PATCH 2/7] Editorial: Relax assertion to account for `timeZone: object` --- spec/calendar.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/calendar.html b/spec/calendar.html index ef7991ae8d..6f9d42eddc 100644 --- a/spec/calendar.html +++ b/spec/calendar.html @@ -843,7 +843,7 @@

- 1. Assert: _fields_ is an ordinary Object that is not directly observable from ECMAScript code and for which the value of the [[Prototype]] internal slot is *null* and every property is a data property whose value is a primitive value. + 1. Assert: _fields_ is an ordinary Object that is not directly observable from ECMAScript code and for which the value of the [[Prototype]] internal slot is *null* and every property is a data property. 1. Let _month_ be ! Get(_fields_, *"month"*). 1. Assert: _month_ is *undefined* or _month_ is a Number. 1. Let _monthCode_ be ! Get(_fields_, *"monthCode"*). From 0fc94f85b9126b850ff3e160aa6105d944043711 Mon Sep 17 00:00:00 2001 From: Richard Gibson Date: Fri, 3 Feb 2023 10:21:52 -0500 Subject: [PATCH 3/7] Normative: Remove the ECMA-402 PrepareTemporalFields override "era" and "eraYear" are still checked in alphabetical order along with other fields when the calendar specifies their relevance, but ensuring both-or-neither and consistency between the pair and "year" is no longer part of the operation (although it remains part of CalendarResolveFields along with similar checks for "month" and "monthCode"). Fixes #2465 --- spec/abstractops.html | 51 ++++++++++++++++++++++++++++++++-------- spec/calendar.html | 3 ++- spec/intl.html | 32 +++++++++++++------------ spec/plainmonthday.html | 2 +- spec/plainyearmonth.html | 2 +- 5 files changed, 62 insertions(+), 28 deletions(-) diff --git a/spec/abstractops.html b/spec/abstractops.html index da1d5fcbc2..fd9a67070d 100644 --- a/spec/abstractops.html +++ b/spec/abstractops.html @@ -1804,6 +1804,7 @@

_fields_: an Object, _fieldNames_: a List of property names, _requiredFields_: ~partial~ or a List of property names, + optional _extraFieldDescriptors_: a List of Calendar Field Descriptor Records, optional _duplicateBehaviour_: ~throw~ or ~ignore~, ): either a normal completion containing an Object, or an abrupt completion

@@ -1813,12 +1814,19 @@

The returned Object has a null prototype, and an own data property for each element of _fieldNames_ that corresponds with a non-*undefined* property of the same name on _fields_ used as the input for relevant conversion. When _requiredFields_ is ~partial~, this operation throws if none of the properties are present with a non-*undefined* value. When _requiredFields_ is a List, this operation throws if any of the properties named by it are absent or undefined, and otherwise substitutes a relevant default for any absent or undefined non-required property (ensuring that the returned object has a property for each element of _fieldNames_). + When _extraFieldDescriptors_ is present, its contents are treated as an extension of . 1. If _duplicateBehaviour_ is not present, set _duplicateBehaviour_ to ~throw~. 1. Let _result_ be OrdinaryObjectCreate(*null*). 1. Let _any_ be *false*. + 1. If _extraFieldDescriptors_ is present, then + 1. For each Calendar Field Descriptor Record _desc_ of _extraFieldDescriptors_, do + 1. Assert: _fieldNames_ does not contain _desc_.[[Property]]. + 1. Append _desc_.[[Property]] to _fieldNames_. + 1. If _desc_.[[Required]] is *true* and _requiredFields_ is a List, then + 1. Append _desc_.[[Property]] to _requiredFields_. 1. Let _sortedFieldNames_ be SortStringListByCodeUnit(_fieldNames_). 1. Let _previousProperty_ be *undefined*. 1. For each property name _property_ of _sortedFieldNames_, do @@ -1841,6 +1849,9 @@

1. NOTE: Non-primitive values are supported here for consistency with other fields, but such values must coerce to Strings. 1. Set _value_ to ? ToPrimitive(_value_, ~string~). 1. If _value_ is not a String, throw a *TypeError* exception. + 1. Else if _extraFieldDescriptors_ is present and _extraFieldDescriptors_ contains a Calendar Field Descriptor Record _desc_ such that _desc_.[[Property]] is _property_, then + 1. Let _converter_ be _desc_.[[Conversion]]. + 1. Set _value_ to ? _converter_(_value_). 1. Perform ! CreateDataPropertyOrThrow(_result_, _property_, _value_). 1. Else if _requiredFields_ is a List, then 1. If _requiredFields_ contains _property_, then @@ -1921,16 +1932,6 @@

~ToPrimitiveAndRequireString~ *undefined* - - *"era"* - ~ToPrimitiveAndRequireString~ - *undefined* - - - *"eraYear"* - ~ToIntegerWithTruncation~ - *undefined* - *"timeZone"* @@ -1939,6 +1940,36 @@

+ + +

Calendar Field Descriptor Record

+

A Calendar Field Descriptor Record is a Record value used to describe a calendar-specific field for use in creating and interacting with instances of Temporal types.

+

Calendar Field Descriptor Records have the fields listed in .

+ + + + + + + + + + + + + + + + + + + + + + +
Field NameValueMeaning
[[Property]]a StringThe property name associated with the field, analogous to the Property column of .
[[Conversion]]an Abstract Closure accepting a single ECMAScript language value and returning either a normal completion containing an ECMAScript language value representing purely static data or a throw completion.The means by which purported values are coerced to a static representation of the correct type (or rejected if that fails), analogous to steps indicated by the Conversion column of .
[[Required]]a BooleanWhether PrepareTemporalFields should consider the field as required when not accepting partial data.
+
+
diff --git a/spec/calendar.html b/spec/calendar.html index 6f9d42eddc..5632d17ef6 100644 --- a/spec/calendar.html +++ b/spec/calendar.html @@ -839,7 +839,8 @@

description
- It validates that the *"month"* and *"monthCode"* properties of the given _fields_ are not inconsistent and merges them into a *"month"* property with an integral Number value, or throws an exception if both are *undefined*. + It ensures that the *"month"* property of _fields_, if *undefined*, is set to the integral Number value in the inclusive interval from 1 to 12 that corresponds to the *"monthCode"* property of _fields_. + It throws an exception if both properties are *undefined*, or if either property is invalid, or if the properties are inconsistent.
diff --git a/spec/intl.html b/spec/intl.html index 71a47ba653..2cdb30bb7b 100644 --- a/spec/intl.html +++ b/spec/intl.html @@ -1754,16 +1754,16 @@

- +

- CalendarDateFields ( + CalendarFieldDescriptors ( _calendar_: a String, - _fields_: a List of Strings, - ): a List of Strings + _type_: ~date~, ~year-month~, or ~month-day~, + ): a List of Calendar Field Descriptor Records

description
-
It interprets _fields_ as the names of fields () necessary for a given operation and returns a new List by appending the names of additional relevant fields specific to the built-in calendar identified by _calendar_. For example, when _calendar_ is *"gregory"* or *"japanese"*, *"era"* and *"eraYear"* are appended if and only if _fields_ contains *"year"*.
+
It characterizes calendar-specific fields that are relevant for values of the provided _type_ in the built-in calendar identified by _calendar_. For example, *"era"* (with ToString conversion) and *"eraYear"* (with ToIntegerWithTruncation conversion) are returned when _calendar_ is *"gregory"* or *"japanese"* and _type_ is ~date~ or ~year-month~.
@@ -1883,8 +1883,8 @@

Temporal.Calendar.prototype.dateFromFields ( _fields_ [ , _options_ ] )

1. If _calendar_.[[Identifier]] is *"iso8601"*, then 1. Set _fields_ to ? PrepareTemporalFields(_fields_, _relevantFieldNames_, « *"year"*, *"day"* »). 1. Else, - 1. Let _fieldNames_ be CalendarDateFields(_calendar_.[[Identifier]], _relevantFieldNames_). - 1. Set _fields_ to ? PrepareTemporalFields(_fields_, _fieldNames_, « »). + 1. Let _calendarRelevantFieldDescriptors_ be CalendarFieldDescriptors(_calendar_.[[Identifier]], ~date~). + 1. Set _fields_ to ? PrepareTemporalFields(_fields_, _relevantFieldNames_, « », _calendarRelevantFieldDescriptors_). 1. Let _overflow_ be ? ToTemporalOverflow(_options_). 1. If _calendar_.[[Identifier]] is *"iso8601"*, then 1. Perform ? ISOResolveMonth(_fields_). @@ -1911,8 +1911,8 @@

Temporal.Calendar.prototype.yearMonthFromFields ( _fields_ [ , _options_ ] ) 1. If _calendar_.[[Identifier]] is *"iso8601"*, then 1. Set _fields_ to ? PrepareTemporalFields(_fields_, _relevantFieldNames_, « *"year"* »). 1. Else, - 1. Let _fieldNames_ be CalendarDateFields(_calendar_.[[Identifier]], _relevantFieldNames_). - 1. Set _fields_ to ? PrepareTemporalFields(_fields_, _fieldNames_, « »). + 1. Let _calendarRelevantFieldDescriptors_ be CalendarFieldDescriptors(_calendar_.[[Identifier]], ~year-month~). + 1. Set _fields_ to ? PrepareTemporalFields(_fields_, _relevantFieldNames_, « », _calendarRelevantFieldDescriptors_). 1. Let _firstDayIndex_ be the 1-based index of the first day of the month described by _fields_ (i.e., 1 unless the month's first day is skipped by this calendar.) 1. Perform ! CreateDataPropertyOrThrow(_fields_, *"day"*, 𝔽(_firstDayIndex_)). 1. Let _overflow_ be ? ToTemporalOverflow(_options_). @@ -1942,8 +1942,8 @@

Temporal.Calendar.prototype.monthDayFromFields ( _fields_ [ , _options_ ] )< 1. If _calendar_.[[Identifier]] is *"iso8601"*, then 1. Set _fields_ to ? PrepareTemporalFields(_fields_, _relevantFieldNames_, « *"day"* »). 1. Else, - 1. Let _fieldNames_ be CalendarDateFields(_calendar_.[[Identifier]], _relevantFieldNames_). - 1. Set _fields_ to ? PrepareTemporalFields(_fields_, _fieldNames_, « »). + 1. Let _calendarRelevantFieldDescriptors_ be CalendarFieldDescriptors(_calendar_.[[Identifier]], ~month-day~). + 1. Set _fields_ to ? PrepareTemporalFields(_fields_, _relevantFieldNames_, « », _calendarRelevantFieldDescriptors_). 1. Let _overflow_ be ? ToTemporalOverflow(_options_). 1. If _calendar_.[[Identifier]] is *"iso8601"*, then 1. Perform ? ISOResolveMonth(_fields_). @@ -2310,10 +2310,12 @@

Temporal.Calendar.prototype.fields ( _fields_ )

1. Let _completion_ be ThrowCompletion(a newly created *RangeError* object). 1. Return ? IteratorClose(_iteratorRecord_, _completion_). 1. Append _nextValue_ to the end of the List _fieldNames_. - 1. If _calendar_.[[Identifier]] is *"iso8601"*, then - 1. Let _result_ be _fieldNames_. - 1. Else, - 1. Let _result_ be CalendarDateFields(_calendar_.[[Identifier]], _fieldNames_). + 1. Let _result_ be _fieldNames_. + 1. If _calendar_.[[Identifier]] is not *"iso8601"*, then + 1. NOTE: Every built-in calendar preserves all input field names in output. + 1. Let _extraFieldDescriptors_ be CalendarFieldDescriptors(_calendar_.[[Identifier]], ~date~). + 1. For each Calendar Field Descriptor Record _desc_ of _extraFieldDescriptors_, do + 1. Append _desc_.[[Property]] to _result_. 1. Return CreateArrayFromList(_result_).
diff --git a/spec/plainmonthday.html b/spec/plainmonthday.html index 84c603ba3f..52fa18e418 100644 --- a/spec/plainmonthday.html +++ b/spec/plainmonthday.html @@ -249,7 +249,7 @@

Temporal.PlainMonthDay.prototype.toPlainDate ( _item_ )

1. Let _inputFields_ be ? PrepareTemporalFields(_item_, _inputFieldNames_, «»). 1. Let _mergedFields_ be ? CalendarMergeFields(_calendar_, _fields_, _inputFields_). 1. Let _concatenatedFieldNames_ be the list-concatenation of _receiverFieldNames_ and _inputFieldNames_. - 1. Set _mergedFields_ to ? PrepareTemporalFields(_mergedFields_, _concatenatedFieldNames_, «», ~ignore~). + 1. Set _mergedFields_ to ? PrepareTemporalFields(_mergedFields_, _concatenatedFieldNames_, «», «», ~ignore~). 1. Let _options_ be OrdinaryObjectCreate(*null*). 1. Perform ! CreateDataPropertyOrThrow(_options_, *"overflow"*, *"reject"*). 1. Return ? CalendarDateFromFields(_calendar_, _mergedFields_, _options_). diff --git a/spec/plainyearmonth.html b/spec/plainyearmonth.html index e69a74ca50..00ed705565 100644 --- a/spec/plainyearmonth.html +++ b/spec/plainyearmonth.html @@ -378,7 +378,7 @@

Temporal.PlainYearMonth.prototype.toPlainDate ( _item_ )

1. Let _inputFields_ be ? PrepareTemporalFields(_item_, _inputFieldNames_, «»). 1. Let _mergedFields_ be ? CalendarMergeFields(_calendar_, _fields_, _inputFields_). 1. Let _concatenatedFieldNames_ be the list-concatenation of _receiverFieldNames_ and _inputFieldNames_. - 1. Set _mergedFields_ to ? PrepareTemporalFields(_mergedFields_, _concatenatedFieldNames_, «», ~ignore~). + 1. Set _mergedFields_ to ? PrepareTemporalFields(_mergedFields_, _concatenatedFieldNames_, «», «», ~ignore~). 1. Let _options_ be OrdinaryObjectCreate(*null*). 1. Perform ! CreateDataPropertyOrThrow(_options_, *"overflow"*, *"reject"*). 1. Return ? CalendarDateFromFields(_calendar_, _mergedFields_, _options_). From a25808e339eb63a4ea92fd0894060dceabc13fce Mon Sep 17 00:00:00 2001 From: Aditi Date: Wed, 17 May 2023 00:17:16 +0530 Subject: [PATCH 4/7] Normative: Determine Temporal type in the ECMA-402 Calendar `fields` method --- spec/intl.html | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/spec/intl.html b/spec/intl.html index 2cdb30bb7b..b3de3a2cc1 100644 --- a/spec/intl.html +++ b/spec/intl.html @@ -2313,7 +2313,14 @@

Temporal.Calendar.prototype.fields ( _fields_ )

1. Let _result_ be _fieldNames_. 1. If _calendar_.[[Identifier]] is not *"iso8601"*, then 1. NOTE: Every built-in calendar preserves all input field names in output. - 1. Let _extraFieldDescriptors_ be CalendarFieldDescriptors(_calendar_.[[Identifier]], ~date~). + 1. Let _type_ be *undefined*. + 1. If _fieldNames_ does not contain *"year"*, then + 1. Set _type_ to ~month-day~. + 1. Else if _fieldNames_ does not contain *"day"*, then + 1. Set _type_ to ~year-month~. + 1. Else, + 1. Set _type_ to ~date~. + 1. Let _extraFieldDescriptors_ be CalendarFieldDescriptors(_calendar_.[[Identifier]], type). 1. For each Calendar Field Descriptor Record _desc_ of _extraFieldDescriptors_, do 1. Append _desc_.[[Property]] to _result_. 1. Return CreateArrayFromList(_result_). From e37fd0079dd7f3593009fc62a9b6c1bc023779e3 Mon Sep 17 00:00:00 2001 From: Aditi Date: Wed, 17 May 2023 19:53:20 +0530 Subject: [PATCH 5/7] Polyfill: Align with spec --- polyfill/lib/calendar.mjs | 28 ++++++++++++++++++++-------- polyfill/lib/ecmascript.mjs | 23 ++++++++++++++++++++--- polyfill/lib/plainmonthday.mjs | 2 +- polyfill/lib/plainyearmonth.mjs | 2 +- 4 files changed, 42 insertions(+), 13 deletions(-) diff --git a/polyfill/lib/calendar.mjs b/polyfill/lib/calendar.mjs index 65f79d7841..b53d5d1c3e 100644 --- a/polyfill/lib/calendar.mjs +++ b/polyfill/lib/calendar.mjs @@ -311,11 +311,10 @@ impl['iso8601'] = { if (fields.month !== undefined && fields.year === undefined && fields.monthCode === undefined) { throw new TypeError('either year or monthCode required with month'); } - const useYear = fields.monthCode === undefined; const referenceISOYear = 1972; fields = resolveNonLunisolarMonth(fields); let { month, day, year } = fields; - ({ month, day } = ES.RegulateISODate(useYear ? year : referenceISOYear, month, day, overflow)); + ({ month, day } = ES.RegulateISODate(year !== undefined ? year : referenceISOYear, month, day, overflow)); return ES.CreateTemporalMonthDay(month, day, calendarSlotValue, referenceISOYear); }, fields(fields) { @@ -1922,10 +1921,21 @@ const helperDangi = ObjectAssign({}, { ...helperChinese, id: 'dangi' }); * ISO and non-ISO implementations vs. code that was very different. */ const nonIsoGeneralImpl = { + CalendarFieldDescriptors(type) { + let fieldDescriptors = []; + if (type !== 'month-day') { + fieldDescriptors = [ + { property: 'era', conversion: ES.ToString, required: false }, + { property: 'eraYear', conversion: ES.ToIntegerOrInfinity, required: false } + ]; + } + return fieldDescriptors; + }, dateFromFields(fields, options, calendarSlotValue) { const cache = new OneObjectCache(); - const fieldNames = this.fields(['day', 'month', 'monthCode', 'year']); - fields = ES.PrepareTemporalFields(fields, fieldNames, []); + const fieldNames = ['day', 'month', 'monthCode', 'year']; + const extraFieldDescriptors = this.CalendarFieldDescriptors('date'); + fields = ES.PrepareTemporalFields(fields, fieldNames, [], extraFieldDescriptors); const overflow = ES.ToTemporalOverflow(options); const { year, month, day } = this.helper.calendarToIsoDate(fields, overflow, cache); const result = ES.CreateTemporalDate(year, month, day, calendarSlotValue); @@ -1934,8 +1944,9 @@ const nonIsoGeneralImpl = { }, yearMonthFromFields(fields, options, calendarSlotValue) { const cache = new OneObjectCache(); - const fieldNames = this.fields(['month', 'monthCode', 'year']); - fields = ES.PrepareTemporalFields(fields, fieldNames, []); + const fieldNames = ['month', 'monthCode', 'year']; + const extraFieldDescriptors = this.CalendarFieldDescriptors('year-month'); + fields = ES.PrepareTemporalFields(fields, fieldNames, [], extraFieldDescriptors); const overflow = ES.ToTemporalOverflow(options); const { year, month, day } = this.helper.calendarToIsoDate({ ...fields, day: 1 }, overflow, cache); const result = ES.CreateTemporalYearMonth(year, month, calendarSlotValue, /* referenceISODay = */ day); @@ -1946,8 +1957,9 @@ const nonIsoGeneralImpl = { const cache = new OneObjectCache(); // For lunisolar calendars, either `monthCode` or `year` must be provided // because `month` is ambiguous without a year or a code. - const fieldNames = this.fields(['day', 'month', 'monthCode', 'year']); - fields = ES.PrepareTemporalFields(fields, fieldNames, []); + const fieldNames = ['day', 'month', 'monthCode', 'year']; + const extraFieldDescriptors = this.CalendarFieldDescriptors('date'); + fields = ES.PrepareTemporalFields(fields, fieldNames, [], extraFieldDescriptors); const overflow = ES.ToTemporalOverflow(options); const { year, month, day } = this.helper.monthDayFromFields(fields, overflow, cache); // `year` is a reference year where this month/day exists in this calendar diff --git a/polyfill/lib/ecmascript.mjs b/polyfill/lib/ecmascript.mjs index 4606d36a3c..a9cc3b3f03 100644 --- a/polyfill/lib/ecmascript.mjs +++ b/polyfill/lib/ecmascript.mjs @@ -3,6 +3,7 @@ const ArrayIncludes = Array.prototype.includes; const ArrayPrototypePush = Array.prototype.push; const ArrayPrototypeSort = Array.prototype.sort; +const ArrayPrototypeFind = Array.prototype.find; const IntlDateTimeFormat = globalThis.Intl.DateTimeFormat; const IntlSupportedValuesOf = globalThis.Intl.supportedValuesOf; const MathAbs = Math.abs; @@ -186,8 +187,6 @@ const BUILTIN_CASTS = new Map([ ['milliseconds', ToIntegerIfIntegral], ['microseconds', ToIntegerIfIntegral], ['nanoseconds', ToIntegerIfIntegral], - ['era', ToPrimitiveAndRequireString], - ['eraYear', ToIntegerOrInfinity], ['offset', ToPrimitiveAndRequireString] ]); @@ -1105,11 +1104,21 @@ export function PrepareTemporalFields( bag, fields, requiredFields, + extraFieldDescriptors = [], duplicateBehaviour = 'throw', { emptySourceErrorMessage = 'no supported properties found' } = {} ) { const result = ObjectCreate(null); let any = false; + if (extraFieldDescriptors) { + for (let index = 0; index < extraFieldDescriptors.length; index++) { + let desc = extraFieldDescriptors[index]; + Call(ArrayPrototypePush, fields, [desc.property]); + if (desc.required === true && requiredFields !== 'partial') { + Call(ArrayPrototypePush, requiredFields, [desc.property]); + } + } + } Call(ArrayPrototypeSort, fields, []); let previousProperty = undefined; for (let index = 0; index < fields.length; index++) { @@ -1123,6 +1132,14 @@ export function PrepareTemporalFields( any = true; if (BUILTIN_CASTS.has(property)) { value = BUILTIN_CASTS.get(property)(value); + } else if (extraFieldDescriptors) { + const matchingDescriptor = Call(ArrayPrototypeFind, extraFieldDescriptors, [ + (desc) => desc.property === property + ]); + if (matchingDescriptor) { + const convertor = matchingDescriptor.conversion; + value = convertor(value); + } } result[property] = value; } else if (requiredFields !== 'partial') { @@ -1145,7 +1162,7 @@ export function PrepareTemporalFields( export function ToTemporalTimeRecord(bag, completeness = 'complete') { const fields = ['hour', 'microsecond', 'millisecond', 'minute', 'nanosecond', 'second']; - const partial = PrepareTemporalFields(bag, fields, 'partial', { emptySourceErrorMessage: 'invalid time-like' }); + const partial = PrepareTemporalFields(bag, fields, 'partial', undefined, undefined, { emptySourceErrorMessage: 'invalid time-like' }); const result = {}; for (let index = 0; index < fields.length; index++) { const field = fields[index]; diff --git a/polyfill/lib/plainmonthday.mjs b/polyfill/lib/plainmonthday.mjs index 8b2fe4c91e..a80a192072 100644 --- a/polyfill/lib/plainmonthday.mjs +++ b/polyfill/lib/plainmonthday.mjs @@ -83,7 +83,7 @@ export class PlainMonthDay { const inputFields = ES.PrepareTemporalFields(item, inputFieldNames, []); let mergedFields = ES.CalendarMergeFields(calendar, fields, inputFields); const concatenatedFieldNames = ES.Call(ArrayPrototypeConcat, receiverFieldNames, inputFieldNames); - mergedFields = ES.PrepareTemporalFields(mergedFields, concatenatedFieldNames, [], 'ignore'); + mergedFields = ES.PrepareTemporalFields(mergedFields, concatenatedFieldNames, [], [], 'ignore'); const options = ObjectCreate(null); options.overflow = 'reject'; return ES.CalendarDateFromFields(calendar, mergedFields, options); diff --git a/polyfill/lib/plainyearmonth.mjs b/polyfill/lib/plainyearmonth.mjs index df5d2268e3..bcaea97159 100644 --- a/polyfill/lib/plainyearmonth.mjs +++ b/polyfill/lib/plainyearmonth.mjs @@ -125,7 +125,7 @@ export class PlainYearMonth { const inputFields = ES.PrepareTemporalFields(item, inputFieldNames, []); let mergedFields = ES.CalendarMergeFields(calendar, fields, inputFields); const concatenatedFieldNames = ES.Call(ArrayPrototypeConcat, receiverFieldNames, inputFieldNames); - mergedFields = ES.PrepareTemporalFields(mergedFields, concatenatedFieldNames, [], 'ignore'); + mergedFields = ES.PrepareTemporalFields(mergedFields, concatenatedFieldNames, [], [], 'ignore'); const options = ObjectCreate(null); options.overflow = 'reject'; return ES.CalendarDateFromFields(calendar, mergedFields, options); From 13e30b630f3459e7abac9a21477233ecf5989fe7 Mon Sep 17 00:00:00 2001 From: Richard Gibson Date: Tue, 15 Aug 2023 18:49:10 -0400 Subject: [PATCH 6/7] Normative: Remove type determination from the ECMA-402 Calendar `fields` algorithm Keep it in the implementation-defined CalendarFieldDescriptors operation --- polyfill/lib/calendar.mjs | 26 ++++++++++++++++++++------ polyfill/lib/ecmascript.mjs | 14 +++++++++++++- spec/intl.html | 15 ++++----------- 3 files changed, 37 insertions(+), 18 deletions(-) diff --git a/polyfill/lib/calendar.mjs b/polyfill/lib/calendar.mjs index b53d5d1c3e..bafdcf8210 100644 --- a/polyfill/lib/calendar.mjs +++ b/polyfill/lib/calendar.mjs @@ -41,6 +41,8 @@ const MapPrototypeEntries = Map.prototype.entries; const MapPrototypeGet = Map.prototype.get; const MapPrototypeSet = Map.prototype.set; const SetPrototypeAdd = Set.prototype.add; +const SetPrototypeDelete = Set.prototype.delete; +const SetPrototypeHas = Set.prototype.has; const SetPrototypeValues = Set.prototype.values; const SymbolIterator = Symbol.iterator; const WeakMapPrototypeGet = WeakMap.prototype.get; @@ -109,12 +111,24 @@ export class Calendar { fields(fields) { if (!ES.IsTemporalCalendar(this)) throw new TypeError('invalid receiver'); const fieldsArray = []; - const allowed = new Set(['year', 'month', 'monthCode', 'day']); - for (const name of fields) { - if (ES.Type(name) !== 'String') throw new TypeError('invalid fields'); - if (!allowed.has(name)) throw new RangeError(`invalid field name ${name}`); - allowed.delete(name); - ES.Call(ArrayPrototypePush, fieldsArray, [name]); + const allowed = new OriginalSet(['year', 'month', 'monthCode', 'day']); + const iteratorRecord = ES.GetIterator(fields, 'sync'); + const abort = (err) => { + const completion = new ES.CompletionRecord('throw', err); + return ES.IteratorClose(iteratorRecord, completion)['?'](); + }; + let next = true; + while (next !== false) { + next = ES.IteratorStep(iteratorRecord); + if (next !== false) { + let name = ES.IteratorValue(next); + if (ES.Type(name) !== 'String') return abort(new TypeError('invalid fields')); + if (!ES.Call(SetPrototypeHas, allowed, [name])) { + return abort(new RangeError(`invalid or duplicate field name ${name}`)); + } + ES.Call(SetPrototypeDelete, allowed, [name]); + ES.Call(ArrayPrototypePush, fieldsArray, [name]); + } } return impl[GetSlot(this, CALENDAR_ID)].fields(fieldsArray); } diff --git a/polyfill/lib/ecmascript.mjs b/polyfill/lib/ecmascript.mjs index a9cc3b3f03..c7f231517c 100644 --- a/polyfill/lib/ecmascript.mjs +++ b/polyfill/lib/ecmascript.mjs @@ -29,13 +29,18 @@ const StringPrototypeSlice = String.prototype.slice; import bigInt from 'big-integer'; import callBound from 'call-bind/callBound'; import Call from 'es-abstract/2022/Call.js'; +import CompletionRecord from 'es-abstract/2022/CompletionRecord.js'; import CreateDataPropertyOrThrow from 'es-abstract/2022/CreateDataPropertyOrThrow.js'; import Get from 'es-abstract/2022/Get.js'; +import GetIterator from 'es-abstract/2022/GetIterator.js'; import GetMethod from 'es-abstract/2022/GetMethod.js'; import HasOwnProperty from 'es-abstract/2022/HasOwnProperty.js'; import IsArray from 'es-abstract/2022/IsArray.js'; import IsIntegralNumber from 'es-abstract/2022/IsIntegralNumber.js'; import IsPropertyKey from 'es-abstract/2022/IsPropertyKey.js'; +import IteratorClose from 'es-abstract/2022/IteratorClose.js'; +import IteratorStep from 'es-abstract/2022/IteratorStep.js'; +import IteratorValue from 'es-abstract/2022/IteratorValue.js'; import SameValue from 'es-abstract/2022/SameValue.js'; import ToIntegerOrInfinity from 'es-abstract/2022/ToIntegerOrInfinity.js'; import ToNumber from 'es-abstract/2022/ToNumber.js'; @@ -233,9 +238,14 @@ import * as PARSE from './regex.mjs'; export { Call, + CompletionRecord, + GetIterator, GetMethod, HasOwnProperty, IsIntegralNumber, + IteratorClose, + IteratorStep, + IteratorValue, ToIntegerOrInfinity, ToNumber, ToObject, @@ -1162,7 +1172,9 @@ export function PrepareTemporalFields( export function ToTemporalTimeRecord(bag, completeness = 'complete') { const fields = ['hour', 'microsecond', 'millisecond', 'minute', 'nanosecond', 'second']; - const partial = PrepareTemporalFields(bag, fields, 'partial', undefined, undefined, { emptySourceErrorMessage: 'invalid time-like' }); + const partial = PrepareTemporalFields(bag, fields, 'partial', undefined, undefined, { + emptySourceErrorMessage: 'invalid time-like' + }); const result = {}; for (let index = 0; index < fields.length; index++) { const field = fields[index]; diff --git a/spec/intl.html b/spec/intl.html index b3de3a2cc1..1ac51aef19 100644 --- a/spec/intl.html +++ b/spec/intl.html @@ -1754,16 +1754,16 @@

- +

CalendarFieldDescriptors ( _calendar_: a String, - _type_: ~date~, ~year-month~, or ~month-day~, + _type_: ~date~, ~year-month~, ~month-day~, or a List of Strings, ): a List of Calendar Field Descriptor Records

description
-
It characterizes calendar-specific fields that are relevant for values of the provided _type_ in the built-in calendar identified by _calendar_. For example, *"era"* (with ToString conversion) and *"eraYear"* (with ToIntegerWithTruncation conversion) are returned when _calendar_ is *"gregory"* or *"japanese"* and _type_ is ~date~ or ~year-month~.
+
It characterizes calendar-specific fields that are relevant for values of the provided _type_ in the built-in calendar identified by _calendar_ (inferring the type when _type_ is a List by interpreting its elements as field names). For example, *"era"* (with ToString conversion) and *"eraYear"* (with ToIntegerWithTruncation conversion) are returned when _calendar_ is *"gregory"* or *"japanese"* and _type_ is ~date~ or ~year-month~ or a List containing *"year"*.
@@ -2313,14 +2313,7 @@

Temporal.Calendar.prototype.fields ( _fields_ )

1. Let _result_ be _fieldNames_. 1. If _calendar_.[[Identifier]] is not *"iso8601"*, then 1. NOTE: Every built-in calendar preserves all input field names in output. - 1. Let _type_ be *undefined*. - 1. If _fieldNames_ does not contain *"year"*, then - 1. Set _type_ to ~month-day~. - 1. Else if _fieldNames_ does not contain *"day"*, then - 1. Set _type_ to ~year-month~. - 1. Else, - 1. Set _type_ to ~date~. - 1. Let _extraFieldDescriptors_ be CalendarFieldDescriptors(_calendar_.[[Identifier]], type). + 1. Let _extraFieldDescriptors_ be CalendarFieldDescriptors(_calendar_.[[Identifier]], _fieldNames_). 1. For each Calendar Field Descriptor Record _desc_ of _extraFieldDescriptors_, do 1. Append _desc_.[[Property]] to _result_. 1. Return CreateArrayFromList(_result_). From ecbe6900d591815a97f633790aec156b74ee13d5 Mon Sep 17 00:00:00 2001 From: Philip Chimento Date: Mon, 21 Aug 2023 09:23:24 -0700 Subject: [PATCH 7/7] Update test262 --- polyfill/test262 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/polyfill/test262 b/polyfill/test262 index c30aff08af..bdddd9e2d2 160000 --- a/polyfill/test262 +++ b/polyfill/test262 @@ -1 +1 @@ -Subproject commit c30aff08af165f3fbdc1de7653aaac97aede693a +Subproject commit bdddd9e2d286600462958c7bac6af10a2946134b