Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use strings in slots to optimize builtin calendar and time zone implementations #2482

Merged
merged 14 commits into from
Apr 7, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 8 additions & 7 deletions docs/calendar.md
Original file line number Diff line number Diff line change
Expand Up @@ -107,16 +107,17 @@ 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 the base calendar.
The class must override `toString()` to return its own identifier.
Overriding all the other members is optional.
If you don't override the optional members, then they will behave as in 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 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 @@ -235,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
3 changes: 2 additions & 1 deletion docs/cookbook.md
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,8 @@ Legacy `Date` represents an exact time, so it's straightforward to convert a `Te

### Time zone object from name

`Temporal.TimeZone.from()` can convert an IANA time zone name into a `Temporal.TimeZone` object.
`Temporal.TimeZone.from()` can convert an IANA time zone name into a `Temporal.TimeZone` object, if you need to call `Temporal.TimeZone` methods.
Usually this is not necessary.

<!-- prettier-ignore-start -->
```javascript
Expand Down
4 changes: 2 additions & 2 deletions docs/cookbook/calculateDailyOccurrence.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
*
* @param {Temporal.PlainDate} startDate - Starting date
* @param {Temporal.PlainTime} plainTime - Local time that event occurs at
* @param {Temporal.TimeZone} timeZone - Time zone in which event is defined
* @param {string} timeZone - Time zone in which event is defined
*/
function* calculateDailyOccurrence(startDate, plainTime, timeZone) {
for (let date = startDate; ; date = date.add({ days: 1 })) {
Expand All @@ -16,7 +16,7 @@ function* calculateDailyOccurrence(startDate, plainTime, timeZone) {
// Daily meeting at 8 AM California time
const startDate = Temporal.PlainDate.from('2017-03-10');
const time = Temporal.PlainTime.from('08:00');
const timeZone = Temporal.TimeZone.from('America/Los_Angeles');
const timeZone = 'America/Los_Angeles';
const iter = calculateDailyOccurrence(startDate, time, timeZone);

assert.equal(iter.next().value.toString(), '2017-03-10T16:00:00Z');
Expand Down
4 changes: 2 additions & 2 deletions docs/cookbook/fromLegacyDate.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ assert.equal(instant.toString(), '1970-01-01T00:00:01Z');
// will be the user's time zone, but on a server the value may not
// be what you expect, so avoid doing this in a server context.

const zoned = instant.toZonedDateTimeISO(Temporal.Now.timeZone());
const zoned = instant.toZonedDateTimeISO(Temporal.Now.timeZoneId());

assert.equal(zoned.epochMilliseconds, legacyDate.getTime());

Expand All @@ -32,7 +32,7 @@ assert.equal(zoned.epochMilliseconds, legacyDate.getTime());
const zoned2 = instant.toZonedDateTimeISO('Asia/Shanghai');

assert.equal(zoned2.epochMilliseconds, legacyDate.getTime());
assert.equal(zoned2.timeZone.id, 'Asia/Shanghai');
assert.equal(zoned2.timeZoneId, 'Asia/Shanghai');

// (And if the legacy Date instance was accessed using the
// getUTCFullYear(), getUTCMonth(), etc. methods, consider just
Expand Down
6 changes: 3 additions & 3 deletions docs/cookbook/fromLegacyDateOnly.mjs
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
// Convert a year/month/day `Date` to a `Temporal.PlainDate`. Uses the caller's time zone.
let date = new Date(2000, 0, 1); // => Sat Jan 01 2000 00:00:00 GMT-0800 (Pacific Standard Time)
let plainDate = date
.toTemporalInstant() // => 2000-01-01T08:00:00Z
.toZonedDateTimeISO(Temporal.Now.timeZone()) // => 2000-01-01T00:00:00-08:00[America/Los_Angeles]
.toPlainDate(); // => 2000-01-01
.toTemporalInstant() // => 2000-01-01T08:00:00Z
.toZonedDateTimeISO(Temporal.Now.timeZoneId()) // => 2000-01-01T00:00:00-08:00[America/Los_Angeles]
.toPlainDate(); // => 2000-01-01

assert.equal(plainDate.toString(), '2000-01-01');

Expand Down
5 changes: 3 additions & 2 deletions docs/cookbook/getBusinessOpenStateText.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,12 @@ function getBusinessOpenStateText(now, businessHours, soonWindow) {
const index = (openDate.dayOfWeek + 7) % 7;
if (!businessHours[index]) continue;

const timeZone = now.timeZoneId;
const { open: openTime, close: closeTime } = businessHours[index];
const open = openDate.toZonedDateTime({ plainTime: openTime, timeZone: now.timeZone });
const open = openDate.toZonedDateTime({ plainTime: openTime, timeZone });
const isWrap = Temporal.PlainTime.compare(closeTime, openTime) < 0;
const closeDate = isWrap ? openDate.add({ days: 1 }) : openDate;
const close = closeDate.toZonedDateTime({ plainTime: closeTime, timeZone: now.timeZone });
const close = closeDate.toZonedDateTime({ plainTime: closeTime, timeZone });

if (inRange(now, open, close)) {
return compare(now, close.subtract(soonWindow)) >= 0 ? 'closing soon' : 'open';
Expand Down
2 changes: 1 addition & 1 deletion docs/cookbook/localTimeForFutureEvents.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ const tc39meetings = [

// To follow the meetings remotely from Tokyo, calculate the times you would
// need to join:
const localTimeZone = Temporal.TimeZone.from('Asia/Tokyo');
const localTimeZone = 'Asia/Tokyo';
const localTimes = tc39meetings.map(({ dateTime, timeZone }) => {
return Temporal.PlainDateTime.from(dateTime)
.toZonedDateTime(timeZone, { disambiguation: 'reject' })
Expand Down
6 changes: 3 additions & 3 deletions docs/cookbook/meetingPlanner.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@ const browserCalendar = new Intl.DateTimeFormat().resolvedOptions().calendar;
const now = Temporal.Now.zonedDateTime(browserCalendar);
const timeZones = [
{ name: 'Here', tz: now.timeZone },
{ name: 'New York', tz: Temporal.TimeZone.from('America/New_York') },
{ name: 'London', tz: Temporal.TimeZone.from('Europe/London') },
{ name: 'Tokyo', tz: Temporal.TimeZone.from('Asia/Tokyo') }
{ name: 'New York', tz: 'America/New_York' },
{ name: 'London', tz: 'Europe/London' },
{ name: 'Tokyo', tz: 'Asia/Tokyo' }
];

// Start the table at midnight local time
Expand Down
4 changes: 2 additions & 2 deletions docs/cookbook/nextWeeklyOccurrence.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,13 @@ function nextWeeklyOccurrence(now, weekday, eventTime, eventTimeZone) {
nextOccurrence = nextOccurrence.add({ weeks: 1 });
}

return nextOccurrence.withTimeZone(now.timeZone);
return nextOccurrence.withTimeZone(now.timeZoneId);
}

// "Weekly on Thursdays at 08:45 California time":
const weekday = 4;
const eventTime = Temporal.PlainTime.from('08:45');
const eventTimeZone = Temporal.TimeZone.from('America/Los_Angeles');
const eventTimeZone = 'America/Los_Angeles';

const rightBefore = Temporal.ZonedDateTime.from('2020-03-26T15:30+00:00[Europe/London]');
let next = nextWeeklyOccurrence(rightBefore, weekday, eventTime, eventTimeZone);
Expand Down
17 changes: 12 additions & 5 deletions docs/cookbook/stockExchangeTimeZone.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
* market day.
* */

const tz = Temporal.TimeZone.from('America/New_York');
const tz = 'America/New_York';
const openTime = Temporal.PlainTime.from('09:30');
const closeTime = Temporal.PlainTime.from('16:00');
function isMarketOpenDate(date) {
Expand Down 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,14 @@ 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;
}
toJSON() {
return this.#id;
}
}

Expand Down Expand Up @@ -187,20 +194,20 @@ zdt = Temporal.ZonedDateTime.from('2020-11-12T18:50-08:00[America/Los_Angeles]')
inNYSE = zdt.withTimeZone(tzNYSE);
isOpen = inNYSE.toPlainDateTime().toZonedDateTime(tzNYSE).equals(inNYSE);
assert.equal(isOpen, false);
nextOpen = inNYSE.timeZone.getNextTransition(zdt.toInstant()).toZonedDateTimeISO(zdt.timeZone);
nextOpen = inNYSE.getTimeZone().getNextTransition(zdt.toInstant()).toZonedDateTimeISO(zdt.timeZoneId);
assert.equal(nextOpen.toString(), '2020-11-13T06:30:00-08:00[America/Los_Angeles]');

zdt = Temporal.ZonedDateTime.from('2020-11-12T12:50-08:00[America/Los_Angeles]');
inNYSE = zdt.withTimeZone(tzNYSE);
isOpen = inNYSE.toPlainDateTime().toZonedDateTime(tzNYSE).equals(inNYSE);
assert.equal(isOpen, true);
todayClose = inNYSE.timeZone.getNextTransition(zdt.toInstant()).toZonedDateTimeISO(zdt.timeZone);
todayClose = inNYSE.getTimeZone().getNextTransition(zdt.toInstant()).toZonedDateTimeISO(zdt.timeZoneId);
assert.equal(todayClose.toString(), '2020-11-12T13:00:00-08:00[America/Los_Angeles]');

// 5. For any particular market date, what were the opening and closing clock times in NYC?
date = Temporal.PlainDate.from('2020-11-09');
openInstant = date.toZonedDateTime(tzNYSE).toInstant();
closeInstant = date.toZonedDateTime(tzNYSE).timeZone.getNextTransition(openInstant);
closeInstant = date.toZonedDateTime(tzNYSE).getTimeZone().getNextTransition(openInstant);
assert.equal(openInstant.toZonedDateTimeISO('America/New_York').toPlainTime().toString(), '09:30:00');
assert.equal(closeInstant.toZonedDateTimeISO('America/New_York').toPlainTime().toString(), '16:00:00');

Expand Down
2 changes: 1 addition & 1 deletion docs/cookbook/storageTank.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
// tankDataX is an array of Temporal.Instant, and tankDataY is an array of numbers.

// Show data starting from the most recent midnight in the tank's location (Stockholm)
const tankTimeZone = Temporal.TimeZone.from('Europe/Stockholm');
const tankTimeZone = 'Europe/Stockholm';
const labelFormatter = new Intl.DateTimeFormat(undefined, {
weekday: 'short',
hour: 'numeric',
Expand Down
11 changes: 6 additions & 5 deletions docs/now.md
Original file line number Diff line number Diff line change
Expand Up @@ -82,23 +82,24 @@ timeit(() => JSON.parse(someData));
// The function took PT0.001031756S
```

### Temporal.Now.**timeZone**() : Temporal.TimeZone
### Temporal.Now.**timeZoneId**() : string

**Returns:** a `Temporal.TimeZone` object representing the time zone according to the current system settings.
**Returns:** The identifier of time zone according to the current system settings.

This method gets the current system time zone.
This method gets the identifier of the current system time zone.
This will usually be a named [IANA time zone](https://www.iana.org/time-zones), as that is how most people configure their computers.

Example usage:

```js
// When is the next daylight saving change from now, in the current location?
tz = Temporal.Now.timeZone();
id = Temporal.Now.timeZoneId();
now = Temporal.Now.instant();
tz = Temporal.TimeZone.from(id);
nextTransition = tz.getNextTransition(now);
before = tz.getOffsetStringFor(nextTransition.subtract({ nanoseconds: 1 }));
after = tz.getOffsetStringFor(nextTransition.add({ nanoseconds: 1 }));
console.log(`At ${nextTransition.toZonedDateTimeISO(tz)} the offset will change from UTC ${before} to ${after}`);
console.log(`At ${nextTransition.toZonedDateTimeISO(id)} the offset will change from UTC ${before} to ${after}`);
// example output:
// At 2021-03-14T03:00:00-07:00[America/Los_Angeles] the offset will change from UTC -08:00 to -07:00
```
Expand Down
27 changes: 18 additions & 9 deletions docs/plaindate.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,14 @@ It can also be combined with a `Temporal.PlainTime` to yield a "zoneless" `Tempo

## Constructor

### **new Temporal.PlainDate**(_isoYear_: number, _isoMonth_: number, _isoDay_: number, _calendar_?: string | object) : Temporal.PlainDate
### **new Temporal.PlainDate**(_isoYear_: number, _isoMonth_: number, _isoDay_: number, _calendar_: string | object = "iso8601") : Temporal.PlainDate

**Parameters:**

- `isoYear` (number): A year.
- `isoMonth` (number): A month, ranging between 1 and 12 inclusive.
- `isoDay` (number): A day of the month, ranging between 1 and 31 inclusive.
- `calendar` (optional `Temporal.Calendar`, plain object, or string): A calendar to project the date into.
- `calendar` (optional string, `Temporal.Calendar` instance, or plain object): A calendar to project the date into.

**Returns:** a new `Temporal.PlainDate` object.

Expand All @@ -37,6 +37,9 @@ Together, `isoYear`, `isoMonth`, and `isoDay` must represent a valid date in tha
The range of allowed values for this type is exactly enough that calling [`toPlainDate()`](./plaindatetime.md#toPlainDate) on any valid `Temporal.PlainDateTime` will succeed.
If `isoYear`, `isoMonth`, and `isoDay` form a date outside of this range, then this function will throw a `RangeError`.

Usually `calendar` will be a string containing the identifier of a built-in calendar, such as `'islamic'` or `'gregory'`.
Use an object if you need to supply [custom calendar behaviour](./calendar.md#custom-calendars).

> **NOTE**: The `isoMonth` argument ranges from 1 to 12, which is different from legacy `Date` where months are represented by zero-based indices (0 to 11).

Usage examples:
Expand Down Expand Up @@ -103,10 +106,8 @@ date = Temporal.PlainDate.from(Temporal.PlainDateTime.from('2006-08-24T15:43:27'
// => 2006-08-24
// same as above; Temporal.PlainDateTime has year, month, and day properties

calendar = Temporal.Calendar.from('islamic');
date = Temporal.PlainDate.from({ year: 1427, month: 8, day: 1, calendar }); // => 2006-08-24[u-ca=islamic]
date = Temporal.PlainDate.from({ year: 1427, month: 8, day: 1, calendar: 'islamic' });
// => 2006-08-24[u-ca=islamic] (same as above)
// => 2006-08-24[u-ca=islamic]

// Different overflow modes
date = Temporal.PlainDate.from({ year: 2001, month: 13, day: 1 }, { overflow: 'constrain' });
Expand Down Expand Up @@ -199,9 +200,10 @@ date.day; // => 18
```
<!-- prettier-ignore-end -->

### date.**calendar** : object
### date.**calendarId** : string

The `calendar` read-only property gives the calendar that the `year`, `month`, and `day` properties are interpreted in.
The `calendarId` read-only property gives the identifier of the calendar that the `year`, `month`, `monthCode`, and `day` properties are interpreted in.
If the date was created with a custom calendar object, this gives the `id` property of that object.

### date.**era** : string | undefined

Expand Down Expand Up @@ -805,9 +807,16 @@ date.toPlainYearMonth(); // => 2006-08
date.toPlainMonthDay(); // => 08-24
```

### date.**getISOFields**(): { isoYear: number, isoMonth: number, isoDay: number, calendar: object }
### date.**getCalendar**(): object

**Returns:** a `Temporal.Calendar` instance or plain object representing the calendar in which `date` is reckoned.

This method is mainly useful if you need an object on which to call calendar methods.
Most code will not need to use it.

### date.**getISOFields**(): { isoYear: number, isoMonth: number, isoDay: number, calendar: string | object }

**Returns:** a plain object with properties expressing `date` in the ISO 8601 calendar, as well as the value of `date.calendar`.
**Returns:** a plain object with properties expressing `date` in the ISO 8601 calendar, as well as the calendar (usually a string, but may be an object) in which `date` is reckoned.

This method is mainly useful if you are implementing a custom calendar.
Most code will not need to use it.
Expand Down
Loading