Skip to content

Commit

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

See: #1808
  • Loading branch information
ptomato committed Apr 7, 2023
1 parent 9739812 commit 08ae76a
Show file tree
Hide file tree
Showing 8 changed files with 21 additions and 16 deletions.
6 changes: 5 additions & 1 deletion docs/cookbook/stockExchangeTimeZone.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ function getPreviousMarketClose(instant) {
}

class NYSETimeZone extends Temporal.TimeZone {
#id = 'NYSE';
constructor() {
super('America/New_York');
}
Expand Down Expand Up @@ -124,8 +125,11 @@ class NYSETimeZone extends Temporal.TimeZone {
const ns = zdt.offsetNanoseconds + zdt.until(zdtWhenMarketIsOpen, { largestUnit: 'nanosecond' }).nanoseconds;
return ns;
}
get id() {
return this.#id;
}
toString() {
return 'NYSE';
return this.#id;
}
}

Expand Down
8 changes: 4 additions & 4 deletions docs/timezone.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,15 +20,15 @@ There are two ways to do this.
The recommended way is to create a class inheriting from `Temporal.TimeZone`.
You must use one of the built-in time zones as the "base time zone".
In the class's constructor, call `super()` with the identifier of a built-in time zone 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 override `toString()` and `toJSON()` to match.
Overriding all the other properties of `Temporal.TimeZone.prototype` is optional.
Any property that is not overridden will behave as in the base time zone.

The other, more difficult, way to create a custom time zone is to create a plain object implementing the `Temporal.TimeZone` protocol, without subclassing.
The object must have at least `getOffsetNanosecondsFor()`, `getPossibleInstantsFor()`, and `toString()` methods.
The object must have at least `getOffsetNanosecondsFor()` and `getPossibleInstantsFor()` methods, and an `id` property.
Any object with those three methods will return the correct output from any Temporal property or method.
However, most other code will assume that custom time zones act like built-in `Temporal.TimeZone` objects.
To interoperate with libraries or other code that you didn't write, then you should implement all the other `Temporal.TimeZone` members as well: `id`, `getOffsetStringFor()`, `getPlainDateTimeFor()`, `getInstantFor()`, `getNextTransition()`, `getPreviousTransition()`, and `toJSON()`.
To interoperate with libraries or other code that you didn't write, then you should implement all the other `Temporal.TimeZone` members as well: `toString()`, `toJSON()`, `getOffsetStringFor()`, `getPlainDateTimeFor()`, `getInstantFor()`, `getNextTransition()`, `getPreviousTransition()`, and `toJSON()`.
Your object must not have a `timeZone` property, so that it can be distinguished in `Temporal.TimeZone.from()` from other Temporal objects that have a time zone.

The identifier of a custom time zone must consist of one or more components separated by slashes (`/`), as described in the [tzdata documentation](https://htmlpreview.github.io/?https://github.com/eggert/tz/blob/master/theory.html#naming).
Expand Down Expand Up @@ -136,7 +136,7 @@ tz2 = Temporal.TimeZone.from(tz);
The `id` property gives an unambiguous identifier for the time zone.
Effectively, this is the canonicalized version of whatever `timeZoneIdentifier` was passed as a parameter to the constructor.

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

## 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 @@ -1090,7 +1090,7 @@ export namespace Temporal {
* A plain object implementing the protocol for a custom time zone.
*/
export interface TimeZoneProtocol {
id?: string;
id: string;
timeZone?: never;
getOffsetNanosecondsFor(instant: Temporal.Instant | string): number;
getOffsetStringFor?(instant: Temporal.Instant | string): string;
Expand All @@ -1102,7 +1102,7 @@ export namespace Temporal {
getNextTransition?(startingPoint: Temporal.Instant | string): Temporal.Instant | null;
getPreviousTransition?(startingPoint: Temporal.Instant | string): Temporal.Instant | null;
getPossibleInstantsFor(dateTime: Temporal.PlainDateTime | PlainDateTimeLike | string): Temporal.Instant[];
toString(): string;
toString?(): string;
toJSON?(): string;
}

Expand Down
7 changes: 4 additions & 3 deletions polyfill/lib/ecmascript.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -1999,8 +1999,8 @@ export const ES = ObjectAssign({}, ES2022, {
},
TimeZoneEquals: (one, two) => {
if (one === two) return true;
const tz1 = ES.ToString(one);
const tz2 = ES.ToString(two);
const tz1 = ES.ToTemporalTimeZoneIdentifier(one);
const tz2 = ES.ToTemporalTimeZoneIdentifier(two);
return tz1 === tz2;
},
TemporalDateTimeToDate: (dateTime) => {
Expand Down Expand Up @@ -2450,8 +2450,9 @@ export const ES = ObjectAssign({}, ES2022, {
result += ES.FormatISOTimeZoneOffsetString(offsetNs);
}
if (showTimeZone !== 'never') {
const identifier = ES.ToTemporalTimeZoneIdentifier(tz);
const flag = showTimeZone === 'critical' ? '!' : '';
result += `[${flag}${tz}]`;
result += `[${flag}${identifier}]`;
}
result += ES.MaybeFormatCalendarAnnotation(GetSlot(zdt, CALENDAR), showCalendar);
return result;
Expand Down
2 changes: 1 addition & 1 deletion polyfill/lib/intl.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -473,7 +473,7 @@ function extractOverrides(temporalObj, main) {
}

let timeZone = GetSlot(temporalObj, TIME_ZONE);
const objTimeZone = ES.ToString(timeZone);
const objTimeZone = ES.ToTemporalTimeZoneIdentifier(timeZone);
if (main[TZ_GIVEN] && main[TZ_GIVEN] !== objTimeZone) {
throw new RangeError(`timeZone option ${main[TZ_GIVEN]} doesn't match actual time zone ${objTimeZone}`);
}
Expand Down
2 changes: 1 addition & 1 deletion spec/intl.html
Original file line number Diff line number Diff line change
Expand Up @@ -903,7 +903,7 @@ <h1>HandleDateTimeTemporalZonedDateTime ( _dateTimeFormat_, _zonedDateTime_ )</h
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]]).
1. Let _timeZone_ be ? ToTemporalTimeZoneIdentifier(_zonedDateTime_.[[TimeZone]]).
1. If _dateTimeFormat_.[[TimeZone]] is not equal to DefaultTimeZone(), and _timeZone_ is not equal to _dateTimeFormat_.[[TimeZone]], then
1. Throw a *RangeError* exception.
1. Let _instant_ be ! CreateTemporalInstant(_zonedDateTime_.[[Nanoseconds]]).
Expand Down
4 changes: 2 additions & 2 deletions spec/timezone.html
Original file line number Diff line number Diff line change
Expand Up @@ -774,8 +774,8 @@ <h1>TimeZoneEquals ( _one_, _two_ )</h1>
</p>
<emu-alg>
1. If _one_ and _two_ are the same Object value, return *true*.
1. Let _timeZoneOne_ be ? ToString(_one_).
1. Let _timeZoneTwo_ be ? ToString(_two_).
1. Let _timeZoneOne_ be ? ToTemporalTimeZoneIdentifier(_one_).
1. Let _timeZoneTwo_ be ? ToTemporalTimeZoneIdentifier(_two_).
1. If _timeZoneOne_ is _timeZoneTwo_, return *true*.
1. Return *false*.
</emu-alg>
Expand Down
4 changes: 2 additions & 2 deletions spec/zoneddatetime.html
Original file line number Diff line number Diff line change
Expand Up @@ -1254,9 +1254,9 @@ <h1>
1. If _showTimeZone_ is *"never"*, then
1. Let _timeZoneString_ be the empty String.
1. Else,
1. Let _timeZoneID_ be ? ToString(_timeZone_).
1. Let _timeZoneIdentifier_ be ? ToTemporalTimeZoneIdentifier(_timeZone_).
1. If _showTimeZone_ is *"critical"*, let _flag_ be *"!"*; else let _flag_ be the empty String.
1. Let _timeZoneString_ be the string-concatenation of the code unit 0x005B (LEFT SQUARE BRACKET), _flag_, _timeZoneID_, and the code unit 0x005D (RIGHT SQUARE BRACKET).
1. Let _timeZoneString_ be the string-concatenation of the code unit 0x005B (LEFT SQUARE BRACKET), _flag_, _timeZoneIdentifier_, and the code unit 0x005D (RIGHT SQUARE BRACKET).
1. Let _calendarString_ be ? MaybeFormatCalendarAnnotation(_zonedDateTime_.[[Calendar]], _showCalendar_).
1. Return the string-concatenation of _dateTimeString_, _offsetString_, _timeZoneString_, and _calendarString_.
</emu-alg>
Expand Down

0 comments on commit 08ae76a

Please sign in to comment.