Introduction

JavaScripts Date constructor deals solely with UTC date/times internally. However, the accompanying date methods (e.g. .getFullYear(), .getDate(), .getMonth(), etc.) coerce the UTC date to the OS/Hosts local timezone. While this is covered in the ECMAScript spec (note the LocalTime(t) abstract operation), this implicit timezone coercion can result in a discrepancy between what value the programmer intended to return and what value was actually returned.

Proof of Concept (a.k.a Example Bug)

Note: The following example has been tested in the Firefox console on November 20, 2019. Other JavaScript environments or future releases of JavaScript may behave differently.

let dateString = '2020-01-01';

Assume that dateString has been returned by some API such as Google Calendars and that somewhere in the API call it is specified that the returned string is an EST date.

To more easily work with the date, it would be helpful to turn it into a Date Object via the Date constructor.

let dateObj = new Date(dateString);

Great! Now just the year can be easily returned with a .getFullYear() method call.

console.log(dateObj.getFullYear()); // '2019' is returned

Note: The date returned from the above code snippet will vary based on the environment/host's timezone. In this example that is EST.

Huh? 2019 is definitely not the year expected to be returned!

The issue, is that since the Date constructor only works in UTC, dateString was assumed to be, and is from the JS engine's point of view, a UTC date. Then, when the .getFullYear() method is called, the date returned is coerced to the local timezone (EST).

It's worth reiterating that this is exactly how the JS engine is supposed to handle this code as is specified in the spec.

The problem illustrated above began when assumptions about dateString's timezone stayed the same, even when internally, the timezone represented was coerced.

Conclusion

A simple solution to the bug would be to instead use the .getUTCFullYear() method. There are an assortment of .getUTC...() methods that could be used in other scenarios where a date is known to be a certain timezone at author time, and does not need to be converted to the local timezone when returned.

If converting to the local timezone is required, you can specify a timezone offset when instantiating the Date object. This method proves more flexible than the simple solution above.