Abstract Operations
ISODateToEpochDays (
_year_: an integer,
_month_: an integer,
_date_: an integer,
): an integer
1. Let _resolvedYear_ be _year_ + floor(_month_ / 12).
1. Let _resolvedMonth_ be _month_ modulo 12.
1. Find a time _t_ such that EpochTimeToEpochYear(_t_) = _resolvedYear_, EpochTimeToMonthInYear(_t_) = _resolvedMonth_, and EpochTimeToDate(_t_) = 1.
1. Return EpochTimeToDayNumber(_t_) + _date_ - 1.
This operation corresponds to ECMA-262 operation MakeDay(_year_, _month_, _date_). It calculates the result in mathematical values instead of Number values. These two operations would be unified when https://github.com/tc39/ecma262/issues/1087 is fixed.
EpochDaysToEpochMs (
_day_: an integer,
_time_: an integer,
): an integer
1. Return _day_ × ℝ(msPerDay) + _time_.
This operation corresponds to ECMA-262 operation MakeDate(_date_, _time_). It calculates the result in mathematical values instead of Number values. These two operations would be unified when https://github.com/tc39/ecma262/issues/1087 is fixed.
Date Equations
A given time _t_ belongs to day number
EpochTimeToDayNumber(_t_) = floor(_t_ / ℝ(msPerDay))
Number of days in year are given by:
MathematicalDaysInYear(_y_)
= 365 if ((_y_) modulo 4) ≠ 0
= 366 if ((_y_) modulo 4) = 0 and ((_y_) modulo 100) ≠ 0
= 365 if ((_y_) modulo 100) = 0 and ((_y_) modulo 400) ≠ 0
= 366 if ((_y_) modulo 400) = 0
The day number of the first day of year _y_ is given by:
EpochDayNumberForYear(_y_) = 365 × (_y_ - 1970) + floor((_y_ - 1969) / 4) - floor((_y_ - 1901) / 100) + floor((_y_ - 1601) / 400)
The time of the start of a year is:
EpochTimeForYear(_y_) = ℝ(msPerDay) × EpochDayNumberForYear(_y_)
Epoch year from time _t_ is given by:
EpochTimeToEpochYear(_t_) = the largest integral Number _y_ (closest to +∞) such that EpochTimeForYear(_y_) ≤ _t_
The following function returns 1 for a time within leap year otherwise it returns 0:
MathematicalInLeapYear(_t_)
= 0 if MathematicalDaysInYear(EpochTimeToEpochYear(_t_)) = 365
= 1 if MathematicalDaysInYear(EpochTimeToEpochYear(_t_)) = 366
The month number for a time _t_ is given by:
EpochTimeToMonthInYear(_t_)
= 0 if 0 ≤ EpochTimeToDayInYear(_t_) < 31
= 1 if 31 ≤ EpochTimeToDayInYear(_t_) < 59 + MathematicalInLeapYear(_t_)
= 2 if 59 + MathematicalInLeapYear(_t_) ≤ EpochTimeToDayInYear(_t_) < 90 + MathematicalInLeapYear(_t_)
= 3 if 90 + MathematicalInLeapYear(_t_) ≤ EpochTimeToDayInYear(_t_) < 120 + MathematicalInLeapYear(_t_)
= 4 if 120 + MathematicalInLeapYear(_t_) ≤ EpochTimeToDayInYear(_t_) < 151 + MathematicalInLeapYear(_t_)
= 5 if 151 + MathematicalInLeapYear(_t_) ≤ EpochTimeToDayInYear(_t_) < 181 + MathematicalInLeapYear(_t_)
= 6 if 181 + MathematicalInLeapYear(_t_) ≤ EpochTimeToDayInYear(_t_) < 212 + MathematicalInLeapYear(_t_)
= 7 if 212 + MathematicalInLeapYear(_t_) ≤ EpochTimeToDayInYear(_t_) < 243 + MathematicalInLeapYear(_t_)
= 8 if 243 + MathematicalInLeapYear(_t_) ≤ EpochTimeToDayInYear(_t_) < 273 + MathematicalInLeapYear(_t_)
= 9 if 273 + MathematicalInLeapYear(_t_) ≤ EpochTimeToDayInYear(_t_) < 304 + MathematicalInLeapYear(_t_)
= 10 if 304 + MathematicalInLeapYear(_t_) ≤ EpochTimeToDayInYear(_t_) < 334 + MathematicalInLeapYear(_t_)
= 11 if 334 + MathematicalInLeapYear(_t_) ≤ EpochTimeToDayInYear(_t_) < 365 + MathematicalInLeapYear(_t_)
where
EpochTimeToDayInYear(_t_) = EpochTimeToDayNumber(_t_) - EpochDayNumberForYear(EpochTimeToEpochYear(_t_))
A month value of 0 specifies January; 1 specifies February; 2 specifies March; 3 specifies April; 4 specifies May; 5 specifies June; 6 specifies July; 7 specifies August; 8 specifies September; 9 specifies October; 10 specifies November; and 11 specifies December. Note that EpochTimeToMonthInYear(0) = 0, corresponding to Thursday, 1 January 1970.
The date number for a time _t_ is given by:
EpochTimeToDate(_t_)
= EpochTimeToDayInYear(_t_) + 1 if EpochTimeToMonthInYear(_t_) = 0
= EpochTimeToDayInYear(_t_) - 30 if EpochTimeToMonthInYear(_t_) = 1
= EpochTimeToDayInYear(_t_) - 58 - MathematicalInLeapYear(_t_) if EpochTimeToMonthInYear(_t_) = 2
= EpochTimeToDayInYear(_t_) - 89 - MathematicalInLeapYear(_t_) if EpochTimeToMonthInYear(_t_) = 3
= EpochTimeToDayInYear(_t_) - 119 - MathematicalInLeapYear(_t_) if EpochTimeToMonthInYear(_t_) = 4
= EpochTimeToDayInYear(_t_) - 150 - MathematicalInLeapYear(_t_) if EpochTimeToMonthInYear(_t_) = 5
= EpochTimeToDayInYear(_t_) - 180 - MathematicalInLeapYear(_t_) if EpochTimeToMonthInYear(_t_) = 6
= EpochTimeToDayInYear(_t_) - 211 - MathematicalInLeapYear(_t_) if EpochTimeToMonthInYear(_t_) = 7
= EpochTimeToDayInYear(_t_) - 242 - MathematicalInLeapYear(_t_) if EpochTimeToMonthInYear(_t_) = 8
= EpochTimeToDayInYear(_t_) - 272 - MathematicalInLeapYear(_t_) if EpochTimeToMonthInYear(_t_) = 9
= EpochTimeToDayInYear(_t_) - 303 - MathematicalInLeapYear(_t_) if EpochTimeToMonthInYear(_t_) = 10
= EpochTimeToDayInYear(_t_) - 333 - MathematicalInLeapYear(_t_) if EpochTimeToMonthInYear(_t_) = 11
The weekday for a particular time _t_ is defined as:
EpochTimeToWeekDay(_t_) =(EpochTimeToDayNumber(_t_) + 4) modulo 7
A weekday value of 0 specifies Sunday; 1 specifies Monday; 2 specifies Tuesday; 3 specifies Wednesday; 4 specifies Thursday; 5 specifies Friday; and 6 specifies Saturday. Note that EpochTimeToWeekDay(0) = 4, corresponding to Thursday, 1 January 1970.
These equations correspond to ECMA-262 equations defined in Days in Year, Month from Time, Date from Time, Week Day respectively. These calculate the result in mathematical values instead of Number values. These equations would be unified when https://github.com/tc39/ecma262/issues/1087 is fixed.
Note that the operation EpochTimeToMonthInYear(_t_) uses 0-based months unlike rest of Temporal since it's intended to be unified with MonthFromTime(_t_) when the above mentioned issue is fixed.
CheckISODaysRange (
_isoDate_: an ISO Date Record,
): either a normal completion containing ~unused~ or a throw completion
1. If abs(ISODateToEpochDays(_isoDate_.[[Year]], _isoDate_.[[Month]] - 1, _isoDate_.[[Day]])) > 108, then
1. Throw a *RangeError* exception.
1. Return ~unused~.
This operation is solely present to ensure that GetUTCEpochNanoseconds is not called with numbers that are too large.
It is distinct from ISODateWithinLimits, which uses GetUTCEpochNanoseconds.
This operation can be removed with no observable effect when https://github.com/tc39/ecma262/issues/1087 is fixed.
Units
Time is reckoned using multiple units.
These units are listed in .
A Temporal unit is a value listed in the "Value" column of .
A calendar unit is a Temporal unit for which IsCalendarUnit returns *true*.
A date unit is a Temporal unit for which the corresponding "Category" value in is ~date~, and a time unit is a Temporal unit for which the corresponding "Category" value is ~time~.
Temporal units by descending magnitude
| Value |
Singular property name |
Plural property name |
Category |
Length in nanoseconds |
Maximum duration rounding increment |
| ~year~ |
*"year"* |
*"years"* |
~date~ |
calendar-dependent |
~unset~ |
| ~month~ |
*"month"* |
*"months"* |
~date~ |
calendar-dependent |
~unset~ |
| ~week~ |
*"week"* |
*"weeks"* |
~date~ |
calendar-dependent |
~unset~ |
| ~day~ |
*"day"* |
*"days"* |
~date~ |
nsPerDay |
~unset~ |
| ~hour~ |
*"hour"* |
*"hours"* |
~time~ |
3.6 × 1012 |
24 |
| ~minute~ |
*"minute"* |
*"minutes"* |
~time~ |
6 × 1010 |
60 |
| ~second~ |
*"second"* |
*"seconds"* |
~time~ |
109 |
60 |
| ~millisecond~ |
*"millisecond"* |
*"milliseconds"* |
~time~ |
106 |
1000 |
| ~microsecond~ |
*"microsecond"* |
*"microseconds"* |
~time~ |
103 |
1000 |
| ~nanosecond~ |
*"nanosecond"* |
*"nanoseconds"* |
~time~ |
1 |
1000 |
The length of a day is given as nsPerDay in the table.
Note that changes in the UTC offset of a time zone may result in longer or shorter days, so care should be taken when using this value in the context of `Temporal.ZonedDateTime`.
GetTemporalOverflowOption (
_options_: an Object,
): either a normal completion containing either ~constrain~ or ~reject~, or a throw completion
1. Let _stringValue_ be ? GetOption(_options_, *"overflow"*, ~string~, « *"constrain"*, *"reject"* », *"constrain"*).
1. If _stringValue_ is *"constrain"*, return ~constrain~.
1. Return ~reject~.
GetTemporalDisambiguationOption (
_options_: an Object,
): either a normal completion containing either ~compatible~, ~earlier~, ~later~, or ~reject~, or a throw completion
1. Let _stringValue_ be ? GetOption(_options_, *"disambiguation"*, ~string~, « *"compatible"*, *"earlier"*, *"later"*, *"reject"* », *"compatible"*).
1. If _stringValue_ is *"compatible"*, return ~compatible~.
1. If _stringValue_ is *"earlier"*, return ~earlier~.
1. If _stringValue_ is *"later"*, return ~later~.
1. Return ~reject~.
NegateRoundingMode (
_roundingMode_: a rounding mode,
): a rounding mode
1. If _roundingMode_ is ~ceil~, return ~floor~.
1. If _roundingMode_ is ~floor~, return ~ceil~.
1. If _roundingMode_ is ~half-ceil~, return ~half-floor~.
1. If _roundingMode_ is ~half-floor~, return ~half-ceil~.
1. Return _roundingMode_.
GetTemporalOffsetOption (
_options_: an Object,
_fallback_: ~prefer~, ~use~, ~ignore~, or ~reject~,
): either a normal completion containing either ~prefer~, ~use~, ~ignore~, or ~reject~, or a throw completion
1. If _fallback_ is ~prefer~, let _stringFallback_ be *"prefer"*.
1. Else if _fallback_ is ~use~, let _stringFallback_ be *"use"*.
1. Else if _fallback_ is ~ignore~, let _stringFallback_ be *"ignore"*.
1. Else, let _stringFallback_ be *"reject"*.
1. Let _stringValue_ be ? GetOption(_options_, *"offset"*, ~string~, « *"prefer"*, *"use"*, *"ignore"*, *"reject"* », _stringFallback_).
1. If _stringValue_ is *"prefer"*, return ~prefer~.
1. If _stringValue_ is *"use"*, return ~use~.
1. If _stringValue_ is *"ignore"*, return ~ignore~.
1. Return ~reject~.
GetTemporalShowCalendarNameOption (
_options_: an Object,
): either a normal completion containing either ~auto~, ~always~, ~never~, or ~critical~, or a throw completion
This property is used in `toString` methods in Temporal to control whether a calendar annotation should be output.
1. Let _stringValue_ be ? GetOption(_options_, *"calendarName"*, ~string~, « *"auto"*, *"always"*, *"never"*, *"critical"* », *"auto"*).
1. If _stringValue_ is *"always"*, return ~always~.
1. If _stringValue_ is *"never"*, return ~never~.
1. If _stringValue_ is *"critical"*, return ~critical~.
1. Return ~auto~.
GetTemporalShowTimeZoneNameOption (
_options_: an Object,
): either a normal completion containing either ~auto~, ~never~, or ~critical~, or a throw completion
This property is used in `Temporal.ZonedDateTime.prototype.toString()`.
It is different from the `timeZone` property passed to `Temporal.ZonedDateTime.from()` and from the `timeZone` property in the options passed to `Temporal.Instant.prototype.toString()`.
1. Let _stringValue_ be ? GetOption(_options_, *"timeZoneName"*, ~string~, « *"auto"*, *"never"*, *"critical"* », *"auto"*).
1. If _stringValue_ is *"never"*, return ~never~.
1. If _stringValue_ is *"critical"*, return ~critical~.
1. Return ~auto~.
GetTemporalShowOffsetOption (
_options_: an Object,
): either a normal completion containing either ~auto~ or ~never~, or a throw completion
This property is used in `Temporal.ZonedDateTime.prototype.toString()`.
It is different from the `offset` property passed to `Temporal.ZonedDateTime.from()`.
1. Let _stringValue_ be ? GetOption(_options_, *"offset"*, ~string~, « *"auto"*, *"never"* », *"auto"*).
1. If _stringValue_ is *"never"*, return ~never~.
1. Return ~auto~.
GetDirectionOption (
_options_: an Object,
): either a normal completion containing either ~next~ or ~previous~, or a throw completion
1. Let _stringValue_ be ? GetOption(_options_, *"direction"*, ~string~, « *"next"*, *"previous"* », ~required~).
1. If _stringValue_ is *"next"*, return ~next~.
1. Return ~previous~.
ValidateTemporalRoundingIncrement (
_increment_: a positive integer,
_dividend_: a positive integer,
_inclusive_: a Boolean,
): either a normal completion containing ~unused~ or a throw completion
1. If _inclusive_ is *true*, then
1. Let _maximum_ be _dividend_.
1. Else,
1. Assert: _dividend_ > 1.
1. Let _maximum_ be _dividend_ - 1.
1. If _increment_ > _maximum_, throw a *RangeError* exception.
1. If _dividend_ modulo _increment_ ≠ 0, then
1. Throw a *RangeError* exception.
1. Return ~unused~.
GetTemporalFractionalSecondDigitsOption (
_options_: an Object,
): either a normal completion containing either ~auto~ or an integer in the inclusive interval from 0 to 9, or a throw completion
1. Let _digitsValue_ be ? Get(_options_, *"fractionalSecondDigits"*).
1. If _digitsValue_ is *undefined*, return ~auto~.
1. If _digitsValue_ is not a Number, then
1. If ? ToString(_digitsValue_) is not *"auto"*, throw a *RangeError* exception.
1. Return ~auto~.
1. If _digitsValue_ is *NaN*, *+∞*𝔽, or *-∞*𝔽, throw a *RangeError* exception.
1. Let _digitCount_ be floor(ℝ(_digitsValue_)).
1. If _digitCount_ < 0 or _digitCount_ > 9, throw a *RangeError* exception.
1. Return _digitCount_.
ToSecondsStringPrecisionRecord (
_smallestUnit_: ~minute~, ~second~, ~millisecond~, ~microsecond~, ~nanosecond~, or ~unset~,
_fractionalDigitCount_: ~auto~ or an integer in the inclusive interval from 0 to 9,
): a Record with fields [[Precision]] (~minute~, ~auto~, or an integer in the inclusive interval from 0 to 9), [[Unit]] (~minute~, ~second~, ~millisecond~, ~microsecond~, or ~nanosecond~), and [[Increment]] (1, 10, or 100)
1. If _smallestUnit_ is ~minute~, then
1. Return the Record {
[[Precision]]: ~minute~,
[[Unit]]: ~minute~,
[[Increment]]: 1
}.
1. If _smallestUnit_ is ~second~, then
1. Return the Record {
[[Precision]]: 0,
[[Unit]]: ~second~,
[[Increment]]: 1
}.
1. If _smallestUnit_ is ~millisecond~, then
1. Return the Record {
[[Precision]]: 3,
[[Unit]]: ~millisecond~,
[[Increment]]: 1
}.
1. If _smallestUnit_ is ~microsecond~, then
1. Return the Record {
[[Precision]]: 6,
[[Unit]]: ~microsecond~,
[[Increment]]: 1
}.
1. If _smallestUnit_ is ~nanosecond~, then
1. Return the Record {
[[Precision]]: 9,
[[Unit]]: ~nanosecond~,
[[Increment]]: 1
}.
1. Assert: _smallestUnit_ is ~unset~.
1. If _fractionalDigitCount_ is ~auto~, then
1. Return the Record {
[[Precision]]: ~auto~,
[[Unit]]: ~nanosecond~,
[[Increment]]: 1
}.
1. If _fractionalDigitCount_ = 0, then
1. Return the Record {
[[Precision]]: 0,
[[Unit]]: ~second~,
[[Increment]]: 1
}.
1. If _fractionalDigitCount_ is in the inclusive interval from 1 to 3, then
1. Return the Record {
[[Precision]]: _fractionalDigitCount_,
[[Unit]]: ~millisecond~,
[[Increment]]: 103 - _fractionalDigitCount_
}.
1. If _fractionalDigitCount_ is in the inclusive interval from 4 to 6, then
1. Return the Record {
[[Precision]]: _fractionalDigitCount_,
[[Unit]]: ~microsecond~,
[[Increment]]: 106 - _fractionalDigitCount_
}.
1. Assert: _fractionalDigitCount_ is in the inclusive interval from 7 to 9.
1. Return the Record {
[[Precision]]: _fractionalDigitCount_,
[[Unit]]: ~nanosecond~,
[[Increment]]: 109 - _fractionalDigitCount_
}.
GetTemporalUnitValuedOption (
_options_: an Object,
_key_: a property key,
_unitGroup_: ~date~, ~time~, or ~datetime~,
_default_: ~required~, ~unset~, ~auto~, or a Temporal unit,
optional _extraValues_: a List of either Temporal units or ~auto~,
): either a normal completion containing either a Temporal unit, ~unset~, or ~auto~, or a throw completion
Both singular and plural unit names are accepted, but only the singular form is used internally.
1. Let _allowedValues_ be a new empty List.
1. For each row of , except the header row, in table order, do
1. Let _unit_ be the value in the "Value" column of the row.
1. If the "Category" column of the row is ~date~ and _unitGroup_ is ~date~ or ~datetime~, append _unit_ to _allowedValues_.
1. Else if the "Category" column of the row is ~time~ and _unitGroup_ is ~time~ or ~datetime~, append _unit_ to _allowedValues_.
1. If _extraValues_ is present, then
1. Set _allowedValues_ to the list-concatenation of _allowedValues_ and _extraValues_.
1. If _default_ is ~unset~, then
1. Let _defaultValue_ be *undefined*.
1. Else if _default_ is ~required~, then
1. Let _defaultValue_ be ~required~.
1. Else if _default_ is ~auto~, then
1. Append _default_ to _allowedValues_.
1. Let _defaultValue_ be *"auto"*.
1. Else,
1. Assert: _allowedValues_ contains _default_.
1. Let _defaultValue_ be the value in the "Singular property name" column of corresponding to the row with _default_ in the "Value" column.
1. Let _allowedStrings_ be a new empty List.
1. For each element _value_ of _allowedValues_, do
1. If _value_ is ~auto~, then
1. Append *"auto"* to _allowedStrings_.
1. Else,
1. Let _singularName_ be the value in the "Singular property name" column of corresponding to the row with _value_ in the "Value" column.
1. Append _singularName_ to _allowedStrings_.
1. Let _pluralName_ be the value in the "Plural property name" column of the corresponding row.
1. Append _pluralName_ to _allowedStrings_.
1. NOTE: For each singular Temporal unit name that is contained within _allowedStrings_, the corresponding plural name is also contained within it.
1. Let _value_ be ? GetOption(_options_, _key_, ~string~, _allowedStrings_, _defaultValue_).
1. If _value_ is *undefined*, return ~unset~.
1. If _value_ is *"auto"*, return ~auto~.
1. Return the value in the "Value" column of corresponding to the row with _value_ in its "Singular property name" or "Plural property name" column.
GetTemporalRelativeToOption (
_options_: an Object,
): either a normal completion containing a Record with fields [[PlainRelativeTo]] (a Temporal.PlainDate or *undefined*) and [[ZonedRelativeTo]] (a Temporal.ZonedDateTime or *undefined*), or a throw completion
1. Let _value_ be ? Get(_options_, *"relativeTo"*).
1. If _value_ is *undefined*, return the Record { [[PlainRelativeTo]]: *undefined*, [[ZonedRelativeTo]]: *undefined* }.
1. Let _offsetBehaviour_ be ~option~.
1. Let _matchBehaviour_ be ~match-exactly~.
1. If _value_ is an Object, then
1. If _value_ has an [[InitializedTemporalZonedDateTime]] internal slot, then
1. Return the Record { [[PlainRelativeTo]]: *undefined*, [[ZonedRelativeTo]]: _value_ }.
1. If _value_ has an [[InitializedTemporalDate]] internal slot, then
1. Return the Record { [[PlainRelativeTo]]: _value_, [[ZonedRelativeTo]]: *undefined* }.
1. If _value_ has an [[InitializedTemporalDateTime]] internal slot, then
1. Let _plainDate_ be ! CreateTemporalDate(_value_.[[ISODateTime]].[[ISODate]], _value_.[[Calendar]]).
1. Return the Record { [[PlainRelativeTo]]: _plainDate_, [[ZonedRelativeTo]]: *undefined* }.
1. Let _calendar_ be ? GetTemporalCalendarIdentifierWithISODefault(_value_).
1. Let _fields_ be ? PrepareCalendarFields(_calendar_, _value_, « ~year~, ~month~, ~month-code~, ~day~ », « ~hour~, ~minute~, ~second~, ~millisecond~, ~microsecond~, ~nanosecond~, ~offset~, ~time-zone~ », «»).
1. Let _result_ be ? InterpretTemporalDateTimeFields(_calendar_, _fields_, ~constrain~).
1. Let _timeZone_ be _fields_.[[TimeZone]].
1. Let _offsetString_ be _fields_.[[OffsetString]].
1. If _offsetString_ is ~unset~, then
1. Set _offsetBehaviour_ to ~wall~.
1. Let _isoDate_ be _result_.[[ISODate]].
1. Let _time_ be _result_.[[Time]].
1. Else,
1. If _value_ is not a String, throw a *TypeError* exception.
1. Let _result_ be ? ParseISODateTime(_value_, « |TemporalDateTimeString[+Zoned]|, |TemporalDateTimeString[~Zoned]| »).
1. Let _offsetString_ be _result_.[[TimeZone]].[[OffsetString]].
1. Let _annotation_ be _result_.[[TimeZone]].[[TimeZoneAnnotation]].
1. If _annotation_ is ~empty~, then
1. Let _timeZone_ be ~unset~.
1. Else,
1. Let _timeZone_ be ? ToTemporalTimeZoneIdentifier(_annotation_).
1. If _result_.[[TimeZone]].[[Z]] is *true*, then
1. Set _offsetBehaviour_ to ~exact~.
1. Else if _offsetString_ is ~empty~, then
1. Set _offsetBehaviour_ to ~wall~.
1. Set _matchBehaviour_ to ~match-minutes~.
1. Let _calendar_ be _result_.[[Calendar]].
1. If _calendar_ is ~empty~, set _calendar_ to *"iso8601"*.
1. Set _calendar_ to ? CanonicalizeCalendar(_calendar_).
1. Let _isoDate_ be CreateISODateRecord(_result_.[[Year]], _result_.[[Month]], _result_.[[Day]]).
1. Let _time_ be _result_.[[Time]].
1. If _timeZone_ is ~unset~, then
1. Let _plainDate_ be ? CreateTemporalDate(_isoDate_, _calendar_).
1. Return the Record { [[PlainRelativeTo]]: _plainDate_, [[ZonedRelativeTo]]: *undefined* }.
1. If _offsetBehaviour_ is ~option~, then
1. Let _offsetNs_ be ! ParseDateTimeUTCOffset(_offsetString_).
1. Else,
1. Let _offsetNs_ be 0.
1. Let _epochNanoseconds_ be ? InterpretISODateTimeOffset(_isoDate_, _time_, _offsetBehaviour_, _offsetNs_, _timeZone_, ~compatible~, ~reject~, _matchBehaviour_).
1. Let _zonedRelativeTo_ be ! CreateTemporalZonedDateTime(_epochNanoseconds_, _timeZone_, _calendar_).
1. Return the Record { [[PlainRelativeTo]]: *undefined*, [[ZonedRelativeTo]]: _zonedRelativeTo_ }.
LargerOfTwoTemporalUnits (
_u1_: a Temporal unit,
_u2_: a Temporal unit,
): a Temporal unit
1. For each row of , except the header row, in table order, do
1. Let _unit_ be the value in the "Value" column of the row.
1. If _u1_ is _unit_, return _unit_.
1. If _u2_ is _unit_, return _unit_.
IsCalendarUnit (
_unit_: a Temporal unit,
): a Boolean
1. If _unit_ is ~year~, return *true*.
1. If _unit_ is ~month~, return *true*.
1. If _unit_ is ~week~, return *true*.
1. Return *false*.
TemporalUnitCategory (
_unit_: a Temporal unit,
): ~date~ or ~time~
1. Return the value from the "Category" column of the row of in which _unit_ is in the "Value" column.
MaximumTemporalDurationRoundingIncrement (
_unit_: a Temporal unit,
): 24, 60, 1000, or ~unset~
1. Return the value from the "Maximum duration rounding increment" column of the row of in which _unit_ is in the "Value" column.
IsPartialTemporalObject (
_value_: an ECMAScript language value,
): either a normal completion containing a Boolean or a throw completion
1. If _value_ is not an Object, return *false*.
1. If _value_ has an [[InitializedTemporalDate]], [[InitializedTemporalDateTime]], [[InitializedTemporalMonthDay]], [[InitializedTemporalTime]], [[InitializedTemporalYearMonth]], or [[InitializedTemporalZonedDateTime]] internal slot, return *false*.
1. Let _calendarProperty_ be ? Get(_value_, *"calendar"*).
1. If _calendarProperty_ is not *undefined*, return *false*.
1. Let _timeZoneProperty_ be ? Get(_value_, *"timeZone"*).
1. If _timeZoneProperty_ is not *undefined*, return *false*.
1. Return *true*.
FormatFractionalSeconds (
_subSecondNanoseconds_: an integer in the inclusive interval from 0 to 999999999,
_precision_: either an integer in the inclusive interval from 0 to 9 or ~auto~,
): a String
1. If _precision_ is ~auto~, then
1. If _subSecondNanoseconds_ = 0, return the empty String.
1. Let _fractionString_ be ToZeroPaddedDecimalString(_subSecondNanoseconds_, 9).
1. Set _fractionString_ to the longest prefix of _fractionString_ ending with a code unit other than 0x0030 (DIGIT ZERO).
1. Else,
1. If _precision_ = 0, return the empty String.
1. Let _fractionString_ be ToZeroPaddedDecimalString(_subSecondNanoseconds_, 9).
1. Set _fractionString_ to the substring of _fractionString_ from 0 to _precision_.
1. Return the string-concatenation of the code unit 0x002E (FULL STOP) and _fractionString_.
FormatTimeString (
_hour_: an integer in the inclusive interval from 0 to 23,
_minute_: an integer in the inclusive interval from 0 to 59,
_second_: an integer in the inclusive interval from 0 to 59,
_subSecondNanoseconds_: an integer in the inclusive interval from 0 to 999999999,
_precision_: an integer in the inclusive interval from 0 to 9, ~minute~, or ~auto~,
optional _style_: ~separated~ or ~unseparated~,
): a String
1. If _style_ is present and _style_ is ~unseparated~, let _separator_ be the empty String; otherwise, let _separator_ be *":"*.
1. Let _hh_ be ToZeroPaddedDecimalString(_hour_, 2).
1. Let _mm_ be ToZeroPaddedDecimalString(_minute_, 2).
1. If _precision_ is ~minute~, return the string-concatenation of _hh_, _separator_, and _mm_.
1. Let _ss_ be ToZeroPaddedDecimalString(_second_, 2).
1. Let _subSecondsPart_ be FormatFractionalSeconds(_subSecondNanoseconds_, _precision_).
1. Return the string-concatenation of _hh_, _separator_, _mm_, _separator_, _ss_, and _subSecondsPart_.
GetUnsignedRoundingMode (
_roundingMode_: a rounding mode,
_sign_: ~negative~ or ~positive~,
): an unsigned rounding mode
1. Return the specification type in the "Unsigned Rounding Mode" column of for the row where the value in the "Rounding Mode" column is _roundingMode_ and the value in the "Sign" column is _sign_.
Conversion from rounding mode to unsigned rounding mode
| Rounding Mode |
Sign |
Unsigned Rounding Mode |
| ~ceil~ |
~positive~ |
~infinity~ |
| ~negative~ |
~zero~ |
| ~floor~ |
~positive~ |
~zero~ |
| ~negative~ |
~infinity~ |
| ~expand~ |
~positive~ |
~infinity~ |
| ~negative~ |
~infinity~ |
| ~trunc~ |
~positive~ |
~zero~ |
| ~negative~ |
~zero~ |
| ~half-ceil~ |
~positive~ |
~half-infinity~ |
| ~negative~ |
~half-zero~ |
| ~half-floor~ |
~positive~ |
~half-zero~ |
| ~negative~ |
~half-infinity~ |
| ~half-expand~ |
~positive~ |
~half-infinity~ |
| ~negative~ |
~half-infinity~ |
| ~half-trunc~ |
~positive~ |
~half-zero~ |
| ~negative~ |
~half-zero~ |
| ~half-even~ |
~positive~ |
~half-even~ |
| ~negative~ |
~half-even~ |
ApplyUnsignedRoundingMode (
_x_: a mathematical value,
_r1_: a mathematical value,
_r2_: a mathematical value,
_unsignedRoundingMode_: a specification type from the "Unsigned Rounding Mode" column of , or *undefined*,
): a mathematical value
1. If _x_ = _r1_, return _r1_.
1. Assert: _r1_ < _x_ < _r2_.
1. Assert: _unsignedRoundingMode_ is not *undefined*.
1. If _unsignedRoundingMode_ is ~zero~, return _r1_.
1. If _unsignedRoundingMode_ is ~infinity~, return _r2_.
1. Let _d1_ be _x_ – _r1_.
1. Let _d2_ be _r2_ – _x_.
1. If _d1_ < _d2_, return _r1_.
1. If _d2_ < _d1_, return _r2_.
1. Assert: _d1_ is equal to _d2_.
1. If _unsignedRoundingMode_ is ~half-zero~, return _r1_.
1. If _unsignedRoundingMode_ is ~half-infinity~, return _r2_.
1. Assert: _unsignedRoundingMode_ is ~half-even~.
1. Let _cardinality_ be (_r1_ / (_r2_ – _r1_)) modulo 2.
1. If _cardinality_ = 0, return _r1_.
1. Return _r2_.
RoundNumberToIncrement (
_x_: a mathematical value,
_increment_: a positive integer,
_roundingMode_: a rounding mode,
): an integer
1. Let _quotient_ be _x_ / _increment_.
1. If _quotient_ < 0, then
1. Let _isNegative_ be ~negative~.
1. Set _quotient_ to -_quotient_.
1. Else,
1. Let _isNegative_ be ~positive~.
1. Let _unsignedRoundingMode_ be GetUnsignedRoundingMode(_roundingMode_, _isNegative_).
1. Let _r1_ be the largest integer such that _r1_ ≤ _quotient_.
1. Let _r2_ be the smallest integer such that _r2_ > _quotient_.
1. Let _rounded_ be ApplyUnsignedRoundingMode(_quotient_, _r1_, _r2_, _unsignedRoundingMode_).
1. If _isNegative_ is ~negative~, set _rounded_ to -_rounded_.
1. Return _rounded_ × _increment_.
RoundNumberToIncrementAsIfPositive (
_x_: a mathematical value,
_increment_: a positive integer,
_roundingMode_: a rounding mode,
): an integer
1. Let _quotient_ be _x_ / _increment_.
1. Let _unsignedRoundingMode_ be GetUnsignedRoundingMode(_roundingMode_, ~positive~).
1. Let _r1_ be the largest integer such that _r1_ ≤ _quotient_.
1. Let _r2_ be the smallest integer such that _r2_ > _quotient_.
1. Let _rounded_ be ApplyUnsignedRoundingMode(_quotient_, _r1_, _r2_, _unsignedRoundingMode_).
1. Return _rounded_ × _increment_.
RFC 9557 / ISO 8601 grammar
Several operations in this section are intended to parse RFC 9557 strings representing a date, a time, a duration, or a combined date and time.
For the purposes of these operations, a valid RFC 9557 string is defined as a string that can be generated by one of the goal elements of the following grammar.
This grammar is adapted from the ABNF grammar of ISO 8601 that is given in appendix A of RFC 3339, augmented with the grammar of annotations in section 4.1 of RFC 9557
RFC 9557 and ISO 8601 are similar, but ISO 8601 defines a number of optional deviations that are allowed "by agreement between the communicating parties".
The following is a list of deviations supported by this grammar:
- Only the calendar date format is supported, not the weekdate or ordinal date format.
- Two-digit years are disallowed.
- Expanded Years of 6 digits are allowed.
- Fractional parts may have 1 through 9 decimal places.
- In time representations, only seconds are allowed to have a fractional part.
- In duration representations, only hours, minutes, and seconds are allowed to have a fractional part.
- A space may be used to separate the date and time in a combined date / time representation, but not in a duration (e.g., *"1970-01-01 00:00Z"* is valid but *"P1D 1H"* is not).
- Alphabetic designators may be in lower or upper case (e.g., *"1970-01-01t00:00Z"* and *"1970-01-01T00:00z"* and *"pT1m"* are valid).
- Period or comma may be used as the decimal separator (e.g., *"PT1,00H"* is a valid representation of a 1-hour duration).
- UTC offsets of *"-00:00"* and *"-0000"* and *"-00"* are allowed, and all mean the same thing as *"+00:00"*.
- UTC offsets may have seconds and up to 9 sub-second fractional digits (e.g., *"1970-01-01T00:00:00+00:00:00.123456789"* is valid).
- The constituent date, time, and UTC offset parts of a combined representation may each independently use basic format (with no separator symbols) or extended format (with mandatory `-` or `:` separators), as long as each such part is itself in either basic format or extended format (e.g., *"1970-01-01T012345"* and *"19700101T01:23:45"* are valid but *"1970-0101T012345"* and *"1970-01-01T0123:45"* are not).
-
When parsing a date representation for a Temporal.PlainMonthDay, the year may be omitted.
The year may optionally be replaced by `--` as in RFC 3339 Appendix A.
- When parsing a date representation without a day for a Temporal.PlainYearMonth, the expression is allowed to be in basic format (with no separator symbols).
- A duration specifier of *"W"* (weeks) can be combined with any of the other specifiers (e.g., *"P1M1W1D"* is valid).
- Anything else described by ISO 8601 as requiring mutual agreement between communicating parties, is disallowed.
In addition to the above deviations, any number of conforming RFC 9557 suffixes in square brackets are allowed.
However, the only recognized suffixes are time zone and BCP 47 calendar.
Others are ignored, unless they are prefixed with `!`, in which case they are rejected.
Note that the suffix keys, although they look similar, are not the same as keys in RFC 6067.
In particular, keys are lowercase-only.
Alpha ::: one of
`A` `B` `C` `D` `E` `F` `G` `H` `I` `J` `K` `L` `M` `N` `O` `P` `Q` `R`
`S` `T` `U` `V` `W` `X` `Y` `Z` `a` `b` `c` `d` `e` `f` `g` `h` `i` `j`
`k` `l` `m` `n` `o` `p` `q` `r` `s` `t` `u` `v` `w` `x` `y` `z`
LowercaseAlpha ::: one of
`a` `b` `c` `d` `e` `f` `g` `h` `i` `j` `k` `l` `m` `n` `o` `p` `q` `r`
`s` `t` `u` `v` `w` `x` `y` `z`
DateSeparator[Extended] :::
[+Extended] `-`
[~Extended] [empty]
DaysDesignator ::: one of
`D` `d`
HoursDesignator ::: one of
`H` `h`
MinutesDesignator ::: one of
`M` `m`
MonthsDesignator ::: one of
`M` `m`
DurationDesignator ::: one of
`P` `p`
SecondsDesignator ::: one of
`S` `s`
DateTimeSeparator :::
<SP>
`T`
`t`
TimeDesignator ::: one of
`T` `t`
WeeksDesignator ::: one of
`W` `w`
YearsDesignator ::: one of
`Y` `y`
UTCDesignator ::: one of
`Z` `z`
AnnotationCriticalFlag :::
`!`
DateYear :::
DecimalDigit DecimalDigit DecimalDigit DecimalDigit
ASCIISign DecimalDigit DecimalDigit DecimalDigit DecimalDigit DecimalDigit DecimalDigit
DateMonth :::
`0` NonZeroDigit
`10`
`11`
`12`
DateDay :::
`0` NonZeroDigit
`1` DecimalDigit
`2` DecimalDigit
`30`
`31`
DateSpecYearMonth :::
DateYear DateSeparator[+Extended] DateMonth
DateYear DateSeparator[~Extended] DateMonth
DateSpecMonthDay :::
`--`? DateMonth DateSeparator[+Extended] DateDay
`--`? DateMonth DateSeparator[~Extended] DateDay
DateSpec[Extended] :::
DateYear DateSeparator[?Extended] DateMonth DateSeparator[?Extended] DateDay
Date :::
DateSpec[+Extended]
DateSpec[~Extended]
TimeSecond :::
MinuteSecond
`60`
NormalizedUTCOffset :::
ASCIISign Hour TimeSeparator[+Extended] MinuteSecond
UTCOffset[SubMinutePrecision] :::
ASCIISign Hour
ASCIISign Hour TimeSeparator[+Extended] MinuteSecond
ASCIISign Hour TimeSeparator[~Extended] MinuteSecond
[+SubMinutePrecision] ASCIISign Hour TimeSeparator[+Extended] MinuteSecond TimeSeparator[+Extended] MinuteSecond TemporalDecimalFraction?
[+SubMinutePrecision] ASCIISign Hour TimeSeparator[~Extended] MinuteSecond TimeSeparator[~Extended] MinuteSecond TemporalDecimalFraction?
DateTimeUTCOffset[Z] :::
[+Z] UTCDesignator
UTCOffset[+SubMinutePrecision]
TZLeadingChar :::
Alpha
`.`
`_`
TZChar :::
TZLeadingChar
DecimalDigit
`-`
`+`
TimeZoneIANANameComponent :::
TZLeadingChar
TimeZoneIANANameComponent TZChar
TimeZoneIANAName :::
TimeZoneIANANameComponent
TimeZoneIANAName `/` TimeZoneIANANameComponent
TimeZoneIdentifier :::
UTCOffset[~SubMinutePrecision]
TimeZoneIANAName
TimeZoneAnnotation :::
`[` AnnotationCriticalFlag? TimeZoneIdentifier `]`
AKeyLeadingChar :::
LowercaseAlpha
`_`
AKeyChar :::
AKeyLeadingChar
DecimalDigit
`-`
AnnotationKey :::
AKeyLeadingChar
AnnotationKey AKeyChar
AnnotationValueComponent :::
Alpha AnnotationValueComponent?
DecimalDigit AnnotationValueComponent?
AnnotationValue :::
AnnotationValueComponent
AnnotationValueComponent `-` AnnotationValue
Annotation :::
`[` AnnotationCriticalFlag? AnnotationKey `=` AnnotationValue `]`
Annotations :::
Annotation Annotations?
TimeSpec[Extended] :::
Hour
Hour TimeSeparator[?Extended] MinuteSecond
Hour TimeSeparator[?Extended] MinuteSecond TimeSeparator[?Extended] TimeSecond TemporalDecimalFraction?
Time :::
TimeSpec[+Extended]
TimeSpec[~Extended]
DateTime[Z, TimeRequired] :::
[~TimeRequired] Date
Date DateTimeSeparator Time DateTimeUTCOffset[?Z]?
AnnotatedTime :::
TimeDesignator Time DateTimeUTCOffset[~Z]? TimeZoneAnnotation? Annotations?
Time DateTimeUTCOffset[~Z]? TimeZoneAnnotation? Annotations?
AnnotatedDateTime[Zoned, TimeRequired] :::
[~Zoned] DateTime[~Z, ?TimeRequired] TimeZoneAnnotation? Annotations?
[+Zoned] DateTime[+Z, ?TimeRequired] TimeZoneAnnotation Annotations?
AnnotatedYearMonth :::
DateSpecYearMonth TimeZoneAnnotation? Annotations?
AnnotatedMonthDay :::
DateSpecMonthDay TimeZoneAnnotation? Annotations?
DurationSecondsPart :::
DecimalDigits[~Sep] TemporalDecimalFraction? SecondsDesignator
DurationMinutesPart :::
DecimalDigits[~Sep] TemporalDecimalFraction MinutesDesignator
DecimalDigits[~Sep] MinutesDesignator DurationSecondsPart?
DurationHoursPart :::
DecimalDigits[~Sep] TemporalDecimalFraction HoursDesignator
DecimalDigits[~Sep] HoursDesignator DurationMinutesPart
DecimalDigits[~Sep] HoursDesignator DurationSecondsPart?
DurationTime :::
TimeDesignator DurationHoursPart
TimeDesignator DurationMinutesPart
TimeDesignator DurationSecondsPart
DurationDaysPart :::
DecimalDigits[~Sep] DaysDesignator
DurationWeeksPart :::
DecimalDigits[~Sep] WeeksDesignator DurationDaysPart?
DurationMonthsPart :::
DecimalDigits[~Sep] MonthsDesignator DurationWeeksPart
DecimalDigits[~Sep] MonthsDesignator DurationDaysPart?
DurationYearsPart :::
DecimalDigits[~Sep] YearsDesignator DurationMonthsPart
DecimalDigits[~Sep] YearsDesignator DurationWeeksPart
DecimalDigits[~Sep] YearsDesignator DurationDaysPart?
DurationDate :::
DurationYearsPart DurationTime?
DurationMonthsPart DurationTime?
DurationWeeksPart DurationTime?
DurationDaysPart DurationTime?
Duration :::
ASCIISign? DurationDesignator DurationDate
ASCIISign? DurationDesignator DurationTime
TemporalInstantString :::
Date DateTimeSeparator Time DateTimeUTCOffset[+Z] TimeZoneAnnotation? Annotations?
TemporalDateTimeString[Zoned] :::
AnnotatedDateTime[?Zoned, ~TimeRequired]
TemporalDurationString :::
Duration
TemporalMonthDayString :::
AnnotatedMonthDay
AnnotatedDateTime[~Zoned, ~TimeRequired]
TemporalTimeString :::
AnnotatedTime
AnnotatedDateTime[~Zoned, +TimeRequired]
TemporalYearMonthString :::
AnnotatedYearMonth
AnnotatedDateTime[~Zoned, ~TimeRequired]
Static Semantics: IsValidMonthDay ( ): a Boolean
DateSpec[Extended] :::
DateYear DateSeparator[?Extended] DateMonth DateSeparator[?Extended] DateDay
DateSpecMonthDay :::
`--`? DateMonth DateSeparator[+Extended] DateDay
`--`? DateMonth DateSeparator[~Extended] DateDay
1. If |DateDay| is *"31"* and |DateMonth| is *"02"*, *"04"*, *"06"*, *"09"*, *"11"*, return *false*.
1. If |DateMonth| is *"02"* and |DateDay| is *"30"*, return *false*.
1. Return *true*.
Static Semantics: IsValidDate ( ): a Boolean
DateSpec[Extended] :::
DateYear DateSeparator[?Extended] DateMonth DateSeparator[?Extended] DateDay
1. If IsValidMonthDay of |DateSpec| is *false*, return *false*.
1. Let _year_ be ℝ(StringToNumber(CodePointsToString(|DateYear|))).
1. If |DateMonth| is *"02"* and |DateDay| is *"29"* and MathematicalInLeapYear(EpochTimeForYear(_year_)) = 0, return *false*.
1. Return *true*.
Static Semantics: Early Errors
AnnotatedTime :::
Time DateTimeUTCOffset[~Z]? TimeZoneAnnotation? Annotations?
-
It is a Syntax Error if ParseText(|Time| |DateTimeUTCOffset[~Z]|, |DateSpecMonthDay|) is a Parse Node.
-
It is a Syntax Error if ParseText(|Time| |DateTimeUTCOffset[~Z]|, |DateSpecYearMonth|) is a Parse Node.
DateSpec[Extended] :::
DateYear DateSeparator[?Extended] DateMonth DateSeparator[?Extended] DateDay
-
It is a Syntax Error if IsValidDate of |DateSpec| is *false*.
DateSpecMonthDay :::
`--`? DateMonth DateSeparator[+Extended] DateDay
`--`? DateMonth DateSeparator[~Extended] DateDay
-
It is a Syntax Error if IsValidMonthDay of |DateSpecMonthDay| is *false*.
DateYear :::
ASCIISign DecimalDigit DecimalDigit DecimalDigit DecimalDigit DecimalDigit DecimalDigit
-
It is a Syntax Error if |DateYear| is *"-000000"*.
ISO String Time Zone Parse Records
A ISO String Time Zone Parse Record is a Record value used to represent the result of parsing the representation of the time zone in an ISO 8601 / RFC 9557 string.
ISO String Time Zone Parse Records have the fields listed in .
| Field Name |
Value |
Meaning |
| [[Z]] |
a Boolean |
Whether the string contained the `Z` UTC designator.
|
| [[OffsetString]] |
a String or ~empty~ |
The UTC offset from the string, or ~empty~ if none was present.
|
| [[TimeZoneAnnotation]] |
a String or ~empty~ |
The time zone annotation from the string, or ~empty~ if none was present.
|
ISO Date-Time Parse Records
An ISO Date-Time Parse Record is a Record value used to represent the result of parsing an ISO 8601 / RFC 9557 string.
For any ISO Date-Time Parse Record _r_, IsValidISODate(_r_.[[Year]], _r_.[[Month]], _r_.[[Day]]) must return *true*, or, if _r_.[[Year]] is ~empty~, IsValidISODate(1972, _r_.[[Month]], _r_.[[Day]]) must return *true*.
It is not necessary for the represented date and time to be within the range given by ISODateTimeWithinLimits.
ISO Date-Time Parse Records have the fields listed in .
| Field Name |
Value |
Meaning |
| [[Year]] |
an integer or ~empty~ |
The year in the ISO 8601 calendar, or ~empty~ if the string's format was that of |TemporalMonthDayString| and the year was omitted.
|
| [[Month]] |
an integer between 1 and 12, inclusive |
The number of the month in the ISO 8601 calendar.
|
| [[Day]] |
an integer between 1 and 31, inclusive |
The number of the day of the month in the ISO 8601 calendar.
|
| [[Time]] |
either a Time Record with [[Days]] value 0, or ~start-of-day~ |
The time of day, or ~start-of-day~ if the time was omitted from the string.
|
| [[TimeZone]] |
an ISO String Time Zone Parse Record |
A representation of how the time zone was expressed in the string.
|
| [[Calendar]] |
a String or ~empty~ |
The calendar type from the string, or ~empty~ if none was present.
|
ParseISODateTime (
_isoString_: a String,
_allowedFormats_: a List of nonterminals,
): either a normal completion containing an ISO Date-Time Parse Record or a throw completion
1. Let _parseResult_ be ~empty~.
1. Let _calendar_ be ~empty~.
1. Let _yearAbsent_ be *false*.
1. For each nonterminal _goal_ of _allowedFormats_, do
1. If _parseResult_ is not a Parse Node, then
1. Set _parseResult_ to ParseText(StringToCodePoints(_isoString_), _goal_).
1. If _parseResult_ is a Parse Node, then
1. Let _calendarWasCritical_ be *false*.
1. For each |Annotation| Parse Node _annotation_ contained within _parseResult_, do
1. Let _key_ be the source text matched by the |AnnotationKey| Parse Node contained within _annotation_.
1. Let _value_ be the source text matched by the |AnnotationValue| Parse Node contained within _annotation_.
1. If CodePointsToString(_key_) is *"u-ca"*, then
1. If _calendar_ is ~empty~, then
1. Set _calendar_ to CodePointsToString(_value_).
1. If _annotation_ contains an |AnnotationCriticalFlag| Parse Node, set _calendarWasCritical_ to *true*.
1. Else,
1. If _annotation_ contains an |AnnotationCriticalFlag| Parse Node, or _calendarWasCritical_ is *true*, throw a *RangeError* exception.
1. Else,
1. If _annotation_ contains an |AnnotationCriticalFlag| Parse Node, throw a *RangeError* exception.
1. If _goal_ is |TemporalMonthDayString| or |TemporalYearMonthString|, _calendar_ is not ~empty~, and the ASCII-lowercase of _calendar_ is not *"iso8601"*, throw a *RangeError* exception.
1. If _goal_ is |TemporalMonthDayString| and _parseResult_ does not contain a |DateYear| Parse Node, set _yearAbsent_ to *true*.
1. If _parseResult_ is not a Parse Node, throw a *RangeError* exception.
1. NOTE: Applications of StringToNumber below do not lose precision, since each of the parsed values is guaranteed to be a sufficiently short string of decimal digits.
1. Let each of _year_, _month_, _day_, _hour_, _minute_, _second_, and _fSeconds_ be the source text matched by the respective |DateYear|, |DateMonth|, |DateDay|, the first |Hour|, the first |MinuteSecond|, |TimeSecond|, and the first |TemporalDecimalFraction| Parse Node contained within _parseResult_, or an empty sequence of code points if not present.
1. Let _yearMV_ be ℝ(StringToNumber(CodePointsToString(_year_))).
1. If _month_ is empty, then
1. Let _monthMV_ be 1.
1. Else,
1. Let _monthMV_ be ℝ(StringToNumber(CodePointsToString(_month_))).
1. If _day_ is empty, then
1. Let _dayMV_ be 1.
1. Else,
1. Let _dayMV_ be ℝ(StringToNumber(CodePointsToString(_day_))).
1. If _hour_ is empty, then
1. Let _hourMV_ be 0.
1. Else,
1. Let _hourMV_ be ℝ(StringToNumber(CodePointsToString(_hour_))).
1. If _minute_ is empty, then
1. Let _minuteMV_ be 0.
1. Else,
1. Let _minuteMV_ be ℝ(StringToNumber(CodePointsToString(_minute_))).
1. If _second_ is empty, then
1. Let _secondMV_ be 0.
1. Else,
1. Let _secondMV_ be ℝ(StringToNumber(CodePointsToString(_second_))).
1. If _secondMV_ = 60, then
1. Set _secondMV_ to 59.
1. If _fSeconds_ is not empty, then
1. Let _fSecondsDigits_ be the substring of CodePointsToString(_fSeconds_) from 1.
1. Let _fSecondsDigitsExtended_ be the string-concatenation of _fSecondsDigits_ and *"000000000"*.
1. Let _millisecond_ be the substring of _fSecondsDigitsExtended_ from 0 to 3.
1. Let _microsecond_ be the substring of _fSecondsDigitsExtended_ from 3 to 6.
1. Let _nanosecond_ be the substring of _fSecondsDigitsExtended_ from 6 to 9.
1. Let _millisecondMV_ be ℝ(StringToNumber(_millisecond_)).
1. Let _microsecondMV_ be ℝ(StringToNumber(_microsecond_)).
1. Let _nanosecondMV_ be ℝ(StringToNumber(_nanosecond_)).
1. Else,
1. Let _millisecondMV_ be 0.
1. Let _microsecondMV_ be 0.
1. Let _nanosecondMV_ be 0.
1. Assert: IsValidISODate(_yearMV_, _monthMV_, _dayMV_) is *true*.
1. If _hour_ is empty, then
1. Let _time_ be ~start-of-day~.
1. Else,
1. Let _time_ be CreateTimeRecord(_hourMV_, _minuteMV_, _secondMV_, _millisecondMV_, _microsecondMV_, _nanosecondMV_).
1. Let _timeZoneResult_ be ISO String Time Zone Parse Record { [[Z]]: *false*, [[OffsetString]]: ~empty~, [[TimeZoneAnnotation]]: ~empty~ }.
1. If _parseResult_ contains a |TimeZoneIdentifier| Parse Node, then
1. Let _identifier_ be the source text matched by the |TimeZoneIdentifier| Parse Node contained within _parseResult_.
1. Set _timeZoneResult_.[[TimeZoneAnnotation]] to CodePointsToString(_identifier_).
1. If _parseResult_ contains a |UTCDesignator| Parse Node, then
1. Set _timeZoneResult_.[[Z]] to *true*.
1. Else if _parseResult_ contains a |UTCOffset[+SubMinutePrecision]| Parse Node, then
1. Let _offset_ be the source text matched by the |UTCOffset[+SubMinutePrecision]| Parse Node contained within _parseResult_.
1. Set _timeZoneResult_.[[OffsetString]] to CodePointsToString(_offset_).
1. If _yearAbsent_ is *true*, let _yearReturn_ be ~empty~; else let _yearReturn_ be _yearMV_.
1. Return ISO Date-Time Parse Record {
[[Year]]: _yearReturn_,
[[Month]]: _monthMV_,
[[Day]]: _dayMV_,
[[Time]]: _time_,
[[TimeZone]]: _timeZoneResult_,
[[Calendar]]: _calendar_
}.
ParseTemporalCalendarString (
_string_: a String,
): either a normal completion containing a String or a throw completion
1. Let _parseResult_ be Completion(ParseISODateTime(_string_, « |TemporalDateTimeString[+Zoned]|, |TemporalDateTimeString[~Zoned]|, |TemporalInstantString|, |TemporalTimeString|, |TemporalMonthDayString|, |TemporalYearMonthString| »)).
1. If _parseResult_ is a normal completion, then
1. Let _calendar_ be _parseResult_.[[Value]].[[Calendar]].
1. If _calendar_ is ~empty~, return *"iso8601"*.
1. Return _calendar_.
1. Set _parseResult_ to ParseText(StringToCodePoints(_string_), |AnnotationValue|).
1. If _parseResult_ is a List of errors, throw a *RangeError* exception.
1. Return _string_.
ParseTemporalDurationString (
_isoString_: a String,
): either a normal completion containing a Temporal.Duration or a throw completion
The value of ToIntegerWithTruncation(the empty String) is 0.
Use of mathematical values rather than approximations is important to avoid off-by-one errors with input like "PT46H66M71.50040904S".
1. Let _duration_ be ParseText(StringToCodePoints(_isoString_), |TemporalDurationString|).
1. If _duration_ is a List of errors, throw a *RangeError* exception.
1. Let _sign_ be the source text matched by the |ASCIISign| Parse Node contained within _duration_, or an empty sequence of code points if not present.
1. If _duration_ contains a |DurationYearsPart| Parse Node, then
1. Let _yearsNode_ be that |DurationYearsPart| Parse Node contained within _duration_.
1. Let _years_ be the source text matched by the |DecimalDigits| Parse Node contained within _yearsNode_.
1. Else,
1. Let _years_ be an empty sequence of code points.
1. If _duration_ contains a |DurationMonthsPart| Parse Node, then
1. Let _monthsNode_ be the |DurationMonthsPart| Parse Node contained within _duration_.
1. Let _months_ be the source text matched by the |DecimalDigits| Parse Node contained within _monthsNode_.
1. Else,
1. Let _months_ be an empty sequence of code points.
1. If _duration_ contains a |DurationWeeksPart| Parse Node, then
1. Let _weeksNode_ be the |DurationWeeksPart| Parse Node contained within _duration_.
1. Let _weeks_ be the source text matched by the |DecimalDigits| Parse Node contained within _weeksNode_.
1. Else,
1. Let _weeks_ be an empty sequence of code points.
1. If _duration_ contains a |DurationDaysPart| Parse Node, then
1. Let _daysNode_ be the |DurationDaysPart| Parse Node contained within _duration_.
1. Let _days_ be the source text matched by the |DecimalDigits| Parse Node contained within _daysNode_.
1. Else,
1. Let _days_ be an empty sequence of code points.
1. If _duration_ contains a |DurationHoursPart| Parse Node, then
1. Let _hoursNode_ be the |DurationHoursPart| Parse Node contained within _duration_.
1. Let _hours_ be the source text matched by the |DecimalDigits| Parse Node contained within _hoursNode_.
1. Let _fHours_ be the source text matched by the |TemporalDecimalFraction| Parse Node contained within _hoursNode_, or an empty sequence of code points if not present.
1. Else,
1. Let _hours_ be an empty sequence of code points.
1. Let _fHours_ be an empty sequence of code points.
1. If _duration_ contains a |DurationMinutesPart| Parse Node, then
1. Let _minutesNode_ be the |DurationMinutesPart| Parse Node contained within _duration_.
1. Let _minutes_ be the source text matched by the |DecimalDigits| Parse Node contained within _minutesNode_.
1. Let _fMinutes_ be the source text matched by the |TemporalDecimalFraction| Parse Node contained within _minutesNode_, or an empty sequence of code points if not present.
1. Else,
1. Let _minutes_ be an empty sequence of code points.
1. Let _fMinutes_ be an empty sequence of code points.
1. If _duration_ contains a |DurationSecondsPart| Parse Node, then
1. Let _secondsNode_ be the |DurationSecondsPart| Parse Node contained within _duration_.
1. Let _seconds_ be the source text matched by the |DecimalDigits| Parse Node contained within _secondsNode_.
1. Let _fSeconds_ be the source text matched by the |TemporalDecimalFraction| Parse Node contained within _secondsNode_, or an empty sequence of code points if not present.
1. Else,
1. Let _seconds_ be an empty sequence of code points.
1. Let _fSeconds_ be an empty sequence of code points.
1. Let _yearsMV_ be ? ToIntegerWithTruncation(CodePointsToString(_years_)).
1. Let _monthsMV_ be ? ToIntegerWithTruncation(CodePointsToString(_months_)).
1. Let _weeksMV_ be ? ToIntegerWithTruncation(CodePointsToString(_weeks_)).
1. Let _daysMV_ be ? ToIntegerWithTruncation(CodePointsToString(_days_)).
1. Let _hoursMV_ be ? ToIntegerWithTruncation(CodePointsToString(_hours_)).
1. If _fHours_ is not empty, then
1. Assert: _minutes_, _fMinutes_, _seconds_, and _fSeconds_ are empty.
1. Let _fHoursDigits_ be the substring of CodePointsToString(_fHours_) from 1.
1. Let _fHoursScale_ be the length of _fHoursDigits_.
1. Let _minutesMV_ be ? ToIntegerWithTruncation(_fHoursDigits_) / 10_fHoursScale_ × 60.
1. Else,
1. Let _minutesMV_ be ? ToIntegerWithTruncation(CodePointsToString(_minutes_)).
1. If _fMinutes_ is not empty, then
1. Assert: _seconds_ and _fSeconds_ are empty.
1. Let _fMinutesDigits_ be the substring of CodePointsToString(_fMinutes_) from 1.
1. Let _fMinutesScale_ be the length of _fMinutesDigits_.
1. Let _secondsMV_ be ? ToIntegerWithTruncation(_fMinutesDigits_) / 10_fMinutesScale_ × 60.
1. Else if _seconds_ is not empty, then
1. Let _secondsMV_ be ? ToIntegerWithTruncation(CodePointsToString(_seconds_)).
1. Else,
1. Let _secondsMV_ be remainder(_minutesMV_, 1) × 60.
1. If _fSeconds_ is not empty, then
1. Let _fSecondsDigits_ be the substring of CodePointsToString(_fSeconds_) from 1.
1. Let _fSecondsScale_ be the length of _fSecondsDigits_.
1. Let _millisecondsMV_ be ? ToIntegerWithTruncation(_fSecondsDigits_) / 10_fSecondsScale_ × 1000.
1. Else,
1. Let _millisecondsMV_ be remainder(_secondsMV_, 1) × 1000.
1. Let _microsecondsMV_ be remainder(_millisecondsMV_, 1) × 1000.
1. Let _nanosecondsMV_ be remainder(_microsecondsMV_, 1) × 1000.
1. If _sign_ contains the code point U+002D (HYPHEN-MINUS), then
1. Let _factor_ be -1.
1. Else,
1. Let _factor_ be 1.
1. Set _yearsMV_ to _yearsMV_ × _factor_.
1. Set _monthsMV_ to _monthsMV_ × _factor_.
1. Set _weeksMV_ to _weeksMV_ × _factor_.
1. Set _daysMV_ to _daysMV_ × _factor_.
1. Set _hoursMV_ to _hoursMV_ × _factor_.
1. Set _minutesMV_ to floor(_minutesMV_) × _factor_.
1. Set _secondsMV_ to floor(_secondsMV_) × _factor_.
1. Set _millisecondsMV_ to floor(_millisecondsMV_) × _factor_.
1. Set _microsecondsMV_ to floor(_microsecondsMV_) × _factor_.
1. Set _nanosecondsMV_ to floor(_nanosecondsMV_) × _factor_.
1. Return ? CreateTemporalDuration(_yearsMV_, _monthsMV_, _weeksMV_, _daysMV_, _hoursMV_, _minutesMV_, _secondsMV_, _millisecondsMV_, _microsecondsMV_, _nanosecondsMV_).
ParseTemporalTimeZoneString (
_timeZoneString_: a String,
): either a normal completion containing a Record containing information about the time zone, or a throw completion
1. Let _parseResult_ be ParseText(StringToCodePoints(_timeZoneString_), |TimeZoneIdentifier|).
1. If _parseResult_ is a Parse Node, then
1. Return ! ParseTimeZoneIdentifier(_timeZoneString_).
1. Let _result_ be ? ParseISODateTime(_timeZoneString_, « |TemporalDateTimeString[+Zoned]|, |TemporalDateTimeString[~Zoned]|, |TemporalInstantString|, |TemporalTimeString|, |TemporalMonthDayString|, |TemporalYearMonthString| »).
1. Let _timeZoneResult_ be _result_.[[TimeZone]].
1. If _timeZoneResult_.[[TimeZoneAnnotation]] is not ~empty~, then
1. Return ! ParseTimeZoneIdentifier(_timeZoneResult_.[[TimeZoneAnnotation]]).
1. If _timeZoneResult_.[[Z]] is *true*, then
1. Return ! ParseTimeZoneIdentifier(*"UTC"*).
1. If _timeZoneResult_.[[OffsetString]] is not ~empty~, then
1. Return ? ParseTimeZoneIdentifier(_timeZoneResult_.[[OffsetString]]).
1. Throw a *RangeError* exception.
ToPositiveIntegerWithTruncation (
_argument_: an ECMAScript language value,
): either a normal completion containing a positive integer or a throw completion
1. Let _integer_ be ? ToIntegerWithTruncation(_argument_).
1. If _integer_ ≤ 0, throw a *RangeError* exception.
1. Return _integer_.
ToIntegerWithTruncation (
_argument_: an ECMAScript language value,
): either a normal completion containing an integer or a throw completion
1. Let _number_ be ? ToNumber(_argument_).
1. If _number_ is *NaN*, *+∞*𝔽 or *-∞*𝔽, throw a *RangeError* exception.
1. Return truncate(ℝ(_number_)).
ToIntegerIfIntegral (
_argument_: an ECMAScript language value,
): either a normal completion containing an integer or a throw completion
1. Let _number_ be ? ToNumber(_argument_).
1. If _number_ is not an integral Number, throw a *RangeError* exception.
1. Return ℝ(_number_).
ToMonthCode (
_argument_: an ECMAScript language value,
): either a normal completion containing a String or a throw completion
1. Let _monthCode_ be ? ToPrimitive(_argument_, ~string~).
1. If _monthCode_ is not a String, throw a *TypeError* exception.
1. If the length of _monthCode_ is not 3 or 4, throw a *RangeError* exception.
1. If the first code unit of _monthCode_ is not 0x004D (LATIN CAPITAL LETTER M), throw a *RangeError* exception.
1. If the second code unit of _monthCode_ is not in the inclusive interval from 0x0030 (DIGIT ZERO) to 0x0039 (DIGIT NINE), throw a *RangeError* exception.
1. If the third code unit of _monthCode_ is not in the inclusive interval from 0x0030 (DIGIT ZERO) to 0x0039 (DIGIT NINE), throw a *RangeError* exception.
1. If the length of _monthCode_ is 4 and the fourth code unit of _monthCode_ is not 0x004C (LATIN CAPITAL LETTER L), throw a *RangeError* exception.
1. Let _monthCodeDigits_ be the substring of monthCode from 1 to 3.
1. Let _monthCodeInteger_ be ℝ(StringToNumber(_monthCodeDigits_)).
1. If _monthCodeInteger_ is 0 and the length of _monthCode_ is not 4, throw a *RangeError* exception.
1. Return _monthCode_.
ToOffsetString (
_argument_: an ECMAScript language value,
): either a normal completion containing a String or a throw completion
1. Let _offset_ be ? ToPrimitive(_argument_, ~string~).
1. If _offset_ is not a String, throw a *TypeError* exception.
1. Perform ? ParseDateTimeUTCOffset(_offset_).
1. Return _offset_.
ISODateToFields (
_calendar_: a calendar type,
_isoDate_: an ISO Date Record,
_type_: ~date~, ~year-month~, or ~month-day~,
): a Calendar Fields Record
1. Let _fields_ be an empty Calendar Fields Record with all fields set to ~unset~.
1. Let _calendarDate_ be CalendarISOToDate(_calendar_, _isoDate_).
1. Set _fields_.[[MonthCode]] to _calendarDate_.[[MonthCode]].
1. If _type_ is ~month-day~ or ~date~, then
1. Set _fields_.[[Day]] to _calendarDate_.[[Day]].
1. If _type_ is ~year-month~ or ~date~, then
1. Set _fields_.[[Year]] to _calendarDate_.[[Year]].
1. Return _fields_.
GetDifferenceSettings (
_operation_: ~since~ or ~until~,
_options_: an Object,
_unitGroup_: ~date~, ~time~, or ~datetime~,
_disallowedUnits_: a List of Temporal units,
_fallbackSmallestUnit_: a Temporal unit,
_smallestLargestDefaultUnit_: a Temporal unit,
): either a normal completion containing a Record with fields [[SmallestUnit]] (a Temporal unit), [[LargestUnit]] (a Temporal unit), [[RoundingMode]] (a rounding mode), and [[RoundingIncrement]] (an integer in the inclusive interval from 1 to 109), or a throw completion
1. NOTE: The following steps read options and perform independent validation in alphabetical order.
1. Let _largestUnit_ be ? GetTemporalUnitValuedOption(_options_, *"largestUnit"*, _unitGroup_, ~auto~).
1. If _disallowedUnits_ contains _largestUnit_, throw a *RangeError* exception.
1. Let _roundingIncrement_ be ? GetRoundingIncrementOption(_options_).
1. Let _roundingMode_ be ? GetRoundingModeOption(_options_, ~trunc~).
1. If _operation_ is ~since~, then
1. Set _roundingMode_ to NegateRoundingMode(_roundingMode_).
1. Let _smallestUnit_ be ? GetTemporalUnitValuedOption(_options_, *"smallestUnit"*, _unitGroup_, _fallbackSmallestUnit_).
1. If _disallowedUnits_ contains _smallestUnit_, throw a *RangeError* exception.
1. Let _defaultLargestUnit_ be LargerOfTwoTemporalUnits(_smallestLargestDefaultUnit_, _smallestUnit_).
1. If _largestUnit_ is ~auto~, set _largestUnit_ to _defaultLargestUnit_.
1. If LargerOfTwoTemporalUnits(_largestUnit_, _smallestUnit_) is not _largestUnit_, throw a *RangeError* exception.
1. Let _maximum_ be MaximumTemporalDurationRoundingIncrement(_smallestUnit_).
1. If _maximum_ is not ~unset~, perform ? ValidateTemporalRoundingIncrement(_roundingIncrement_, _maximum_, *false*).
1. Return the Record {
[[SmallestUnit]]: _smallestUnit_,
[[LargestUnit]]: _largestUnit_,
[[RoundingMode]]: _roundingMode_,
[[RoundingIncrement]]: _roundingIncrement_,
}.