Skip to content

Commit

Permalink
Normative: Change calendar compare semantics to use .id
Browse files Browse the repository at this point in the history
Previously algorithms such as CalendarEquals used ToString to get the
identifier of a calendar object. Instead, use ToTemporalCalendarIdentifier
which observably gets the .id property of a custom calendar instead of
getting and calling the .toString property.

See: #1808
  • Loading branch information
ptomato committed Apr 7, 2023
1 parent 52f3159 commit 5e0e870
Show file tree
Hide file tree
Showing 8 changed files with 42 additions and 48 deletions.
8 changes: 4 additions & 4 deletions docs/calendar.md
Original file line number Diff line number Diff line change
Expand Up @@ -108,16 +108,16 @@ There are two ways to do this.
The recommended way is to create a class inheriting from `Temporal.Calendar`.
You must use one of the built-in calendars as the "base calendar".
In the class's constructor, call `super()` with the identifier of a built-in calendar to serve as a base.
The class must override `toString()` to return its own identifier.
The class must override the `id` prototype property, and should also override `toString()` and `toJSON()` to match.
Overriding all the other properties of `Temporal.Calendar.prototype` is optional.
Any property that's not overridden will behave as in the base calendar.
It's recommended to override `dateFromFields()`, `monthDayFromFields()`, `yearMonthFromFields()`, and `dateAdd()`so that they return Temporal objects with the custom calendar and not the base calendar.

The other, more difficult, way to create a custom calendar is to create a plain object implementing the `Temporal.Calendar` protocol, without subclassing.
The object must implement all of the `Temporal.Calendar` properties and methods except for `id`, `fields()`, `mergeFields()`, and `toJSON()`.
The object must implement all of the `Temporal.Calendar` properties and methods except for `fields()`, `mergeFields()`, `toString()`, and `toJSON()`.
Any object with the required methods will return the correct output from any Temporal property or method.
However, most other code will assume that custom calendars act like built-in `Temporal.Calendar` objects.
To interoperate with libraries or other code that you didn't write, then you should implement the `id` property and the `fields()`, `mergeFields()`, and `toJSON()` methods as well.
To interoperate with libraries or other code that you didn't write, then you should implement the `fields()`, `mergeFields()`, `toString()`, and `toJSON()` methods as well.
Your object must not have a `calendar` property, so that it can be distinguished in `Temporal.Calendar.from()` from other Temporal objects that have a calendar.

The identifier of a custom calendar must consist of one or more components of between 3 and 8 ASCII alphanumeric characters each, separated by dashes, as described in [Unicode Technical Standard 35](https://unicode.org/reports/tr35/tr35.html#Unicode_locale_identifier).
Expand Down Expand Up @@ -236,7 +236,7 @@ cal2 = Temporal.Calendar.from(cal);
The `id` property gives an unambiguous identifier for the calendar.
Effectively, this is whatever `calendarIdentifier` was passed as a parameter to the constructor.

When subclassing `Temporal.Calendar`, this property doesn't need to be overridden because the default implementation gives the result of calling `toString()`.
When subclassing `Temporal.Calendar`, this property must be overridden to provide an identifier for the custom calendar.

## Methods

Expand Down
4 changes: 2 additions & 2 deletions polyfill/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -613,7 +613,7 @@ export namespace Temporal {
type MonthOrMonthCode = { month: number } | { monthCode: string };

export interface CalendarProtocol {
id?: string;
id: string;
calendar?: never;
year(date: Temporal.PlainDate | Temporal.PlainDateTime | Temporal.PlainYearMonth | PlainDateLike | string): number;
month(
Expand Down Expand Up @@ -677,7 +677,7 @@ export namespace Temporal {
): Temporal.Duration;
fields?(fields: Iterable<string>): Iterable<string>;
mergeFields?(fields: Record<string, unknown>, additionalFields: Record<string, unknown>): Record<string, unknown>;
toString(): string;
toString?(): string;
toJSON?(): string;
}

Expand Down
18 changes: 9 additions & 9 deletions polyfill/lib/ecmascript.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -341,7 +341,7 @@ export const ES = ObjectAssign({}, ES2022, {
},
MaybeFormatCalendarAnnotation: (calendar, showCalendar) => {
if (showCalendar === 'never') return '';
return ES.FormatCalendarAnnotation(ES.ToString(ES.ToTemporalCalendarObject(calendar)), showCalendar);
return ES.FormatCalendarAnnotation(ES.ToTemporalCalendarIdentifier(calendar), showCalendar);
},
FormatCalendarAnnotation: (id, showCalendar) => {
if (showCalendar === 'never') return '';
Expand Down Expand Up @@ -1904,8 +1904,8 @@ export const ES = ObjectAssign({}, ES2022, {
},
CalendarEquals: (one, two) => {
if (one === two) return true;
const cal1 = ES.ToString(one);
const cal2 = ES.ToString(two);
const cal1 = ES.ToTemporalCalendarIdentifier(one);
const cal2 = ES.ToTemporalCalendarIdentifier(two);
return cal1 === cal2;
},
// This operation is not in the spec, it implements the following:
Expand All @@ -1914,16 +1914,16 @@ export const ES = ObjectAssign({}, ES2022, {
// re-getting the .id properties.
CalendarEqualsOrThrow: (one, two, errorMessageAction) => {
if (one === two) return true;
const cal1 = ES.ToString(one);
const cal2 = ES.ToString(two);
const cal1 = ES.ToTemporalCalendarIdentifier(one);
const cal2 = ES.ToTemporalCalendarIdentifier(two);
if (cal1 !== cal2) {
throw new RangeError(`cannot ${errorMessageAction} of ${cal1} and ${cal2} calendars`);
}
},
ConsolidateCalendars: (one, two) => {
if (one === two) return two;
const sOne = ES.ToString(one);
const sTwo = ES.ToString(two);
const sOne = ES.ToTemporalCalendarIdentifier(one);
const sTwo = ES.ToTemporalCalendarIdentifier(two);
if (sOne === sTwo || sOne === 'iso8601') {
return two;
} else if (sTwo === 'iso8601') {
Expand Down Expand Up @@ -2372,7 +2372,7 @@ export const ES = ObjectAssign({}, ES2022, {
const day = ES.ISODateTimePartString(GetSlot(monthDay, ISO_DAY));
let resultString = `${month}-${day}`;
const calendar = GetSlot(monthDay, CALENDAR);
const calendarID = ES.ToString(calendar);
const calendarID = ES.ToTemporalCalendarIdentifier(calendar);
if (showCalendar === 'always' || showCalendar === 'critical' || calendarID !== 'iso8601') {
const year = ES.ISOYearString(GetSlot(monthDay, ISO_YEAR));
resultString = `${year}-${resultString}`;
Expand All @@ -2386,7 +2386,7 @@ export const ES = ObjectAssign({}, ES2022, {
const month = ES.ISODateTimePartString(GetSlot(yearMonth, ISO_MONTH));
let resultString = `${year}-${month}`;
const calendar = GetSlot(yearMonth, CALENDAR);
const calendarID = ES.ToString(calendar);
const calendarID = ES.ToTemporalCalendarIdentifier(calendar);
if (showCalendar === 'always' || showCalendar === 'critical' || calendarID !== 'iso8601') {
const day = ES.ISODateTimePartString(GetSlot(yearMonth, ISO_DAY));
resultString += `-${day}`;
Expand Down
10 changes: 5 additions & 5 deletions polyfill/lib/intl.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -392,7 +392,7 @@ function extractOverrides(temporalObj, main) {
const isoYear = GetSlot(temporalObj, ISO_YEAR);
const isoMonth = GetSlot(temporalObj, ISO_MONTH);
const referenceISODay = GetSlot(temporalObj, ISO_DAY);
const calendar = ES.ToString(GetSlot(temporalObj, CALENDAR));
const calendar = ES.ToTemporalCalendarIdentifier(GetSlot(temporalObj, CALENDAR));
if (calendar !== main[CAL_ID]) {
throw new RangeError(
`cannot format PlainYearMonth with calendar ${calendar} in locale with calendar ${main[CAL_ID]}`
Expand All @@ -409,7 +409,7 @@ function extractOverrides(temporalObj, main) {
const referenceISOYear = GetSlot(temporalObj, ISO_YEAR);
const isoMonth = GetSlot(temporalObj, ISO_MONTH);
const isoDay = GetSlot(temporalObj, ISO_DAY);
const calendar = ES.ToString(GetSlot(temporalObj, CALENDAR));
const calendar = ES.ToTemporalCalendarIdentifier(GetSlot(temporalObj, CALENDAR));
if (calendar !== main[CAL_ID]) {
throw new RangeError(
`cannot format PlainMonthDay with calendar ${calendar} in locale with calendar ${main[CAL_ID]}`
Expand All @@ -426,7 +426,7 @@ function extractOverrides(temporalObj, main) {
const isoYear = GetSlot(temporalObj, ISO_YEAR);
const isoMonth = GetSlot(temporalObj, ISO_MONTH);
const isoDay = GetSlot(temporalObj, ISO_DAY);
const calendar = ES.ToString(GetSlot(temporalObj, CALENDAR));
const calendar = ES.ToTemporalCalendarIdentifier(GetSlot(temporalObj, CALENDAR));
if (calendar !== 'iso8601' && calendar !== main[CAL_ID]) {
throw new RangeError(`cannot format PlainDate with calendar ${calendar} in locale with calendar ${main[CAL_ID]}`);
}
Expand All @@ -447,7 +447,7 @@ function extractOverrides(temporalObj, main) {
const millisecond = GetSlot(temporalObj, ISO_MILLISECOND);
const microsecond = GetSlot(temporalObj, ISO_MICROSECOND);
const nanosecond = GetSlot(temporalObj, ISO_NANOSECOND);
const calendar = ES.ToString(GetSlot(temporalObj, CALENDAR));
const calendar = ES.ToTemporalCalendarIdentifier(GetSlot(temporalObj, CALENDAR));
if (calendar !== 'iso8601' && calendar !== main[CAL_ID]) {
throw new RangeError(
`cannot format PlainDateTime with calendar ${calendar} in locale with calendar ${main[CAL_ID]}`
Expand Down Expand Up @@ -475,7 +475,7 @@ function extractOverrides(temporalObj, main) {
}

if (ES.IsTemporalZonedDateTime(temporalObj)) {
const calendar = ES.ToString(GetSlot(temporalObj, CALENDAR));
const calendar = ES.ToTemporalCalendarIdentifier(GetSlot(temporalObj, CALENDAR));
if (calendar !== 'iso8601' && calendar !== main[CAL_ID]) {
throw new RangeError(
`cannot format ZonedDateTime with calendar ${calendar} in locale with calendar ${main[CAL_ID]}`
Expand Down
15 changes: 7 additions & 8 deletions spec/calendar.html
Original file line number Diff line number Diff line change
Expand Up @@ -651,14 +651,13 @@ <h1>
<dd>
It returns a string with a calendar annotation suitable for concatenating to the end of an ISO 8601 string.
Depending on the given _calendar_ and the value of _showCalendar_, the string may be empty if no calendar annotation need be included.
This operation may invoke an observable ToString operation on _calendarObject_, but not if _showCalendar_ is *"never"*.
This operation may invoke an observable Get operation on _calendar_ if it is a custom calendar, but not if _showCalendar_ is *"never"*.
</dd>
</dl>
<emu-alg>
1. If _showCalendar_ is *"never"*, return the empty String.
1. Let _calendarObject_ be ToTemporalCalendarObject(_calendar_).
1. Let _calendarID_ be ? ToString(_calendarObject_).
1. Return FormatCalendarAnnotation(_calendarID_, _showCalendar_).
1. Let _calendarIdentifier_ be ? ToTemporalCalendarIdentifier(_calendar_).
1. Return FormatCalendarAnnotation(_calendarIdentifier_, _showCalendar_).
</emu-alg>
</emu-clause>

Expand Down Expand Up @@ -693,8 +692,8 @@ <h1>CalendarEquals ( _one_, _two_ )</h1>
</p>
<emu-alg>
1. If _one_ and _two_ are the same Object value, return *true*.
1. Let _calendarOne_ be ? ToString(_one_).
1. Let _calendarTwo_ be ? ToString(_two_).
1. Let _calendarOne_ be ? ToTemporalCalendarIdentifier(_one_).
1. Let _calendarTwo_ be ? ToTemporalCalendarIdentifier(_two_).
1. If _calendarOne_ is _calendarTwo_, return *true*.
1. Return *false*.
</emu-alg>
Expand All @@ -709,8 +708,8 @@ <h1>ConsolidateCalendars ( _one_, _two_ )</h1>
</p>
<emu-alg>
1. If _one_ and _two_ are the same Object value, return _two_.
1. Let _calendarOne_ be ? ToString(_one_).
1. Let _calendarTwo_ be ? ToString(_two_).
1. Let _calendarOne_ be ? ToTemporalCalendarIdentifier(_one_).
1. Let _calendarTwo_ be ? ToTemporalCalendarIdentifier(_two_).
1. If _calendarOne_ is _calendarTwo_, return _two_.
1. If _calendarOne_ is *"iso8601"*, return _two_.
1. If _calendarTwo_ is *"iso8601"*, return _one_.
Expand Down
23 changes: 9 additions & 14 deletions spec/intl.html
Original file line number Diff line number Diff line change
Expand Up @@ -756,14 +756,9 @@ <h1>HandleDateTimeTemporalDate ( _dateTimeFormat_, _temporalDate_ )</h1>
<emu-alg>
1. Assert: _temporalDate_ has an [[InitializedTemporalDate]] internal slot.
1. Let _pattern_ be _dateTimeFormat_.[[TemporalPlainDatePattern]].
1. Let _calendar_ be ? ToString(_temporalDate_.[[Calendar]]).
1. If _calendar_ is _dateTimeFormat_.[[Calendar]], then
1. Let _calendarOverride_ be _temporalDate_.[[Calendar]].
1. Else if _calendar_ is *"iso8601"*, then
1. Let _calendarOverride_ be _dateTimeFormat_.[[Calendar]].
1. Else,
1. Throw a *RangeError* exception.
1. Let _plainDateTime_ be ? CreateTemporalDateTime(_temporalDate_.[[ISOYear]], _temporalDate_.[[ISOMonth]], _temporalDate_.[[ISODay]], 12, 0, 0, 0, 0, 0, _calendarOverride_).
1. Let _calendar_ be ? ToTemporalCalendarIdentifier(_temporalDate_.[[Calendar]]).
1. If _calendar_ is not _dateTimeFormat_.[[Calendar]] or *"iso8601"*, throw a *RangeError* exception.
1. Let _plainDateTime_ be ? CreateTemporalDateTime(_temporalDate_.[[ISOYear]], _temporalDate_.[[ISOMonth]], _temporalDate_.[[ISODay]], 12, 0, 0, 0, 0, 0, _dateTimeFormat_.[[Calendar]]).
1. Let _timeZone_ be ! CreateTemporalTimeZone(_dateTimeFormat_.[[TimeZone]]).
1. Let _instant_ be ? GetInstantFor(_timeZone_, _plainDateTime_, *"compatible"*).
1. If _pattern_ is *null*, throw a *TypeError* exception.
Expand All @@ -786,10 +781,10 @@ <h1>HandleDateTimeTemporalYearMonth ( _dateTimeFormat_, _temporalYearMonth_ )</h
<emu-alg>
1. Assert: _temporalYearMonth_ has an [[InitializedTemporalYearMonth]] internal slot.
1. Let _pattern_ be _dateTimeFormat_.[[TemporalPlainYearMonthPattern]].
1. Let _calendar_ be ? ToString(_temporalYearMonth_.[[Calendar]]).
1. Let _calendar_ be ? ToTemporalCalendarIdentifier(_temporalYearMonth_.[[Calendar]]).
1. If _calendar_ is not equal to _dateTimeFormat_.[[Calendar]], then
1. Throw a *RangeError* exception.
1. Let _plainDateTime_ be ? CreateTemporalDateTime(_temporalYearMonth_.[[ISOYear]], _temporalYearMonth_.[[ISOMonth]], _temporalYearMonth_.[[ISODay]], 12, 0, 0, 0, 0, 0, _temporalYearMonth_.[[Calendar]]).
1. Let _plainDateTime_ be ? CreateTemporalDateTime(_temporalYearMonth_.[[ISOYear]], _temporalYearMonth_.[[ISOMonth]], _temporalYearMonth_.[[ISODay]], 12, 0, 0, 0, 0, 0, _dateTimeFormat_.[[Calendar]]).
1. Let _timeZone_ be ! CreateTemporalTimeZone(_dateTimeFormat_.[[TimeZone]]).
1. Let _instant_ be ? GetInstantFor(_timeZone_, _plainDateTime_, *"compatible"*).
1. If _pattern_ is *null*, throw a *TypeError* exception.
Expand All @@ -813,10 +808,10 @@ <h1>HandleDateTimeTemporalMonthDay ( _dateTimeFormat_, _temporalMonthDay_ )</h1>
<emu-alg>
1. Assert: _temporalMonthDay_ has an [[InitializedTemporalMonthDay]] internal slot.
1. Let _pattern_ be _dateTimeFormat_.[[TemporalPlainMonthDayPattern]].
1. Let _calendar_ be ? ToString(_temporalMonthDay_.[[Calendar]]).
1. Let _calendar_ be ? ToTemporalCalendarIdentifier(_temporalMonthDay_.[[Calendar]]).
1. If _calendar_ is not equal to _dateTimeFormat_.[[Calendar]], then
1. Throw a *RangeError* exception.
1. Let _plainDateTime_ be ? CreateTemporalDateTime(_temporalMonthDay_.[[ISOYear]], _temporalMonthDay_.[[ISOMonth]], _temporalMonthDay_.[[ISODay]], 12, 0, 0, 0, 0, 0, _temporalMonthDay_.[[Calendar]]).
1. Let _plainDateTime_ be ? CreateTemporalDateTime(_temporalMonthDay_.[[ISOYear]], _temporalMonthDay_.[[ISOMonth]], _temporalMonthDay_.[[ISODay]], 12, 0, 0, 0, 0, 0, _dateTimeFormat_.[[Calendar]]).
1. Let _timeZone_ be ! CreateTemporalTimeZone(_dateTimeFormat_.[[TimeZone]]).
1. Let _instant_ be ? GetInstantFor(_timeZone_, _plainDateTime_, *"compatible"*).
1. If _pattern_ is *null*, throw a *TypeError* exception.
Expand Down Expand Up @@ -864,7 +859,7 @@ <h1>HandleDateTimeTemporalDateTime ( _dateTimeFormat_, _dateTime_ )</h1>
<emu-alg>
1. Assert: _dateTime_ has an [[InitializedTemporalDateTime]] internal slot.
1. Let _pattern_ be _dateTimeFormat_.[[TemporalPlainDateTimePattern]].
1. Let _calendar_ be ? ToString(_dateTime_.[[Calendar]]).
1. Let _calendar_ be ? ToTemporalCalendarIdentifier(_dateTime_.[[Calendar]]).
1. If _calendar_ is not *"iso8601"* and not equal to _dateTimeFormat_.[[Calendar]], then
1. Throw a *RangeError* exception.
1. Let _timeZone_ be ! CreateTemporalTimeZone(_dateTimeFormat_.[[TimeZone]]).
Expand Down Expand Up @@ -910,7 +905,7 @@ <h1>HandleDateTimeTemporalZonedDateTime ( _dateTimeFormat_, _zonedDateTime_ )</h
<emu-alg>
1. Assert: _zonedDateTime_ has an [[InitializedTemporalZonedDateTime]] internal slot.
1. Let _pattern_ be _dateTimeFormat_.[[TemporalZonedDateTimePattern]].
1. Let _calendar_ be ? ToString(_zonedDateTime_.[[Calendar]]).
1. Let _calendar_ be ? ToTemporalCalendarIdentifier(_zonedDateTime_.[[Calendar]]).
1. If _calendar_ is not *"iso8601"* and not equal to _dateTimeFormat_.[[Calendar]], then
1. Throw a *RangeError* exception.
1. Let _timeZone_ be ? ToString(_zonedDateTime_.[[TimeZone]]).
Expand Down
6 changes: 3 additions & 3 deletions spec/plainmonthday.html
Original file line number Diff line number Diff line change
Expand Up @@ -433,11 +433,11 @@ <h1>TemporalMonthDayToString ( _monthDay_, _showCalendar_ )</h1>
1. Let _month_ be ToZeroPaddedDecimalString(_monthDay_.[[ISOMonth]], 2).
1. Let _day_ be ToZeroPaddedDecimalString(_monthDay_.[[ISODay]], 2).
1. Let _result_ be the string-concatenation of _month_, the code unit 0x002D (HYPHEN-MINUS), and _day_.
1. Let _calendarID_ be ? ToString(_monthDay_.[[Calendar]]).
1. If _showCalendar_ is one of *"always"* or *"critical"*, or if _calendarID_ is not *"iso8601"*, then
1. Let _calendarIdentifier_ be ? ToTemporalCalendarIdentifier(_monthDay_.[[Calendar]]).
1. If _showCalendar_ is one of *"always"* or *"critical"*, or if _calendarIdentifier_ is not *"iso8601"*, then
1. Let _year_ be ! PadISOYear(_monthDay_.[[ISOYear]]).
1. Set _result_ to the string-concatenation of _year_, the code unit 0x002D (HYPHEN-MINUS), and _result_.
1. Let _calendarString_ be FormatCalendarAnnotation(_calendarID_, _showCalendar_).
1. Let _calendarString_ be FormatCalendarAnnotation(_calendarIdentifier_, _showCalendar_).
1. Set _result_ to the string-concatenation of _result_ and _calendarString_.
1. Return _result_.
</emu-alg>
Expand Down
6 changes: 3 additions & 3 deletions spec/plainyearmonth.html
Original file line number Diff line number Diff line change
Expand Up @@ -599,11 +599,11 @@ <h1>TemporalYearMonthToString ( _yearMonth_, _showCalendar_ )</h1>
1. Let _year_ be ! PadISOYear(_yearMonth_.[[ISOYear]]).
1. Let _month_ be ToZeroPaddedDecimalString(_yearMonth_.[[ISOMonth]], 2).
1. Let _result_ be the string-concatenation of _year_, the code unit 0x002D (HYPHEN-MINUS), and _month_.
1. Let _calendarID_ be ? ToString(_yearMonth_.[[Calendar]]).
1. If _showCalendar_ is one of *"always"* or *"critical"*, or if _calendarID_ is not *"iso8601"*, then
1. Let _calendarIdentifier_ be ? ToTemporalCalendarIdentifier(_yearMonth_.[[Calendar]]).
1. If _showCalendar_ is one of *"always"* or *"critical"*, or if _calendarIdentifier_ is not *"iso8601"*, then
1. Let _day_ be ToZeroPaddedDecimalString(_yearMonth_.[[ISODay]], 2).
1. Set _result_ to the string-concatenation of _result_, the code unit 0x002D (HYPHEN-MINUS), and _day_.
1. Let _calendarString_ be FormatCalendarAnnotation(_calendarID_, _showCalendar_).
1. Let _calendarString_ be FormatCalendarAnnotation(_calendarIdentifier_, _showCalendar_).
1. Set _result_ to the string-concatenation of _result_ and _calendarString_.
1. Return _result_.
</emu-alg>
Expand Down

0 comments on commit 5e0e870

Please sign in to comment.