Skip to content

Commit

Permalink
Add clarifications about TimeZone and Calendar protocols
Browse files Browse the repository at this point in the history
Emphasize that inheriting from the built-in objects is the recommended way
to create custom time zones and calendars, and give details about what the
default implementations of each of the optional methods do.

Closes: #1040
  • Loading branch information
ptomato authored and Ms2ger committed Oct 26, 2020
1 parent ff301ae commit 770a1a2
Show file tree
Hide file tree
Showing 2 changed files with 44 additions and 20 deletions.
24 changes: 14 additions & 10 deletions docs/calendar.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,18 +37,20 @@ date.withCalendar(calendar).add({ months: 1 })

### Custom calendars

For specialized applications where you need to do calculations in a calendar system that is not supported by Intl, you can also implement your own `Temporal.Calendar` object.
To do this, create a class inheriting from `Temporal.Calendar`, call `super()` in the constructor with a calendar identifier, and implement all the methods.
Any subclass of `Temporal.Calendar` will be accepted in Temporal APIs where a built-in `Temporal.Calendar` would work.
For specialized applications where you need to do calculations in a calendar system that is not supported by Intl, you can implement a custom calendar.
There are two ways to do this.

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).

### Protocol
The recommended way is to create a class inheriting from `Temporal.Calendar`, call `super()` in the constructor with a calendar identifier string, and implement all the members except `id`, `toString()`, and `fields()`, which are optional.
If you don't implement the optional members, then the base class's default implementations will be used.

It's also possible for a plain object to be a custom calendar, without subclassing.
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` methods except for `fields()`.
It must not have a `calendar` property, so that it can be distinguished in `Temporal.Calendar.from()` from other Temporal objects that have a calendar.
It is possible to pass such an object into any Temporal API that would normally take a built-in `Temporal.Calendar`.
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()` method 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).

## Constructor

Expand Down Expand Up @@ -121,6 +123,8 @@ cal = Temporal.Calendar.from({id: 'mycalendar'});
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()`.

## Methods

### calendar.**year**(_date_: Temporal.Date) : number
Expand Down Expand Up @@ -307,7 +311,7 @@ It is called indirectly when using the `from()` static methods and `with()` meth
Custom calendars should override this method if they require more fields with which to denote the date than the standard `era`, `year`, `month`, and `day`.
The input array contains the field names that are necessary for a particular operation (for example, `'month'` and `'day'` for `Temporal.MonthDay.prototype.with()`), and the method should make a copy of the array and add whichever extra fields are necessary.

The default implementation of this method returns a copy of `fields`.
When subclassing `Temporal.Calendar`, this method doesn't need to be overridden, unless your calendar requires extra fields, because the default implementation returns a copy of `fields`.

Usage example:

Expand Down
40 changes: 30 additions & 10 deletions docs/timezone.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,22 +11,24 @@ Since `Temporal.Instant` and `Temporal.DateTime` do not contain any time zone in

## Custom time zones

For specialized applications where you need to do calculations in a time zone that is not supported by `Intl`, you can also implement your own `Temporal.TimeZone` object.
To do this, create a class inheriting from `Temporal.TimeZone`, call `super()` in the constructor with a time zone identifier, and implement the methods `getOffsetNanosecondsFor()`, `getPossibleInstantsFor()`, `getNextTransition()`, and `getPreviousTransition()`.
Any subclass of `Temporal.TimeZone` will be accepted in Temporal APIs where a built-in `Temporal.TimeZone` would work.
For specialized applications where you need to do calculations in a time zone that is not built in, you can implement a custom time zone.
There are two ways to do this.

The recommended way is to create a class inheriting from `Temporal.TimeZone`, call `super()` in the constructor with a time zone identifier string, and implement the methods `getOffsetNanosecondsFor()` and `getPossibleInstantsFor()`, and optionally `getNextTransition()` and `getPreviousTransition()`.
You don't need to implement any other methods because other properties and methods will use the base class's default implementations.

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.
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()`, `getDateTimeFor()`, `getZonedDateTimeFor()`, `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).
Each component must consist of between one and 14 characters.
Valid characters are ASCII letters, `.`, `-`, and `_`.
`-` may not appear as the first character of a component, and a component may not be a single dot `.` or two dots `..`.

### Protocol

It's also possible for a plain object to be a custom time zone, without subclassing.
The object must have `getOffsetNanosecondsFor()`, `getPossibleInstantsFor()`, and `toString()` methods.
It 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.
It is possible to pass such an object into any Temporal API that would normally take a built-in `Temporal.TimeZone`.

## Constructor

### **new Temporal.TimeZone**(_timeZoneIdentifier_: string) : Temporal.TimeZone
Expand Down Expand Up @@ -127,6 +129,8 @@ 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()`.

## Methods

### timeZone.**getOffsetNanosecondsFor**(_instant_: Temporal.Instant | string) : number
Expand Down Expand Up @@ -179,6 +183,8 @@ If `timeZone` is a UTC offset time zone, the return value of this method is effe

If `instant` is not a `Temporal.Instant` object, then it will be converted to one as if it were passed to `Temporal.Instant.from()`.

When subclassing `Temporal.TimeZone`, this method doesn't need to be overridden because the default implementation creates an offset string using the result of calling `timeZone.getOffsetNanosecondsFor()`.

Example usage:

```javascript
Expand Down Expand Up @@ -206,6 +212,8 @@ This method is one way to convert a `Temporal.Instant` to a `Temporal.ZonedDateT

If `instant` is not a `Temporal.Instant` object, then it will be converted to one as if it were passed to `Temporal.Instant.from()`.

When subclassing `Temporal.TimeZone`, this method doesn't need to be overridden because the default implementation gives a result equivalent to calling `new Temporal.ZonedDateTime(instant.epochNanoseconds, timeZone, calendar)`.

Example usage:

```javascript
Expand Down Expand Up @@ -234,6 +242,8 @@ This method is one way to convert a `Temporal.Instant` to a `Temporal.DateTime`.

If `instant` is not a `Temporal.Instant` object, then it will be converted to one as if it were passed to `Temporal.Instant.from()`.

When subclassing `Temporal.TimeZone`, this method doesn't need to be overridden because the default implementation creates a `Temporal.DateTime` from `instant` using a UTC offset which is the result of calling `timeZone.getOffsetNanosecondsFor()`.

Example usage:

```javascript
Expand Down Expand Up @@ -283,6 +293,8 @@ For usage examples and a more complete explanation of how this disambiguation wo

If the result is earlier or later than the range that `Temporal.Instant` can represent (approximately half a million years centered on the [Unix epoch](https://en.wikipedia.org/wiki/Unix_time)), then a `RangeError` will be thrown, no matter the value of `disambiguation`.

When subclassing `Temporal.TimeZone`, this method doesn't need to be overridden because the default implementation calls `timeZone.getPossibleInstantsFor()`, and if there is more than one possible instant, uses `disambiguation` to pick which one to return.

### timeZone.**getPossibleInstantsFor**(_dateTime_: Temporal.DateTime | object | string) : array<Temporal.Instant>

**Parameters:**
Expand Down Expand Up @@ -316,6 +328,9 @@ In that case, this method will return `null`.

If `instant` is not a `Temporal.Instant` object, then it will be converted to one as if it were passed to `Temporal.Instant.from()`.

When subclassing `Temporal.TimeZone`, this method should be overridden if the time zone changes offsets.
Single-offset time zones can use the default implementation which returns `null`.

Example usage:

```javascript
Expand All @@ -342,6 +357,9 @@ In that case, this method will return `null`.

If `instant` is not a `Temporal.Instant` object, then it will be converted to one as if it were passed to `Temporal.Instant.from()`.

When subclassing `Temporal.TimeZone`, this method should be overridden if the time zone changes offsets.
Single-offset time zones can use the default implementation which returns `null`.

Example usage:

```javascript
Expand Down Expand Up @@ -370,6 +388,8 @@ The reverse operation, recovering a `Temporal.TimeZone` object from a string, is
If you need to rebuild a `Temporal.TimeZone` object from a JSON string, then you need to know the names of the keys that should be interpreted as `Temporal.TimeZone`s.
In that case you can build a custom "reviver" function for your use case.

When subclassing `Temporal.TimeZone`, this method doesn't need to be overridden because the default implementation returns the result of calling `timeZone.toString()`.

Example usage:

```js
Expand Down

0 comments on commit 770a1a2

Please sign in to comment.