2

Short Version

In JavaScript, can new Date(2013, 9, 20) legitimately return October 19?

Long Version

When Daylight Saving Time begins, there is a gap in local time as clocks are adjusted forward. If I construct a Date object with a time that falls within this gap, what is the expected behavior according to the ECMAScript specification?

Various browsers behave differently, as seen below. (I ran these tests on Windows 8.)

Example 1: In the Pacific Time Zone (UTC-08), the expression

new Date(2013, 2, 10, 2, 34, 56).toString() // Sun Mar 10 2013 02:34:56

gives the following results:

IE 10:      Sun Mar 10 03:34:56 PDT 2013
IE 11:      Sun Mar 10 2013 03:34:56 GMT-0700 (Pacific Daylight Time)
Chrome 29:  Sun Mar 10 2013 01:34:56 GMT-0800 (Pacific Standard Time)
Firefox 23: Sun Mar 10 2013 03:34:56 GMT-0700 (Pacific Standard Time)

Example 2: In the Brasilia Time Zone (UTC-03), the expression

new Date(2013, 9, 20, 0, 34, 56).toString() // Sun Oct 20 2013 00:34:56

gives the following results:

IE 10:      Sun Oct 20 01:34:56 UTC-0200 2013
IE 11:      Sun Oct 20 2013 01:34:56 GMT-0200 (E. South America Daylight Time)
Chrome 29:  Sat Oct 19 2013 23:34:56 GMT-0300 (E. South America Standard Time)
Firefox 23: Sat Oct 19 2013 23:34:56 GMT-0300

Based on these two examples, it seems that IE adjusts the time forward, Chrome adjusts the time backward, and Firefox can't make up its mind.

What the specification says: Based on what I could glean, new Date(yyyy, mm-1, dd, hh, mi, ss) constructs a Date with a time value of UTC(yyyy-mm-dd hh:mi:ss), where

UTC(t) = t – LocalTZA – DaylightSavingTA(t – LocalTZA)

LocalTZA is the local time zone adjustment for standard time (e.g., -08:00 in the Pacific Time Zone), and DaylightSavingTA(t) is the daylight saving time adjustment for t (e.g., 01:00 during DST, 00:00 otherwise).

It's unclear to me, though, what DaylightSavingTA should return for an argument of t = 2013-03-10 10:34:56 in the Pacific Time Zone or t = 2013-10-20 03:34:56 in the Brasilia Time Zone.

Michael Liu
  • 44,833
  • 12
  • 104
  • 136
  • 1
    Good question. Best advice: Don't do it :) – mplungjan Sep 19 '13 at 14:39
  • @mplungjan: I'm asking because I (and probably others) have written code like `new Date(year, month, day)` to construct a date without a time, and evidently this doesn't always work! – Michael Liu Sep 19 '13 at 14:41
  • I ALWAYS normalise. `new Date(year,month-1,date,0,0,0,0)` but you may decide to use `new Date(year,month-1,date,12,0,0,0)` to make sure the -X does not get you a date yesterday – mplungjan Sep 19 '13 at 14:44
  • @mplungjan: Adding those zeroes doesn't help. Setting the hour to 12 does, but how many developers would think to do that? – Michael Liu Sep 19 '13 at 14:47

2 Answers2

3

what is the expected behavior according to the ECMAScript specification?

Good question! You are correct that this depends on DaylightSavingTA which is strictly undefined, however we can derive implications about DaylightSavingTA from the way it works when used in the inverse function:

LocalTime(t) = t + LocalTZA + DaylightSavingTA(t)

For this to convert UTC t to a local time correctly, DaylightSavingTA(hour_at_beginning_of_dst) must be 1 hour and DaylightSavingTA(hour_after_end_of_dst) must be 0.

The same function is called in the UTC() function using a t that in that function represents a DST-adjusted time. So in the non-existent-local-hour at the start of DST, DaylightSavingTA(the-dst-adjusted-time-which-is-one-hour-ahead) is 1 hour. Consequently:

Can new Date(2013, 9, 20) legitimately return October 19?

Yes.

I'm asking because I (and probably others) have written code like new Date(year, month, day) to construct a date without a time, and evidently this doesn't always work!

Indeed! Use UTC dates for that kind of calculation—new Date(Date.UTC(2013, 9, 20)) and the getUTC* methods. In general, it's best to stick to UTC for everything except the final user-facing presentation of times.

bobince
  • 498,320
  • 101
  • 621
  • 807
1

Your observations are correct, and I have made similar ones. Read this post and the blog article I wrote for additional details.

The gist is that ECMAScript 5 defines part of the DaylightSavingTA algorithm in section 15.9.1.8., but it does a really crummy job of it. Things are looking better for ECMAScript 6.

Regarding skipping forward or backwards for an invalid input, the spec doesn't require it to fall either way. In practicality though, spec aside, it only ever makes sense to do one of the following:

  • Skip forward by the DST amount. This makes sense because you might encounter the invalid value if someone forgot to adjust for DST in the input.

  • Throw an error or exception, since that calendar time is nonexistent. JavaScript doesn't do this, but other languages/libraries do. For example, .NET's TimeZoneInfo.ConvertTimeToUtc method will throw an exception if you pass it an invalid local time.

I cannot think of any real-world use case where it makes sense to move backwards, like Chrome does. It's valid by the ES5 spec, but it doesn't make any sense.

Firefox just has a bug where its labels are messed up, which is also described here.

Also keep in mind that while most zones transition by one hour for DST, there is at least one that is only 30 minutes (Australia/Lord_Howe), and some of the Antarctica zones are even stranger.

The other related concern is with ambiguity in the fall-back transition. Because of DST, there is an overlap where a valid local time could represent two different UTC moments. Again, the ES5 spec is silent about what should happen in this situation. In reality, we again find that the behavior is different per browser. Some take the daylight time, because it's the first of the two possible moments. Others take the standard time because, well, it's the "standard".

In other frameworks, Python (via pytz) will throw an exception, while .NET's TimeZoneInfo methods will assume the "standard" time. Libraries like Noda Time give you options. But in JavaScript, the behavior is implementation specific.

Probably the best thing you could do for ambiguous time or invalid time is test your input. If it's invalid, prompt your user to enter a valid time. If it's ambiguous, prompt your user to select one of the two possibilities.

Consider the graphs of the LocalTime(t) function as it applies to US Pacific Time (America/Los_Angeles).

                Spring Forward DST Transition

                Fall Back DST Transition

This is a valid function, but if you flip the X and Y axis then you will see the effect of UTC(t), which is not defined over the spring-forward transition range, and is not a function at all over the fall-back transition range.

Community
  • 1
  • 1
Matt Johnson-Pint
  • 197,368
  • 66
  • 382
  • 508
  • Thanks for the links. You write that "the spec doesn't require it to fall either way", but what do you think about bobince's conclusion, based on the formula for LocalTime(t), that the ECMAScript 5 spec actually *requires* it to go backward? – Michael Liu Sep 20 '13 at 03:03
  • *Require* is a bit strong... that's the only result I find consistent with the language in the spec, but there are underlying assumptions in there, like `DaylightSavingTA` being a pure function, which are required for sanity but not explicitly stated. – bobince Sep 20 '13 at 09:25