Skip to content

Commit

Permalink
Normative: Replace TimeZone transition methods with ZonedDateTime met…
Browse files Browse the repository at this point in the history
…hods

TimeZone.p.getNextTransition → ZonedDateTime.p.nextTransition
TimeZone.p.getPreviousTransition → ZonedDateTime.p.previousTransition

This is one step towards removing Temporal.TimeZone. The functionality of
querying UTC offset transition remains, but is moved to ZonedDateTime.

See: #2826
  • Loading branch information
ptomato committed May 25, 2024
1 parent 1c1ec18 commit 45921c3
Show file tree
Hide file tree
Showing 12 changed files with 151 additions and 170 deletions.
6 changes: 3 additions & 3 deletions docs/cookbook.md
Original file line number Diff line number Diff line change
Expand Up @@ -428,13 +428,13 @@ Take the difference between two `Temporal.Instant` instances as a `Temporal.Dura
```
<!-- prettier-ignore-end -->

### Nearest offset transition in a time zone
### Next offset transition in a time zone

Map a `Temporal.Instant` instance and a `Temporal.TimeZone` object into a `Temporal.Instant` instance representing the nearest following exact time at which there is an offset transition in the time zone (e.g., for setting reminders).
Map a `Temporal.ZonedDateTime` instance into another `Temporal.ZonedDateTime` instance representing the nearest following exact time at which there is an offset transition in the time zone (e.g., for setting reminders).

<!-- prettier-ignore-start -->
```javascript
{{cookbook/getInstantOfNearestOffsetTransitionToInstant.mjs}}
{{cookbook/getNextOffsetTransitionFromExactTime.mjs}}
```
<!-- prettier-ignore-end -->

Expand Down
2 changes: 1 addition & 1 deletion docs/cookbook/all.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,9 @@ import './getCurrentDate.mjs';
import './getElapsedDurationSinceInstant.mjs';
import './getFirstTuesdayOfMonth.mjs';
import './getInstantBeforeOldRecord.mjs';
import './getInstantOfNearestOffsetTransitionToInstant.mjs';
import './getInstantWithLocalTimeInZone.mjs';
import './getLocalizedArrival.mjs';
import './getNextOffsetTransitionFromExactTime.mjs';
import './getParseableZonedStringAtInstant.mjs';
import './getSortedLocalDateTimes.mjs';
import './getTimeStamp.mjs';
Expand Down
34 changes: 0 additions & 34 deletions docs/cookbook/getInstantOfNearestOffsetTransitionToInstant.mjs

This file was deleted.

4 changes: 2 additions & 2 deletions docs/cookbook/getInstantWithLocalTimeInZone.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -35,13 +35,13 @@ function getInstantWithLocalTimeInZone(dateTime, timeZone, disambiguation = 'ear
case 'clipEarlier':
if (possible.length === 0) {
const before = timeZone.getInstantFor(dateTime, { disambiguation: 'earlier' });
return timeZone.getNextTransition(before).add({ nanoseconds: -1 });
return before.toZonedDateTimeISO(timeZone).nextTransition().add({ nanoseconds: -1 }).toInstant();
}
return possible[0];
case 'clipLater':
if (possible.length === 0) {
const before = timeZone.getInstantFor(dateTime, { disambiguation: 'earlier' });
return timeZone.getNextTransition(before);
return before.toZonedDateTimeISO(timeZone).nextTransition().toInstant();
}
return possible[possible.length - 1];
}
Expand Down
33 changes: 33 additions & 0 deletions docs/cookbook/getNextOffsetTransitionFromExactTime.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/**
* Get the nearest following exact time that the given time zone transitions
* to another UTC offset, inclusive or exclusive.
*
* @param {Temporal.ZonedDateTime} zonedDateTime - Starting exact time and time
* zone to consider
* @param {boolean} inclusive - Include the start time, or not
* @returns {(Temporal.ZonedDateTime|null)} - Next UTC offset transition, or
* null if none known at this time
*/
function getNextOffsetTransitionFromExactTime(zonedDateTime, inclusive) {
let nearest;
if (inclusive) {
// In case instant itself is the exact time of a transition:
nearest = zonedDateTime.add({ nanoseconds: -1 }).nextTransition();
} else {
nearest = zonedDateTime.nextTransition();
}
return nearest;
}

const nycTime = Temporal.ZonedDateTime.from('2019-04-16T21:01Z[America/New_York]');

const nextTransition = getNextOffsetTransitionFromExactTime(nycTime, false);
assert.equal(nextTransition.toString(), '2019-11-03T01:00:00-05:00[America/New_York]');

// Inclusive
const sameTransition = getNextOffsetTransitionFromExactTime(nextTransition, true);
assert.equal(sameTransition.toString(), nextTransition.toString());

// No known future DST transitions in a time zone
const reginaTime = Temporal.ZonedDateTime.from('2019-04-16T21:01Z[America/Regina]');
assert.equal(getNextOffsetTransitionFromExactTime(reginaTime), null);
66 changes: 0 additions & 66 deletions docs/timezone.md
Original file line number Diff line number Diff line change
Expand Up @@ -446,72 +446,6 @@ See [Resolving ambiguity](./ambiguity.md) for usage examples and a more complete
Although this method is useful for implementing a custom time zone or custom disambiguation behavior, but otherwise `getInstantFor()` should be used instead, because it is more convenient, because it's compatible with the behavior of other methods and libraries, and because it always returns a single value.
For example, during "skipped" clock time like the hour after DST starts in the spring, `getPossibleInstantsFor()` returns an empty array while `getInstantFor()` returns a `Temporal.Instant`.

### timeZone.**getNextTransition**(_startingPoint_: Temporal.Instant | string) : Temporal.Instant

**Parameters:**

- `startingPoint` (`Temporal.Instant` or value convertible to one): Time after which to find the next UTC offset transition.

**Returns:** A `Temporal.Instant` object representing the next UTC offset transition in this time zone, or `null` if no transitions later than `startingPoint` could be found.

This method is used to calculate a possible future UTC offset transition after `startingPoint` for this time zone.
A "transition" is a point in time where the UTC offset of a time zone changes, for example when Daylight Saving Time starts or stops.
Transitions can also be caused by other political changes like a country permanently changing the UTC offset of its time zone.

The returned `Temporal.Instant` will represent the first nanosecond where the new UTC offset is used, not the last nanosecond where the previous UTC offset is used.

When no more transitions are expected, this method will return `null`.
Some time zones (e.g., `Etc/GMT+5` or `-05:00`) have no offset transitions and will return `null` for all values of `startingPoint`.

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
// How long until the next offset change from now, in the current location?
tz = Temporal.Now.timeZone();
now = Temporal.Now.instant();
nextTransition = tz.getNextTransition(now);
duration = now.until(nextTransition);
duration.toLocaleString(); // output will vary
```

### timeZone.**getPreviousTransition**(_startingPoint_: Temporal.Instant | string) : Temporal.Instant

**Parameters:**

- `startingPoint` (`Temporal.Instant` or value convertible to one): Time before which to find the previous UTC offset transition.

**Returns:** A `Temporal.Instant` object representing the previous UTC offset transition in this time zone, or `null` if no transitions earlier than `startingPoint` could be found.

This method is used to calculate a possible past UTC offset transition before `startingPoint` for this time zone.
A "transition" is a point in time where the UTC offset of a time zone changes, for example when Daylight Saving Time starts or stops.
Transitions can also be caused by other political changes like a country permanently changing the UTC offset of its time zone.

The returned `Temporal.Instant` will represent the first nanosecond where the new UTC offset is used, not the last nanosecond where the previous UTC offset is used.

When no previous transitions exist, this method will return `null`.
Some time zones (e.g., `Etc/GMT+5` or `-05:00`) have no offset transitions and will return `null` for all values of `startingPoint`.

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
// How long until the previous offset change from now, in the current location?
tz = Temporal.Now.timeZone();
now = Temporal.Now.instant();
previousTransition = tz.getPreviousTransition(now);
duration = previousTransition.until(now);
duration.toLocaleString(); // output will vary
```

### timeZone.**toString**() : string

**Returns:** The string given by `timeZone.id`.
Expand Down
50 changes: 50 additions & 0 deletions docs/zoneddatetime.md
Original file line number Diff line number Diff line change
Expand Up @@ -1090,6 +1090,56 @@ zdt = Temporal.ZonedDateTime.from('2018-11-04T12:00-02:00[America/Sao_Paulo]').s
```
<!-- prettier-ignore-end -->

### zonedDateTime.**nextTransition**() : Temporal.ZonedDateTime | null

**Returns:** A `Temporal.ZonedDateTime` object representing the next UTC offset transition in `zonedDateTime`'s time zone, or `null` if no transitions later than `zonedDateTime` could be found.

This method is used to calculate a possible future UTC offset transition after `zonedDateTime` for its time zone.
A "transition" is a point in time where the UTC offset of a time zone changes, for example when Daylight Saving Time starts or stops.
Transitions can also be caused by other political changes like a country permanently changing the UTC offset of its time zone.

The returned `Temporal.ZonedDateTime` will represent the first nanosecond where the new UTC offset is used, not the last nanosecond where the previous UTC offset is used.

When no more transitions are expected, this method will return `null`.
Some time zones (e.g., `Etc/GMT+5` or `-05:00`) have no offset transitions.
If `zonedDateTime` has one of these time zones, this method will always return `null`.

Example usage:

```javascript
// How long until the next offset change from now, in the current location?
tz = Temporal.Now.timeZoneId();
now = Temporal.Now.zonedDateTimeISO(tz);
nextTransition = now.nextTransition();
duration = now.until(nextTransition);
duration.toLocaleString(); // output will vary
```

### zonedDateTime.**previousTransition**() : Temporal.ZonedDateTime | null

**Returns:** A `Temporal.ZonedDateTime` object representing the previous UTC offset transition in `zonedDateTime`'s time zone, or `null` if no transitions earlier than `zonedDateTime` could be found.

This method is used to calculate a possible past UTC offset transition before `zonedDateTime` for its time zone.
A "transition" is a point in time where the UTC offset of a time zone changes, for example when Daylight Saving Time starts or stops.
Transitions can also be caused by other political changes like a country permanently changing the UTC offset of its time zone.

The returned `Temporal.ZonedDateTime` will represent the first nanosecond where the new UTC offset is used, not the last nanosecond where the previous UTC offset is used.

When no previous transitions exist, this method will return `null`.
Some time zones (e.g., `Etc/GMT+5` or `-05:00`) have no offset transitions.
If `zonedDateTime` has one of these time zones, this method will always return `null`.

Example usage:

```javascript
// How long until the previous offset change from now, in the current location?
tz = Temporal.Now.timeZoneId();
now = Temporal.Now.zonedDateTimeISO(tz);
previousTransition = now.previousTransition();
duration = previousTransition.until(now);
duration.toLocaleString(); // output will vary
```

### zonedDateTime.**equals**(_other_: Temporal.ZonedDateTime) : boolean

**Parameters:**
Expand Down
4 changes: 2 additions & 2 deletions polyfill/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1111,8 +1111,6 @@ export namespace Temporal {
dateTime: Temporal.PlainDateTime | PlainDateTimeLike | string,
options?: ToInstantOptions
): Temporal.Instant;
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;
toJSON(): string;
Expand Down Expand Up @@ -1261,6 +1259,8 @@ export namespace Temporal {
roundTo: RoundTo<'day' | 'hour' | 'minute' | 'second' | 'millisecond' | 'microsecond' | 'nanosecond'>
): Temporal.ZonedDateTime;
startOfDay(): Temporal.ZonedDateTime;
nextTransition(): Temporal.ZonedDateTime | null;
previousTransition(): Temporal.ZonedDateTime | null;
toInstant(): Temporal.Instant;
toPlainDateTime(): Temporal.PlainDateTime;
toPlainDate(): Temporal.PlainDate;
Expand Down
30 changes: 0 additions & 30 deletions polyfill/lib/timezone.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -120,36 +120,6 @@ export class TimeZone {
);
return possibleEpochNs.map((ns) => new Instant(ns));
}
getNextTransition(startingPoint) {
if (!ES.IsTemporalTimeZone(this)) throw new TypeError('invalid receiver');
startingPoint = ES.ToTemporalInstant(startingPoint);
const id = GetSlot(this, TIMEZONE_ID);

// Offset time zones or UTC have no transitions
if (ES.IsOffsetTimeZoneIdentifier(id) || id === 'UTC') {
return null;
}

let epochNanoseconds = GetSlot(startingPoint, EPOCHNANOSECONDS);
const Instant = GetIntrinsic('%Temporal.Instant%');
epochNanoseconds = ES.GetNamedTimeZoneNextTransition(id, epochNanoseconds);
return epochNanoseconds === null ? null : new Instant(epochNanoseconds);
}
getPreviousTransition(startingPoint) {
if (!ES.IsTemporalTimeZone(this)) throw new TypeError('invalid receiver');
startingPoint = ES.ToTemporalInstant(startingPoint);
const id = GetSlot(this, TIMEZONE_ID);

// Offset time zones or UTC have no transitions
if (ES.IsOffsetTimeZoneIdentifier(id) || id === 'UTC') {
return null;
}

let epochNanoseconds = GetSlot(startingPoint, EPOCHNANOSECONDS);
const Instant = GetIntrinsic('%Temporal.Instant%');
epochNanoseconds = ES.GetNamedTimeZonePreviousTransition(id, epochNanoseconds);
return epochNanoseconds === null ? null : new Instant(epochNanoseconds);
}
toString() {
if (!ES.IsTemporalTimeZone(this)) throw new TypeError('invalid receiver');
return GetSlot(this, TIMEZONE_ID);
Expand Down
26 changes: 26 additions & 0 deletions polyfill/lib/zoneddatetime.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -525,6 +525,32 @@ export class ZonedDateTime {
const instant = ES.GetInstantFor(timeZoneRec, dtStart, 'compatible');
return ES.CreateTemporalZonedDateTime(GetSlot(instant, EPOCHNANOSECONDS), timeZoneRec.receiver, calendar);
}
nextTransition() {
if (!ES.IsTemporalZonedDateTime(this)) throw new TypeError('invalid receiver');
const timeZone = GetSlot(this, TIME_ZONE);
const id = ES.ToTemporalTimeZoneIdentifier(timeZone);

// Offset time zones or UTC have no transitions
if (ES.IsOffsetTimeZoneIdentifier(id) || id === 'UTC') {
return null;
}

const epochNanoseconds = ES.GetNamedTimeZoneNextTransition(id, GetSlot(this, EPOCHNANOSECONDS));
return epochNanoseconds === null ? null : new ZonedDateTime(epochNanoseconds, timeZone, GetSlot(this, CALENDAR));
}
previousTransition() {
if (!ES.IsTemporalZonedDateTime(this)) throw new TypeError('invalid receiver');
const timeZone = GetSlot(this, TIME_ZONE);
const id = ES.ToTemporalTimeZoneIdentifier(timeZone);

// Offset time zones or UTC have no transitions
if (ES.IsOffsetTimeZoneIdentifier(id) || id === 'UTC') {
return null;
}

const epochNanoseconds = ES.GetNamedTimeZonePreviousTransition(id, GetSlot(this, EPOCHNANOSECONDS));
return epochNanoseconds === null ? null : new ZonedDateTime(epochNanoseconds, timeZone, GetSlot(this, CALENDAR));
}
toInstant() {
if (!ES.IsTemporalZonedDateTime(this)) throw new TypeError('invalid receiver');
const TemporalInstant = GetIntrinsic('%Temporal.Instant%');
Expand Down
32 changes: 0 additions & 32 deletions spec/timezone.html
Original file line number Diff line number Diff line change
Expand Up @@ -201,38 +201,6 @@ <h1>Temporal.TimeZone.prototype.getPossibleInstantsFor ( _dateTime_ )</h1>
</emu-alg>
</emu-clause>

<emu-clause id="sec-temporal.timezone.prototype.getnexttransition">
<h1>Temporal.TimeZone.prototype.getNextTransition ( _startingPoint_ )</h1>
<p>
This method performs the following steps when called:
</p>
<emu-alg>
1. Let _timeZone_ be the *this* value.
1. Perform ? RequireInternalSlot(_timeZone_, [[InitializedTemporalTimeZone]]).
1. Set _startingPoint_ to ? ToTemporalInstant(_startingPoint_).
1. If _timeZone_.[[OffsetMinutes]] is not ~empty~, return *null*.
1. Let _transition_ be GetNamedTimeZoneNextTransition(_timeZone_.[[Identifier]], _startingPoint_.[[Nanoseconds]]).
1. If _transition_ is *null*, return *null*.
1. Return ! CreateTemporalInstant(_transition_).
</emu-alg>
</emu-clause>

<emu-clause id="sec-temporal.timezone.prototype.getprevioustransition">
<h1>Temporal.TimeZone.prototype.getPreviousTransition ( _startingPoint_ )</h1>
<p>
This method performs the following steps when called:
</p>
<emu-alg>
1. Let _timeZone_ be the *this* value.
1. Perform ? RequireInternalSlot(_timeZone_, [[InitializedTemporalTimeZone]]).
1. Set _startingPoint_ to ? ToTemporalInstant(_startingPoint_).
1. If _timeZone_.[[OffsetMinutes]] is not ~empty~, return *null*.
1. Let _transition_ be GetNamedTimeZonePreviousTransition(_timeZone_.[[Identifier]], _startingPoint_.[[Nanoseconds]]).
1. If _transition_ is *null*, return *null*.
1. Return ! CreateTemporalInstant(_transition_).
</emu-alg>
</emu-clause>

<emu-clause id="sec-temporal.timezone.prototype.tostring">
<h1>Temporal.TimeZone.prototype.toString ( )</h1>
<p>
Expand Down
Loading

0 comments on commit 45921c3

Please sign in to comment.