Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Editorial: Add "local-offset time value" for time values with local time zone offset applied #3464

Open
wants to merge 6 commits into
base: main
Choose a base branch
from
55 changes: 31 additions & 24 deletions spec.html
Original file line number Diff line number Diff line change
Expand Up @@ -32580,7 +32580,7 @@ <h1>Date Objects</h1>

<emu-clause id="sec-overview-of-date-objects-and-definitions-of-abstract-operations">
<h1>Overview of Date Objects and Definitions of Abstract Operations</h1>
<p>The following abstract operations operate on time values (defined in <emu-xref href="#sec-time-values-and-time-range"></emu-xref>). Note that, in every case, if any argument to one of these functions is *NaN*, the result will be *NaN*.</p>
<p>The following abstract operations operate on time values and local-offset time values. Note that, in every case, if any argument to one of these functions is *NaN*, the result will be *NaN*.</p>

<emu-clause id="sec-time-values-and-time-range">
<h1>Time Values and Time Range</h1>
Expand All @@ -32589,6 +32589,12 @@ <h1>Time Values and Time Range</h1>
<p>Time values do not account for UTC leap seconds—there are no time values representing instants within positive leap seconds, and there are time values representing instants removed from the UTC timeline by negative leap seconds. However, the definition of time values nonetheless yields piecewise alignment with UTC, with discontinuities only at leap second boundaries and zero difference outside of leap seconds.</p>
<p>A Number can exactly represent all integers from -9,007,199,254,740,992 to 9,007,199,254,740,992 (<emu-xref href="#sec-number.min_safe_integer"></emu-xref> and <emu-xref href="#sec-number.max_safe_integer"></emu-xref>). A time value supports a slightly smaller range of -8,640,000,000,000,000 to 8,640,000,000,000,000 milliseconds. This yields a supported time value range of exactly -100,000,000 days to 100,000,000 days relative to midnight at the beginning of 1 January 1970 UTC.</p>
<p>The exact moment of midnight at the beginning of 1 January 1970 UTC is represented by the time value *+0*<sub>𝔽</sub>.</p>
<p>An ECMAScript <dfn variants="local-offset time values">local-offset time value</dfn> is a time value adjusted by a time zone offset that is an integer number of milliseconds in the interval from -86,400,000 (exclusive) to 86,400,000 (exclusive). A local-offset time value represents a local date and time of day, specifically the UTC date and time of day corresponding an identical time value, ignoring range limits. For example, a local-offset time value of *60000*<sub>𝔽</sub> represents time of day 00:01 on 1 January 1970, and could equivalently come from any of the following:</p>
anba marked this conversation as resolved.
Show resolved Hide resolved
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We talked in editor call about how technically a local-offset time value isn't a time value and that might be confusing to readers. But I don't really mind explaining it like this. @tc39/ecma262-editors Thoughts?

<ul>
<li>local offset +00:00 = *+0*<sub>𝔽</sub> at epoch time 1970-01-01T00:01Z = *60000*<sub>𝔽</sub></li>
<li>local offset +01:00 = *3600000*<sub>𝔽</sub> at epoch time 1969-12-31T23:01Z = 𝔽(-msPerHour + 60000) = *-3540000*<sub>𝔽</sub></li>
<li>local offset -05:00 = 𝔽(-5 × msPerHour) = *-18000000*<sub>𝔽</sub> at epoch time 1970-01-01T05:01Z = 𝔽(5 × msPerHour + 60000) = *18060000*<sub>𝔽</sub></li>
</ul>
<emu-note>
<p>In the proleptic Gregorian calendar, leap years are precisely those which are both divisible by 4 and either divisible by 400 or not divisible by 100.</p>
<p>The 400 year cycle of the proleptic Gregorian calendar contains 97 leap years. This yields an average of 365.2425 days per year, which is 31,556,952,000 milliseconds. Therefore, the maximum range a Number could represent exactly with millisecond precision is approximately -285,426 to 285,426 years relative to 1970. The smaller range supported by a time value as specified in this section is approximately -273,790 to 273,790 years relative to 1970.</p>
Expand All @@ -32610,7 +32616,7 @@ <h1>Time-related Constants</h1>
<emu-clause id="sec-day" type="abstract operation" oldids="eqn-Day,sec-day-number-and-time-within-day">
<h1>
Day (
_t_: a finite time value,
_t_: a finite local-offset time value,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All local-offset time values are finite. We can drop the qualifier at all these use sites. It's already implied by the definition, but we can also be more explicit about it if we want.

): an integral Number
</h1>
<dl class="header">
Expand All @@ -32625,7 +32631,7 @@ <h1>
<emu-clause id="sec-timewithinday" type="abstract operation" oldids="eqn-TimeWithinDay">
<h1>
TimeWithinDay (
_t_: a finite time value,
_t_: a finite local-offset time value,
): an integral Number in the interval from *+0*<sub>𝔽</sub> (inclusive) to msPerDay (exclusive)
</h1>
<dl class="header">
Expand Down Expand Up @@ -32695,7 +32701,7 @@ <h1>
<emu-clause id="sec-yearfromtime" type="abstract operation" oldids="eqn-YearFromTime">
<h1>
YearFromTime (
_t_: a finite time value,
_t_: a finite local-offset time value,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note that YearFromTime can still be called from MakeDay with a Number that is outside of the local-offset time value range, for example in Date.UTC(-271821, 3, 20). Explanation in #1087 (comment). Same for MonthFromTime and DateFromTime. (This is pre-existing, so doesn't need to block the PR.)

): an integral Number
</h1>
<dl class="header">
Expand All @@ -32710,7 +32716,7 @@ <h1>
<emu-clause id="sec-daywithinyear" type="abstract operation" oldids="eqn-DayWithinYear">
<h1>
DayWithinYear (
_t_: a finite time value,
_t_: a finite local-offset time value,
): an integral Number in the inclusive interval from *+0*<sub>𝔽</sub> to *365*<sub>𝔽</sub>
</h1>
<dl class="header">
Expand All @@ -32723,7 +32729,7 @@ <h1>
<emu-clause id="sec-inleapyear" type="abstract operation" oldids="eqn-InLeapYear">
<h1>
InLeapYear (
_t_: a finite time value,
_t_: a finite local-offset time value,
): *+0*<sub>𝔽</sub> or *1*<sub>𝔽</sub>
</h1>
<dl class="header">
Expand All @@ -32738,7 +32744,7 @@ <h1>
<emu-clause id="sec-monthfromtime" type="abstract operation" oldids="eqn-MonthFromTime,sec-month-number">
<h1>
MonthFromTime (
_t_: a finite time value,
_t_: a finite local-offset time value,
): an integral Number in the inclusive interval from *+0*<sub>𝔽</sub> to *11*<sub>𝔽</sub>
</h1>
<dl class="header">
Expand Down Expand Up @@ -32767,7 +32773,7 @@ <h1>
<emu-clause id="sec-datefromtime" type="abstract operation" oldids="sec-date-number">
<h1>
DateFromTime (
_t_: a finite time value,
_t_: a finite local-offset time value,
): an integral Number in the inclusive interval from *1*<sub>𝔽</sub> to *31*<sub>𝔽</sub>
</h1>
<dl class="header">
Expand Down Expand Up @@ -32797,7 +32803,7 @@ <h1>
<emu-clause id="sec-weekday" type="abstract operation" oldids="sec-week-day">
<h1>
WeekDay (
_t_: a finite time value,
_t_: a finite local-offset time value,
): an integral Number in the inclusive interval from *+0*<sub>𝔽</sub> to *6*<sub>𝔽</sub>
</h1>
<dl class="header">
Expand All @@ -32812,7 +32818,7 @@ <h1>
<emu-clause id="sec-hourfromtime" type="abstract operation" oldids="eqn-HourFromTime,sec-hours-minutes-second-and-milliseconds">
<h1>
HourFromTime (
_t_: a finite time value,
_t_: a finite local-offset time value,
): an integral Number in the inclusive interval from *+0*<sub>𝔽</sub> to *23*<sub>𝔽</sub>
</h1>
<dl class="header">
Expand All @@ -32827,7 +32833,7 @@ <h1>
<emu-clause id="sec-minfromtime" type="abstract operation" oldids="eqn-MinFromTime">
<h1>
MinFromTime (
_t_: a finite time value,
_t_: a finite local-offset time value,
): an integral Number in the inclusive interval from *+0*<sub>𝔽</sub> to *59*<sub>𝔽</sub>
</h1>
<dl class="header">
Expand All @@ -32842,7 +32848,7 @@ <h1>
<emu-clause id="sec-secfromtime" type="abstract operation" oldids="eqn-SecFromTime">
<h1>
SecFromTime (
_t_: a finite time value,
_t_: a finite local-offset time value,
): an integral Number in the inclusive interval from *+0*<sub>𝔽</sub> to *59*<sub>𝔽</sub>
</h1>
<dl class="header">
Expand All @@ -32857,7 +32863,7 @@ <h1>
<emu-clause id="sec-msfromtime" type="abstract operation" oldids="eqn-msFromTime">
<h1>
msFromTime (
_t_: a finite time value,
_t_: a finite local-offset time value,
): an integral Number in the inclusive interval from *+0*<sub>𝔽</sub> to *999*<sub>𝔽</sub>
</h1>
<dl class="header">
Expand Down Expand Up @@ -32966,7 +32972,7 @@ <h1>
GetNamedTimeZoneOffsetNanoseconds (
_timeZoneIdentifier_: a String,
_epochNanoseconds_: a BigInt,
): an integer
): an integer in the exclusive interval from -8.64 × 10<sup>13</sup> to 8.64 × 10<sup>13</sup>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would prefer defining this type up in Time Values and Time Range alongside "local-offset time value" as something like "timezone offset".

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can reuse that definition in Temporal.

Copy link
Member

@michaelficarra michaelficarra Oct 31, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We actually don't define the term "exclusive interval". We should add a dfn for it where we define intervals.

</h1>
<dl class="header">
</dl>
Expand Down Expand Up @@ -33069,7 +33075,7 @@ <h1>SystemTimeZoneIdentifier ( ): a String</h1>
<h1>
LocalTime (
_t_: a finite time value,
): an integral Number
): a local-offset time value
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If, for some reason, we don't make all local-offset time values finite, we should add the finite qualifier here.

</h1>
<dl class="header">
<dt>description</dt>
Expand All @@ -33094,7 +33100,7 @@ <h1>
<p>It is required for time zone aware implementations (and recommended for all others) to use the time zone information of the IANA Time Zone Database <a href="https://www.iana.org/time-zones/">https://www.iana.org/time-zones/</a>.</p>
</emu-note>
<emu-note>
<p>Two different input time values <emu-eqn>_t_<sub>UTC</sub></emu-eqn> are converted to the same local time <emu-eqn>t<sub>local</sub></emu-eqn> at a negative time zone transition when there are repeated times (e.g. the daylight saving time ends or the time zone adjustment is decreased.).</p>
<p>Two different input time values <emu-eqn>_t_<sub>UTC</sub></emu-eqn> are converted to the same local-offset time value <emu-eqn>t<sub>local</sub></emu-eqn> at a negative time zone transition when there are repeated times (e.g. the daylight saving time ends or the time zone adjustment is decreased.).</p>
<p><emu-eqn>LocalTime(UTC(_t_<sub>local</sub>))</emu-eqn> is not necessarily always equal to <emu-eqn>_t_<sub>local</sub></emu-eqn>. Correspondingly, <emu-eqn>UTC(LocalTime(_t_<sub>UTC</sub>))</emu-eqn> is not necessarily always equal to <emu-eqn>_t_<sub>UTC</sub></emu-eqn>.</p>
</emu-note>
</emu-clause>
Expand All @@ -33114,6 +33120,7 @@ <h1>
</dl>
<emu-alg>
1. If _t_ is not finite, return *NaN*.
1. If abs(ℝ(_t_)) ≥ 8.64 × 10<sup>15</sup> + ℝ(msPerDay), return *NaN*.
1. Let _systemTimeZoneIdentifier_ be SystemTimeZoneIdentifier().
1. If IsTimeZoneOffsetString(_systemTimeZoneIdentifier_) is *true*, then
1. Let _offsetNs_ be ParseTimeZoneOffsetString(_systemTimeZoneIdentifier_).
Expand All @@ -33131,7 +33138,7 @@ <h1>
1. Return _t_ - 𝔽(_offsetMs_).
</emu-alg>
<p>
Input _t_ is nominally a time value but may be any Number value.
Input _t_ is nominally a local-offset time value but may be any Number value.
The algorithm must not limit _t_ to the time value range, so that inputs corresponding with a boundary of the time value range can be supported regardless of local UTC offset.
For example, the maximum time value is 8.64 × 10<sup>15</sup>, corresponding with *"+275760-09-13T00:00:00Z"*.
In an environment where the local time zone offset is ahead of UTC by 1 hour at that instant, it is represented by the larger input of 8.64 × 10<sup>15</sup> + 3.6 × 10<sup>6</sup>, corresponding with *"+275760-09-13T01:00:00+01:00"*.
Comment on lines +33141 to 33144
Copy link
Member

@michaelficarra michaelficarra Oct 31, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suggest we redefine local-offset time value to account for this additional range, then type this AO properly, taking a local-offset time value.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Alternatively, I don't think it'd be a problem to just expand the range of time value either, right?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

UTC is called with unbounded integral Number values, so we can't restrict the input to time values.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay. I still think we should expand the range supported by local-offset time values.

Also, it's unrelated to this PR, but: t isn't nominally a time value, it's nominally a Number. It's "ostensibly" or "figuratively" a time value. I would update that term as well.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't understand to which range local-offset time values should be extended? Any integral Number, including NaN (and possibly Infinity, too)? Because that's the range which can be produced by MakeDate. (And the output of MakeDate is passed as the input to UTC.)

Copy link
Member

@michaelficarra michaelficarra Oct 31, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I understand we won't be able to narrow the parameter to UTC. Now I'm just talking about changing the definition of local-offset time value.

ISO 8601 timezone offsets permit ±24 hours (less 1 nanosecond). Time values are integers within the range -8,640,000,000,000,000 to 8,640,000,000,000,000 (100 million days on either side of the epoch). I'm suggesting that local time values have an extended range of ±8,640,000,086,400,000 (100,000,001 days on either side of the epoch).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

±8,640,000,086,400,000 suggests it's valid for a time zone offset to be ±24 hours, but just as you said, the maximum valid time zone offset is ±23:59:59.999.999.999 hours. Ignoring micro- and nanoseconds, we get ±23:59:59.999, which is ±86399999 = ±(86400000 - 1) milliseconds, so exactly the range already used within this PR.

Copy link
Member

@michaelficarra michaelficarra Oct 31, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you point to where a local-offset time value is defined in that way in this PR? All I see is that it's restricted to the same range as (non-offset) time values: ±8,640,000,000,000,000.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, I think I understand our misunderstanding.

The first commit has:

An ECMAScript local-offset time value is a time value adjusted by a time zone offset that is an integer number of milliseconds in the interval from -86,400,000 (exclusive) to 86,400,000 (exclusive).

I guess "time value adjusted by a time zone offset" can be read in two different ways:

  1. Take a time value ±(8.64 * 10^15) and adjust it by ±86399999 to get ±8,640,000,086,399,999 as the final range.
  2. "local-offset time value" is still a "time value", so restricted to ±(8.64 * 10^15), but additionally has some time zone offset applied.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I read it the second way. I would prefer to explicitly state the final computed range in its definition.

Expand Down Expand Up @@ -33160,7 +33167,7 @@ <h1>
_min_: a Number,
_sec_: a Number,
_ms_: a Number,
): a Number
): an integral Number or *NaN*
</h1>
<dl class="header">
<dt>description</dt>
Expand All @@ -33185,7 +33192,7 @@ <h1>
_year_: a Number,
_month_: a Number,
_date_: a Number,
): a Number
): an integral Number or *NaN*
</h1>
<dl class="header">
<dt>description</dt>
Expand All @@ -33207,9 +33214,9 @@ <h1>
<emu-clause id="sec-makedate" type="abstract operation">
<h1>
MakeDate (
_day_: a Number,
_time_: a Number,
): a Number
_day_: an integral Number or *NaN*,
_time_: an integral Number or *NaN*,
): an integral Number or *NaN*
</h1>
<dl class="header">
<dt>description</dt>
Expand Down Expand Up @@ -33245,7 +33252,7 @@ <h1>
<h1>
TimeClip (
_time_: a Number,
): a Number
): a time value
</h1>
<dl class="header">
<dt>description</dt>
Expand Down Expand Up @@ -33501,7 +33508,7 @@ <h1>
<h1>
ParseTimeZoneOffsetString (
_offsetString_: a String,
): an integer
): an integer in the exclusive interval from -8.64 × 10<sup>13</sup> to 8.64 × 10<sup>13</sup>
</h1>
<dl class="header">
<dt>description</dt>
Expand Down
Loading