Time Zones

Abstract Operations

In ECMA-262, many time-zone-related sections and abstract operations are contained in the Date Objects section of the specification. It may be appropriate to move those sections here, for example:
  • Time Zone Identifiers
  • AvailableNamedTimeZoneIdentifiers
  • SystemTimeZoneIdentifier
  • IsTimeZoneOffsetString
  • GetNamedTimeZoneEpochNanoseconds
  • GetNamedTimeZoneOffsetNanoseconds

GetAvailableNamedTimeZoneIdentifier ( _timeZoneIdentifier_: a named time zone identifier, ): either a Time Zone Identifier Record or ~empty~

description
If _timeZoneIdentifier_ is an available named time zone identifier, then it returns one of the records in the List returned by AvailableNamedTimeZoneIdentifiers. Otherwise, ~empty~ will be returned.
1. For each element _record_ of AvailableNamedTimeZoneIdentifiers(), do 1. If _record_.[[Identifier]] is an ASCII-case-insensitive match for _timeZoneIdentifier_, return _record_. 1. Return ~empty~.

For any _timeZoneIdentifier_, or any value that is an ASCII-case-insensitive match for it, the result of this operation must remain the same for the lifetime of the surrounding agent. Specifically, if that result is a Time Zone Identifier Record, its fields must contain the same values.

Furthermore, time zone identifiers must not dynamically change from primary to non-primary or vice versa during the lifetime of the surrounding agent, meaning that if _timeZoneIdentifier_ is an ASCII-case-insensitive match for the [[PrimaryIdentifier]] field of the result of a previous call to GetAvailableNamedTimeZoneIdentifier, then GetAvailableNamedTimeZoneIdentifier(_timeZoneIdentifier_) must return a record where [[Identifier]] is [[PrimaryIdentifier]].

Due to the complexity of supporting these requirements, it is recommended that the result of AvailableNamedTimeZoneIdentifiers (and therefore GetAvailableNamedTimeZoneIdentifier) remains the same for the lifetime of the surrounding agent.

GetISOPartsFromEpoch ( _epochNanoseconds_: an integer, ): an ISO Date-Time Record

description
It returns the components of a date in UTC corresponding to the given number of nanoseconds since the epoch.
1. Assert: IsValidEpochNanoseconds(ℤ(_epochNanoseconds_)) is *true*. 1. Let _remainderNs_ be _epochNanoseconds_ modulo 106. 1. Let _epochMilliseconds_ be 𝔽((_epochNanoseconds_ - _remainderNs_) / 106). 1. Let _year_ be EpochTimeToEpochYear(_epochMilliseconds_). 1. Let _month_ be EpochTimeToMonthInYear(_epochMilliseconds_) + 1. 1. Let _day_ be EpochTimeToDate(_epochMilliseconds_). 1. Let _hour_ be ℝ(HourFromTime(_epochMilliseconds_)). 1. Let _minute_ be ℝ(MinFromTime(_epochMilliseconds_)). 1. Let _second_ be ℝ(SecFromTime(_epochMilliseconds_)). 1. Let _millisecond_ be ℝ(msFromTime(_epochMilliseconds_)). 1. Let _microsecond_ be floor(_remainderNs_ / 1000). 1. Assert: _microsecond_ < 1000. 1. Let _nanosecond_ be _remainderNs_ modulo 1000. 1. Let _isoDate_ be CreateISODateRecord(_year_, _month_, _day_). 1. Let _time_ be CreateTimeRecord(_hour_, _minute_, _second_, _millisecond_, _microsecond_, _nanosecond_). 1. Return CombineISODateAndTimeRecord(_isoDate_, _time_).

GetNamedTimeZoneNextTransition ( _timeZoneIdentifier_: an available named time zone identifier, _epochNanoseconds_: a BigInt, ): a BigInt or *null*

The returned value _t_ represents the number of nanoseconds since the epoch that corresponds to the first time zone transition after _epochNanoseconds_ in the IANA time zone identified by _timeZoneIdentifier_. The operation returns *null* if no such transition exists for which _t_ ≤ ℤ(nsMaxInstant).

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. The returned value _t_ represents the first nanosecond where the new UTC offset is used in this time zone, not the last nanosecond where the previous UTC offset is used.

Given the same values of _timeZoneIdentifier_ and _epochNanoseconds_, the result must be the same for the lifetime of the surrounding agent.

The minimum implementation of GetNamedTimeZoneNextTransition for ECMAScript implementations that do not include local political rules for any time zones performs the following steps when called:

1. Assert: _timeZoneIdentifier_ is *"UTC"*. 1. Return *null*.

GetNamedTimeZonePreviousTransition ( _timeZoneIdentifier_: an available named time zone identifier, _epochNanoseconds_: a BigInt, ): a BigInt or *null*

The returned value _t_ represents the number of nanoseconds since the epoch that corresponds to the last time zone transition before _epochNanoseconds_ in the IANA time zone identified by _timeZoneIdentifier_. The operation returns *null* if no such transition exists for which _t_ ≥ ℤ(nsMinInstant).

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. The returned value _t_ represents the first nanosecond where the new UTC offset is used in this time zone, not the last nanosecond where the previous UTC offset is used.

Given the same values of _timeZoneIdentifier_ and _epochNanoseconds_, the result must be the same for the lifetime of the surrounding agent.

The minimum implementation of GetNamedTimeZonePreviousTransition for ECMAScript implementations that do not include local political rules for any time zones performs the following steps when called:

1. Assert: _timeZoneIdentifier_ is *"UTC"*. 1. Return *null*.

FormatOffsetTimeZoneIdentifier ( _offsetMinutes_: an integer, optional _style_: ~separated~ or ~unseparated~, ): an offset time zone identifier

description
It formats a UTC offset, in minutes, into a UTC offset string. If _style_ is ~separated~ or not present, then the output will be formatted like ±HH:MM. If _style_ is ~unseparated~, then the output will be formatted like ±HHMM.
1. If _offsetMinutes_ ≥ 0, let _sign_ be the code unit 0x002B (PLUS SIGN); otherwise, let _sign_ be the code unit 0x002D (HYPHEN-MINUS). 1. Let _absoluteMinutes_ be abs(_offsetMinutes_). 1. Let _hour_ be floor(_absoluteMinutes_ / 60). 1. Let _minute_ be _absoluteMinutes_ modulo 60. 1. Let _timeString_ be FormatTimeString(_hour_, _minute_, 0, 0, ~minute~, _style_). 1. Return the string-concatenation of _sign_ and _timeString_.

FormatUTCOffsetNanoseconds ( _offsetNanoseconds_: an integer, ): a String

description
If the offset represents an integer number of minutes, then the output will be formatted like ±HH:MM. Otherwise, the output will be formatted like ±HH:MM:SS or (if the offset does not evenly divide into seconds) ±HH:MM:SS.fff… where the "fff" part is a sequence of at least 1 and at most 9 fractional seconds digits with no trailing zeroes.
1. If _offsetNanoseconds_ ≥ 0, let _sign_ be the code unit 0x002B (PLUS SIGN); otherwise, let _sign_ be the code unit 0x002D (HYPHEN-MINUS). 1. Let _absoluteNanoseconds_ be abs(_offsetNanoseconds_). 1. Let _hour_ be floor(_absoluteNanoseconds_ / (3600 × 109)). 1. Let _minute_ be floor(_absoluteNanoseconds_ / (60 × 109)) modulo 60. 1. Let _second_ be floor(_absoluteNanoseconds_ / 109) modulo 60. 1. Let _subSecondNanoseconds_ be _absoluteNanoseconds_ modulo 109. 1. If _second_ = 0 and _subSecondNanoseconds_ = 0, let _precision_ be ~minute~; otherwise, let _precision_ be ~auto~. 1. Let _timeString_ be FormatTimeString(_hour_, _minute_, _second_, _subSecondNanoseconds_, _precision_). 1. Return the string-concatenation of _sign_ and _timeString_.

FormatDateTimeUTCOffsetRounded ( _offsetNanoseconds_: an integer, ): a String

description
It rounds _offsetNanoseconds_ to the nearest minute boundary and formats the rounded value into a ±HH:MM format, to support available named time zones that may have sub-minute offsets.
1. Set _offsetNanoseconds_ to RoundNumberToIncrement(_offsetNanoseconds_, 60 × 109, ~half-expand~). 1. Let _offsetMinutes_ be _offsetNanoseconds_ / (60 × 109). 1. Assert: _offsetMinutes_ is an integer. 1. Return FormatOffsetTimeZoneIdentifier(_offsetMinutes_).

ToTemporalTimeZoneIdentifier ( _temporalTimeZoneLike_: an ECMAScript value, ): either a normal completion containing an available time zone identifier or a throw completion

description
It attempts to derive a value from _temporalTimeZoneLike_ that is an available time zone identifier, and returns that value if found or throws an exception if not.
1. If _temporalTimeZoneLike_ is an Object, then 1. If _temporalTimeZoneLike_ has an [[InitializedTemporalZonedDateTime]] internal slot, then 1. Return _temporalTimeZoneLike_.[[TimeZone]]. 1. If _temporalTimeZoneLike_ is not a String, throw a *TypeError* exception. 1. Let _parseResult_ be ? ParseTemporalTimeZoneString(_temporalTimeZoneLike_). 1. Let _offsetMinutes_ be _parseResult_.[[OffsetMinutes]]. 1. If _offsetMinutes_ is not ~empty~, return FormatOffsetTimeZoneIdentifier(_offsetMinutes_). 1. Let _name_ be _parseResult_.[[Name]]. 1. Let _timeZoneIdentifierRecord_ be GetAvailableNamedTimeZoneIdentifier(_name_). 1. If _timeZoneIdentifierRecord_ is ~empty~, throw a *RangeError* exception. 1. Return _timeZoneIdentifierRecord_.[[Identifier]].

GetOffsetNanosecondsFor ( _timeZone_: an available time zone identifier, _epochNs_: a BigInt, ): an integer in the interval from -86400 × 109 (exclusive) to 86400 × 109 (exclusive)

description
It determines the UTC offset of an exact time _epochNs_, in nanoseconds.
1. Let _parseResult_ be ! ParseTimeZoneIdentifier(_timeZone_). 1. If _parseResult_.[[OffsetMinutes]] is not ~empty~, return _parseResult_.[[OffsetMinutes]] × (60 × 109). 1. Return GetNamedTimeZoneOffsetNanoseconds(_parseResult_.[[Name]], _epochNs_).

GetISODateTimeFor ( _timeZone_: an available time zone identifier, _epochNs_: a BigInt, ): an ISO Date-Time Record

description
1. Let _offsetNanoseconds_ be GetOffsetNanosecondsFor(_timeZone_, _epochNs_). 1. Let _result_ be GetISOPartsFromEpoch(ℝ(_epochNs_)). 1. Return BalanceISODateTime(_result_.[[ISODate]].[[Year]], _result_.[[ISODate]].[[Month]], _result_.[[ISODate]].[[Day]], _result_.[[Time]].[[Hour]], _result_.[[Time]].[[Minute]], _result_.[[Time]].[[Second]], _result_.[[Time]].[[Millisecond]], _result_.[[Time]].[[Microsecond]], _result_.[[Time]].[[Nanosecond]] + _offsetNanoseconds_).

GetEpochNanosecondsFor ( _timeZone_: an available time zone identifier, _isoDateTime_: an ISO Date-Time Record, _disambiguation_: ~compatible~, ~earlier~, ~later~, or ~reject~, ): either a normal completion containing a BigInt or a throw completion

description
1. Let _possibleEpochNs_ be ? GetPossibleEpochNanoseconds(_timeZone_, _isoDateTime_). 1. Return ? DisambiguatePossibleEpochNanoseconds(_possibleEpochNs_, _timeZone_, _isoDateTime_, _disambiguation_).

DisambiguatePossibleEpochNanoseconds ( _possibleEpochNs_: a List of BigInts, _timeZone_: an available time zone identifier, _isoDateTime_: an ISO Date-Time Record, _disambiguation_: ~compatible~, ~earlier~, ~later~, or ~reject~, ): either a normal completion containing a BigInt or a throw completion

description
It chooses from a List of possible exact times the one indicated by the _disambiguation_ parameter.
1. Let _n_ be _possibleEpochNs_'s length. 1. If _n_ = 1, then 1. Return _possibleEpochNs_[0]. 1. If _n_ ≠ 0, then 1. If _disambiguation_ is ~earlier~ or ~compatible~, then 1. Return _possibleEpochNs_[0]. 1. If _disambiguation_ is ~later~, then 1. Return _possibleEpochNs_[_n_ - 1]. 1. Assert: _disambiguation_ is ~reject~. 1. Throw a *RangeError* exception. 1. Assert: _n_ = 0. 1. If _disambiguation_ is ~reject~, then 1. Throw a *RangeError* exception. 1. Let _before_ be the latest possible ISO Date-Time Record for which CompareISODateTime(_before_, _isoDateTime_) = -1 and ! GetPossibleEpochNanoseconds(_timeZone_, _before_) is not empty. 1. Let _after_ be the earliest possible ISO Date-Time Record for which CompareISODateTime(_after_, _isoDateTime_) = 1 and ! GetPossibleEpochNanoseconds(_timeZone_, _after_) is not empty. 1. Let _beforePossible_ be ! GetPossibleEpochNanoseconds(_timeZone_, _before_). 1. Assert: _beforePossible_'s length is 1. 1. Let _afterPossible_ be ! GetPossibleEpochNanoseconds(_timeZone_, _after_). 1. Assert: _afterPossible_'s length is 1. 1. Let _offsetBefore_ be GetOffsetNanosecondsFor(_timeZone_, _beforePossible_[0]). 1. Let _offsetAfter_ be GetOffsetNanosecondsFor(_timeZone_, _afterPossible_[0]). 1. Let _nanoseconds_ be _offsetAfter_ - _offsetBefore_. 1. Assert: abs(_nanoseconds_) ≤ nsPerDay. 1. If _disambiguation_ is ~earlier~, then 1. Let _timeDuration_ be TimeDurationFromComponents(0, 0, 0, 0, 0, -_nanoseconds_). 1. Let _earlierTime_ be AddTime(_isoDateTime_.[[Time]], _timeDuration_). 1. Let _earlierDate_ be BalanceISODate(_isoDateTime_.[[ISODate]].[[Year]], _isoDateTime_.[[ISODate]].[[Month]], _isoDateTime_.[[ISODate]].[[Day]] + _earlierTime_.[[Days]]). 1. Let _earlierDateTime_ be CombineISODateAndTimeRecord(_earlierDate_, _earlierTime_). 1. Set _possibleEpochNs_ to ? GetPossibleEpochNanoseconds(_timeZone_, _earlierDateTime_). 1. Assert: _possibleEpochNs_ is not empty. 1. Return _possibleEpochNs_[0]. 1. Assert: _disambiguation_ is ~compatible~ or ~later~. 1. Let _timeDuration_ be TimeDurationFromComponents(0, 0, 0, 0, 0, _nanoseconds_). 1. Let _laterTime_ be AddTime(_isoDateTime_.[[Time]], _timeDuration_). 1. Let _laterDate_ be BalanceISODate(_isoDateTime_.[[ISODate]].[[Year]], _isoDateTime_.[[ISODate]].[[Month]], _isoDateTime_.[[ISODate]].[[Day]] + _laterTime_.[[Days]]). 1. Let _laterDateTime_ be CombineISODateAndTimeRecord(_laterDate_, _laterTime_). 1. Set _possibleEpochNs_ to ? GetPossibleEpochNanoseconds(_timeZone_, _laterDateTime_). 1. Set _n_ to _possibleEpochNs_'s length. 1. Assert: _n_ ≠ 0. 1. Return _possibleEpochNs_[_n_ - 1].

GetPossibleEpochNanoseconds ( _timeZone_: an available time zone identifier, _isoDateTime_: an ISO Date-Time Record, ): either a normal completion containing a List of BigInts or a throw completion

description
It determines the possible exact times that may correspond to _isoDateTime_.
1. Let _parseResult_ be ! ParseTimeZoneIdentifier(_timeZone_). 1. If _parseResult_.[[OffsetMinutes]] is not ~empty~, then 1. Let _balanced_ be BalanceISODateTime(_isoDateTime_.[[ISODate]].[[Year]], _isoDateTime_.[[ISODate]].[[Month]], _isoDateTime_.[[ISODate]].[[Day]], _isoDateTime_.[[Time]].[[Hour]], _isoDateTime_.[[Time]].[[Minute]] - _parseResult_.[[OffsetMinutes]], _isoDateTime_.[[Time]].[[Second]], _isoDateTime_.[[Time]].[[Millisecond]], _isoDateTime_.[[Time]].[[Microsecond]], _isoDateTime_.[[Time]].[[Nanosecond]]). 1. Perform ? CheckISODaysRange(_balanced_.[[ISODate]]). 1. Let _epochNanoseconds_ be GetUTCEpochNanoseconds(_balanced_). 1. Let _possibleEpochNanoseconds_ be « _epochNanoseconds_ ». 1. Else, 1. Perform ? CheckISODaysRange(_isoDateTime_.[[ISODate]]). 1. Let _possibleEpochNanoseconds_ be GetNamedTimeZoneEpochNanoseconds(_parseResult_.[[Name]], _isoDateTime_). 1. For each value _epochNanoseconds_ in _possibleEpochNanoseconds_, do 1. If IsValidEpochNanoseconds(_epochNanoseconds_) is *false*, throw a *RangeError* exception. 1. Return _possibleEpochNanoseconds_.

GetStartOfDay ( _timeZone_: an available time zone identifier, _isoDate_: an ISO Date Record, ): either a normal completion containing a BigInt or a throw completion

description
It determines the exact time that corresponds to the first valid wall-clock time in the calendar date _isoDate_ in _timeZone_.
1. Let _isoDateTime_ be CombineISODateAndTimeRecord(_isoDate_, MidnightTimeRecord()). 1. Let _possibleEpochNs_ be ? GetPossibleEpochNanoseconds(_timeZone_, _isoDateTime_). 1. If _possibleEpochNs_ is not empty, return _possibleEpochNs_[0]. 1. Assert: IsOffsetTimeZoneIdentifier(_timeZone_) is *false*. 1. [declared="isoDateTimeAfter"] Let _possibleEpochNsAfter_ be GetNamedTimeZoneEpochNanoseconds(_timeZone_, _isoDateTimeAfter_), where _isoDateTimeAfter_ is the ISO Date-Time Record for which DifferenceISODateTime(_isoDateTime_, _isoDateTimeAfter_, *"iso8601"*, ~hour~).[[Time]] is the smallest possible value > 0 for which _possibleEpochNsAfter_ is not empty (i.e., _isoDateTimeAfter_ represents the first local time after the transition). 1. Assert: _possibleEpochNsAfter_'s length = 1. 1. Return _possibleEpochNsAfter_[0].

TimeZoneEquals ( _one_: an available time zone identifier, _two_: an available time zone identifier, ): a Boolean

description
It returns *true* if its arguments represent time zones using the same identifier.
1. If _one_ is _two_, return *true*. 1. Let _offsetMinutesOne_ be ! ParseTimeZoneIdentifier(_one_).[[OffsetMinutes]]. 1. Let _offsetMinutesTwo_ be ! ParseTimeZoneIdentifier(_two_).[[OffsetMinutes]]. 1. If _offsetMinutesOne_ is ~empty~ and _offsetMinutesTwo_ is ~empty~, then 1. Let _recordOne_ be GetAvailableNamedTimeZoneIdentifier(_one_). 1. Let _recordTwo_ be GetAvailableNamedTimeZoneIdentifier(_two_). 1. If _recordOne_ is not ~empty~ and _recordTwo_ is not ~empty~ and _recordOne_.[[PrimaryIdentifier]] is _recordTwo_.[[PrimaryIdentifier]], return *true*. 1. Else, 1. If _offsetMinutesOne_ is not ~empty~ and _offsetMinutesTwo_ is not ~empty~ and _offsetMinutesOne_ = _offsetMinutesTwo_, return *true*. 1. Return *false*.

ParseTimeZoneIdentifier ( _identifier_: a String, ): either a normal completion containing a Record with fields [[Name]] (a named time zone identifier or ~empty~) and [[OffsetMinutes]] (an integer or ~empty~), or a throw completion

description
If _identifier_ is a named time zone identifier, [[Name]] will be _identifier_ and [[OffsetMinutes]] will be ~empty~. If _identifier_ is an offset time zone identifier, [[Name]] will be ~empty~ and [[OffsetMinutes]] will be a signed integer. Otherwise, a *RangeError* will be thrown.
1. Let _parseResult_ be ParseText(StringToCodePoints(_identifier_), |TimeZoneIdentifier|). 1. If _parseResult_ is a List of errors, throw a *RangeError* exception. 1. If _parseResult_ contains a |TimeZoneIANAName| Parse Node, then 1. Let _name_ be the source text matched by the |TimeZoneIANAName| Parse Node contained within _parseResult_. 1. NOTE: _name_ is syntactically valid, but does not necessarily conform to IANA Time Zone Database naming guidelines or correspond with an available named time zone identifier. 1. Return the Record { [[Name]]: CodePointsToString(_name_), [[OffsetMinutes]]: ~empty~ }. 1. Else, 1. Assert: _parseResult_ contains a |UTCOffset[~SubMinutePrecision]| Parse Node. 1. Let _offset_ be the source text matched by the |UTCOffset[~SubMinutePrecision]| Parse Node contained within _parseResult_. 1. Let _offsetNanoseconds_ be ! ParseDateTimeUTCOffset(CodePointsToString(_offset_)). 1. Let _offsetMinutes_ be _offsetNanoseconds_ / (60 × 109). 1. Assert: _offsetMinutes_ is an integer. 1. Return the Record { [[Name]]: ~empty~, [[OffsetMinutes]]: _offsetMinutes_ }.