DEV Community

Junxiao Shi
Junxiao Shi

Posted on • Updated on • Originally published at yoursunny.com

Where does GMT-0456 Timezone Come From?

This post is originally published on yoursunny.com blog https://yoursunny.com/t/2020/GMT-0456/

Recently, someone on the DCTech Slack community asked why the Date.prototype.toDateString function is having an off-by-one error:

new Date("2020-10-17").toDateString();
"Fri Oct 16 2020"
Enter fullscreen mode Exit fullscreen mode

My immediate response was: timezone.
The group then proceeded to discover that the Date constructor would interpret a date-only string as being in UTC timezone.
Washington, DC uses Eastern Daylight Time that is four hours behind UTC, so the timezone of the constructed Date object is 20:00:00 local time on the previous date.
Since toDateString uses local time, it prints as the previous date.

After that, I started testing some boundary conditions:

new Date("0001-01-01").toString()
"Sun Dec 31 0000 19:03:58 GMT-0456 (Eastern Standard Time)"

new Date("0000-01-01").toString()
"Fri Dec 31 -0001 19:03:58 GMT-0456 (Eastern Standard Time)"
Enter fullscreen mode Exit fullscreen mode

One problem is that, the year zero should not exist, but it's understandable that JavaScript has not been invented around that time.
What really puzzles me is, why does the timezone show up as GMT-0456, instead of the usual GMT-0500.

I did a binary search to find when did the timezone change from GMT-0456 to GMT-0500:

new Date("1883-11-18 12:03:57").toString();
"Sun Nov 18 1883 12:03:57 GMT-0456 (Eastern Standard Time)"

new Date("1883-11-18 12:03:58").toString();
"Sun Nov 18 1883 12:03:58 GMT-0500 (Eastern Standard Time)"
Enter fullscreen mode Exit fullscreen mode

The magic timestamp turns out to be between 1883-11-18 12:03:57 and 1883-11-18 12:03:58.
Something must have happened on that day!

An Internet search of the date turned up this article: Today in History: November 18, 1883: Time zones standardized in Canada and USA.
Basically,

  • Until 1883-11-18, each town sets its own time, based on their own estimation of solar time.
  • Having different local time in each town was causing problems with the railroads, so that the railroad companies established five timezones for Canada and the United States.
  • 1883-11-18 marks the date when Eastern Standard Time was established.

This explains the date 1883-11-18, but still doesn't explain why the cut-off time was 12:03:57 and why the previous timezone is GMT-0456.
I started looking for answers in the computer source code.

Since I was testing on Chrome browser, I started with the V8 JavaScript engine.
I located the code related to Date and timezone, but didn't find GMT-0456 in there.
Instead, V8 invokes GetLocalOffsetFromOS function, suggesting that the timezone information comes from the operating system.
I don't have access to Windows source code, but I recall that Linux uses tzdata for timezone information.

I downloaded the current Time Zone Database.
In the northamerica data file, I found the following records:

# US eastern time, represented by New York

# From Paul Eggert (2014-09-06):
# Monthly Notices of the Royal Astronomical Society 44, 4 (1884-02-08), 208
# says that New York City Hall time was 3 minutes 58.4 seconds fast of
# Eastern time (i.e., -4:56:01.6) just before the 1883 switch.  Round to the
# nearest second.

# Rule  NAME  FROM  TO    -  IN   ON       AT    SAVE  LETTER
Rule    NYC   1920  only  -  Mar  lastSun  2:00  1:00  D
Rule    NYC   1920  only  -  Oct  lastSun  2:00  0     S
Rule    NYC   1921  1966  -  Apr  lastSun  2:00  1:00  D
Rule    NYC   1921  1954  -  Sep  lastSun  2:00  0     S
Rule    NYC   1955  1966  -  Oct  lastSun  2:00  0     S
# Zone  NAME              STDOFF    RULES  FORMAT  [UNTIL]
Zone    America/New_York  -4:56:02  -      LMT     1883     Nov  18  12:03:58
                          -5:00     US     E%sT    1920
                          -5:00     NYC    E%sT    1942
                          -5:00     US     E%sT    1946
                          -5:00     NYC    E%sT    1967
                          -5:00     US     E%sT
Enter fullscreen mode Exit fullscreen mode

So the answer to this GMT-0456 mystery is:

  • Eastern Time is really "time in New York".
  • Before Eastern Standard Time was established on the noon of Nov 18, 1883, the local time in New York was 12:03:58.
  • That time was 4 hours and 56 minute (04:56) behind Greenwich Mean Time, so that it shows up as GMT-0456.

Oldest comments (3)

Collapse
 
tbroyer profile image
Thomas Broyer

Also, a takeaway from this story: never use a datetime object to represent a local date (in JS, use an ISO 8601 formatted string, or make your own LocalDate object).

Collapse
 
crisarji profile image
crisarji

Nice to hear the story! 2 weeks holding on to the article, and you could have posted it on the anniversary 😜

Collapse
 
sang profile image
Sang • Edited

Thanks to your post, I have added test cases to my zoned-date library.

Precisely speaking, the clocks shown something like:

-> 1883-11-18T12:00:00.000
-> 1883-11-18T12:03:59.999 (to be backwarded in next tick, in old timezone so far, GMT-0456)
-> 1883-11-18T12:00:00.000 (backwarded, join new timezone GMT-5)
-> 1883-11-18T12:03:59.999
-> 1883-11-18T12:04:00.000

github.com/tranvansang/zoned-date/...

test('GMT -0456', () => {
    // https://dev.to/yoursunny/where-does-gmt-0456-timezone-come-from-38m1
    const eps = 1e-8

    // before
    assert.ok(Math.abs(new ZonedDate('1883-11-18T11:58:59.999', {timezone: 'America/New_York', disambiguation: 'reject'}).offset - -4.933333333333334) < eps)

    // ambiguous
    assert.throws(() => new ZonedDate('1883-11-18T12:00:00.000', {timezone: 'America/New_York', disambiguation: 'reject'}).offset)
    assert.ok(Math.abs(new ZonedDate('1883-11-18T12:00:00.000', {timezone: 'America/New_York', disambiguation: 'earlier'}).offset - -4.933333333333334) < eps)
    assert.strictEqual(new ZonedDate('1883-11-18T12:00:00.000', {timezone: 'America/New_York', disambiguation: 'later'}).offset, -5)

    // after
    assert.strictEqual(new ZonedDate('1883-11-18T12:04:00.000', {timezone: 'America/New_York', disambiguation: 'reject'}).offset, -5)

    // try moving time
    assert.strictEqual(
        new ZonedDate('1883-11-18T12:04:00.000', {timezone: 'America/New_York'}).time -
        new ZonedDate('1883-11-18T12:03:59.999', {timezone: 'America/New_York'}).time,
        240001
    )
    assert.throws(() => new ZonedDate('1883-11-18T12:03:59.999', {timezone: 'America/New_York', disambiguation: 'reject'}).setTime(t => t + 1).offset)
    assert.ok(Math.abs(new ZonedDate('1883-11-18T12:03:59.999', {timezone: 'America/New_York', disambiguation: 'earlier'}).setTime(t => t + 1).offset - -4.933333333333334) < eps)
    assert.strictEqual(new ZonedDate('1883-11-18T12:03:59.999', {timezone: 'America/New_York', disambiguation: 'later'}).setTime(t => t + 1).offset, -5)
})

Enter fullscreen mode Exit fullscreen mode