Why you should never mutate a JavaScript Date

In JavaScript on August 2, 2013 by Matt Giuca Tagged: ,

Poor JavaScript. Everybody’s friend, but nobody loves it. Perhaps because some of its bits are just made of stupid. Consider this JavaScript snippet involving the Date class:

> date = new Date(2013, 0, 1)
Tue Jan 01 2013 00:00:00 GMT+1100 (EST)
> date.setUTCMonth(10)

First, I create a new date at midnight on January 1, 2013 (yes, months start from 0 and dates start from 1, it’s insane). Then, I set the month to November. What is the resulting value of date? Surely it’s November 01 2013? Nope, it’s:

Sun Dec 02 2012 00:00:00 GMT+1100 (EST)

Wat? The month is one year earlier, one month later, and one day later than I had expected. But it gets worse:

> date.setUTCMonth(10)
Fri Nov 02 2012 00:00:00 GMT+1100 (EST)

That’s right: setUTCMonth is not even idempotent! Calling it a second time gives a different result than the first. What on Earth is going on? Two things, actually.

Firstly, you may have noticed that the dates are in GMT+1100 (my local time zone, Australian Eastern Daylight Time). The constructor and string display both use the computer’s local time, which is a big mistake. For one thing, it means that software behaves differently in different parts of the world. (Remember this bug I found in the SOPA blackout snippet?) Software should only ever deal with UTC except when displaying times to the user. So in eastern Australia, “new Date(2013, 0, 1)” is actually Dec 31 2012 13:00:00 UTC. You can work around this, but JavaScript isn’t helpful: there is a Date.UTC function, but it doesn’t return a Date object. To construct a date from UTC time, call “new Date(Date.UTC(year, month, day, hours, minutes, seconds))”. Okay, so that’s confusing, but not altogether wrong.

Secondly, and more awfully, we are running into the intrinsic problem of Gregorian dates, that day 31 is valid for some months but not others. Every date library has to deal with this issue, but here JavaScript just sucks. When you start with December 31 and set the month to November, you get November 31. Since that date doesn’t exist, JavaScript “helpfully” gives you December 1. (Fancy that: a setter that doesn’t always end up setting the field to the value you gave it!) Dec 01 2012 13:00:00 UTC is Dec 02 2012 00:00:00 in my local time zone. Calling setUTCMonth a second time sets the month to November, keeping the date value at 1. Seriously, setting the month when the date is not valid for that month should be an error; at least then you would notice the problem. Here JavaScript falls into the classic API design trap of giving unhelpful garbage instead of useful errors.

So what should you do to avoid this? I would recommend never using any setters on the Date class. It is fundamentally dangerous to change the year, month or date, because in all three cases you can get silent corruption of other fields. Treat Date objects as immutable, and make new ones when you need to. Use the “new Date(Date.UTC(…))” form I gave above.

Leave a Reply

Fill in your details below or click an icon to log in: Logo

You are commenting using your account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

%d bloggers like this: