tc39 / tc39/proposal-temporal

Polyfill: time zone transitions before 1847 are not found (Asia/Manila and four Pacific zones on 1844-12-31)

Open Beginner friendly
#3,330 1 comment 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
HTML
Stars
3.7k
Forks
177
PR merge metrics
No merged PRs in 30d

Description

GetNamedTimeZoneNextTransition and GetNamedTimeZonePreviousTransition don't search before BEFORE_FIRST_DST (polyfill/lib/ecmascript.mjs l.143 at e8cc03fc970a65a3359e8870e3b35e687ac94e55):

const BEFORE_FIRST_DST = DateUTC(1847, 0, 1); // 1847-01-01T00:00:00Z

next jumps forward to it, and previous stops there. 1847 was meant to be the year of the first transition in the TZDB, but it no longer is. Five zones have a transition on 1844-12-31, when the Philippines and nearby Pacific islands moved from the American side of the date line to the Asian side and skipped that day: Asia/Manila (-15:56+08:04), Pacific/Guam and Pacific/Saipan (-14:21+09:39), Pacific/Kosrae (-13:08+10:52) and Pacific/Palau (-15:02+08:58). (The tzdb asia file cites the governor-general's proclamation of 1844-08-16 that 1844-12-30 be followed by 1845-01-01. australasia says the other islands "kept American time until the Philippines switched at the end of 1844".) The polyfill doesn't find these transitions:

Call Polyfill on main Expected
Temporal.Instant.from('1800-01-01T00:00:00Z').toZonedDateTimeISO('Asia/Manila').getTimeZoneTransition('next') 1899-09-06T12:00:00+08:00[Asia/Manila] 1845-01-01T00:00:00+08:04[Asia/Manila]
Temporal.Instant.from('1846-06-01T00:00:00Z').toZonedDateTimeISO('Asia/Manila').getTimeZoneTransition('previous') null 1845-01-01T00:00:00+08:04[Asia/Manila]
Temporal.PlainDate.from('1844-12-31').toZonedDateTime('Asia/Manila') 1899-09-06T12:00:00+08:00[Asia/Manila] 1845-01-01T00:00:00+08:04[Asia/Manila]
Temporal.ZonedDateTime.from('1844-12-30T12:00[Asia/Manila]').hoursInDay 479340.06444444443 24
Temporal.ZonedDateTime.from('1844-12-30T12:00[Asia/Manila]').round({ smallestUnit: 'day' }) 1844-12-30T00:00:00-15:56[Asia/Manila] 1845-01-01T00:00:00+08:04[Asia/Manila]
control: Temporal.Instant.from('1800-01-01T00:00:00Z').toZonedDateTimeISO('Europe/London').getTimeZoneTransition('next') 1847-12-01T00:01:15+00:00[Europe/London] same

The expected values are Chrome 153's native Temporal. The offsets themselves are right in the polyfill, because they come from Intl.DateTimeFormat. Only the transition search misses the change.

The third row is the worst one. Local 1844-12-31 doesn't exist in Manila, so GetStartOfDay looks for the next transition after the day before. That search jumps to 1847 and returns the 1899 transition, 55 years later. hoursInDay and round() on 1844-12-30 are wrong for the same reason. Guam, Saipan, Kosrae and Palau give the same kinds of wrong results for these calls, with their 1901 transition in place of Manila's 1899 one.

I checked all 418 zones in Intl.supportedValuesOf('timeZone') against Chrome 153: the first transition after 1800, previous from 1 ns after it, and previous from 1845-06-01 and 1850-01-01. Only these five zones differ. In Chrome, previous from 1844-12-31T00:00Z is null in all 418 zones, so these are the earliest transitions.

Proposed fix

Move the constant to the start of 1844:

--- a/polyfill/lib/ecmascript.mjs
+++ b/polyfill/lib/ecmascript.mjs
@@ -140,7 +140,9 @@
 const MS_IN_400_YEAR_CYCLE = (400 * 365 + 97) * DAY_MS;
 const YEAR_MIN = -271821;
 const YEAR_MAX = 275760;
-const BEFORE_FIRST_DST = DateUTC(1847, 0, 1); // 1847-01-01T00:00:00Z
+// The first transition in the TZDB is on 1844-12-31, when Asia/Manila and some
+// Pacific zones moved across the date line.
+const BEFORE_FIRST_DST = DateUTC(1844, 0, 1); // 1844-01-01T00:00:00Z
 
 const BUILTIN_CALENDAR_IDS = [
   'iso8601',

1844-01-01 is almost a year before the first transition, as 1847-01-01 was before Europe/London's on 1847-12-01. So GetNamedTimeZonePreviousTransition's early return null at the constant still can't cut off a search window, which is 19 days at most. The only cost is up to three more years of 19-day steps when a search reaches the constant. Antarctica/Troll is the worst case (its first transition is in 2005): previous from just before that transition takes about 4.95 ms instead of 4.86 ms.

With this change on e8cc03fc97, all 1,829 rows I ran match Chrome 153. These include the table above, the five zones' next/previous/hoursInDay/startOfDay/round/disambiguation rows around 1844-12-31, and the 418-zone checks. On main, 56 of them are wrong, all in these five zones. next and previous from the first of every month of 2020–2030 in 24 zones (6,336 lookups) give byte-identical output before and after the change.

js-temporal/temporal-polyfill has the same constant and gives the same results. I'll open the same change there and link it here.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start in polyfill/lib/ecmascript.mjs at BEFORE_FIRST_DST and the GetNamedTimeZoneNextTransition/GetNamedTimeZonePreviousTransition searches. Reproduce the listed Manila transition cases and compare the five affected zones with the expected values; done means the 1,829 checks match Chrome 153 without changing the stated 2020–2030 control results.

Written by the indexing model from the issue text.

Assessment

Tech stack
javascript
Domain
backend
Issue type
Bug
Difficulty
1/5
Estimated time
Under an hour
Activity status
Active
Clarity
Clearly specified
Newbie friendliness
92/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.