loading...

My mistakes with Dates on JavaScript

raerpo profile image Rafael E. Poveda ・6 min read

Dates are hard! And I'm not talking about that human to human interaction (those are hard too but differently). I'm talking about describing the time in programming, especially in JavaScript. Not only the widely known quirks with JavaScript and dates make it hard, but if you add timezones and different types of representations that exist everything becomes even harder.

I recently had a bug (that turned out to be two bugs) that made me realize that there were a lot of missing pieces in my understanding of how dates work on JavaScript so I made a journey to fill those gaps and learned a few tricks on the way that I want to share with you.

Creating a new date with a wrong string parameter

You can create a new date using the Date constructor with 4 types of parameters.

1) without parameters.

This is very common. You just use new Date() and you will get a Date object in your local time. It's very useful for scenarios when you don't need to represent a Date in multiple timezones.

2) With timestamp.

A timestamp in JavaScript is the number of milliseconds that have been passed since January 1, 1970 (Epoch time). One thing that is important here is that JavaScript handles Epoch time in milliseconds while other languages and even Unix-based operating systems do it in seconds.

For example, in Python I get this:

>>> time.time()
1582293096.868101

But in JavaScript I will get:

Date.now()
1582293096211

So it's very important that if you receive a timestamp as date representation, you need to check if it's in milliseconds or seconds (it will be more likely the latter). If you are not aware of this you will get different dates.

// Parsing Date with timestamp from Python
new Date(1582293096.868101)
Date Mon Jan 19 1970 04:31:33 GMT-0300 (Chile Summer Time)

// Parsing Date with timestamp from JS
new Date(1582293096211)
Date Fri Feb 21 2020 10:51:36 GMT-0300 (Chile Summer Time)

In this case, a quick fix is to multiply the timestamp by 1000 before using it in a JavaScript app.

// Date with timestamp from Python adding milliseconds
new Date(1582293096.868101*1000)
Date Fri Feb 21 2020 10:51:36 GMT-0300 (Chile Summer Time)

// Date with timestamp from JS
new Date(1582293096211)
Date Fri Feb 21 2020 10:51:36 GMT-0300 (Chile Summer Time)

3) With date arguments.

In this option, we specify the year, month, day, hours, minutes, seconds and milliseconds as parameters in the Date constructor.

new Date(2020, 1, 20, 13, 20);
// Date Thu Feb 20 2020 13:20:00 GMT-0300 (Chile Summer Time)

In this way of creating a date, you will also get a date in your system's timezone. This way is commonly ignored by developers because of the month being zero-based 🤷🏻‍♂️. One cool thing about this way is that all of the undefined values in the parameter list will default to 0.

new Date(2020, 1);
// Date Sat Feb 01 2020 00:00:00 GMT-0300 (Chile Summer Time)

new Date(2020, 1, 10, 3);
// Date Mon Feb 10 2020 03:00:00 GMT-0300 (Chile Summer Time)

4) With strings

In this way, you passed a string to initialize your date. The most common way to represent a Date is an ISO string like this:

"2020-02-21T14:19:35.926Z"

It has a standard structure, with year-month-day-hours-minutes-seconds and the "Z" representing UTC or GMT-0. So when you pass that string as parameter to the Date constructor, the date will be converted to your local system timezone.

new Date("2020-02-21T14:19:35.926Z")
// Date Fri Feb 21 2020 11:19:35 GMT-0300 (Chile Summer Time)

// As you can see, it subtracts the 3 hours corresponding to my timezone.

Soooo, after all this introduction I want to show you the cause of my second bug.

I had a string date like this '2020-02-21' and I just passed it like that to a Date constructor and I got this:

new Date('2020-02-21')
// Date Thu Feb 20 2020 21:00:00 GMT-0300 (Chile Summer Time)

🤦🏻‍♂️🤦🏻‍♂️🤦🏻‍♂️🤦🏻‍♂️🤦🏻‍♂️🤦🏻‍♂️🤦🏻‍♂️

So instead of getting the date that I (naively) expected, I got the day before that. And it makes total sense since JavaScript will parse the date in UTC and then apply the timezone change to set it in my system timezone.

My first lesson is to be consistent with how do you represent dates. If you get strings as input just make sure that they have the right format. And if not, make sure to modify them to have the right format.

In this case, as I couldn't get a different string from backend, I could've done two things:

1) Split the string and pass it as date arguments:

const [year, month, day] = '2020-02-21'.split('-');
new Date(year, Number(month) - 1, day);

It works, but the Number(month) - 1 part doesn't feel right 🤔.

2) Adding an explicit time

new Date('2020-02-21' + 'T00:00');

This way is cleaner and it will work since I'm telling JavaScript that I don't want the time to be inferred so it doesn't need to subtract hours (or add them if I happen to be in the + side of the UTC).

Bug in an older version of date-fns-tz

After reaching this point, you probably are thinking "pff I never have these kinds of problems because I use moment/date-fns/another-date-library and let them take care of those things".

This bug was hard to find. date-fns-tz is a library that allows you to add timezone support to date-fns dates. I had a date defined in UTC and I need it to be in multiple timezones. For this purpose, I used a function named utcToZonedTime and its use is pretty straightforward.

import { utcToZonedTime } from "date-fns-tz";
const myDate = utcToZonedTime("2020-02-17T03:00:00Z", "America/Santiago");

America/Santiago is a GMT-3 timezone, so, as you would expect, from the date that I passed the result would be Mon Feb 17 2020 00:00:00. But surprisingly, the result was Tue Feb 18 2020 00:00:00.

WTF

It was adding a full day and I did not understand why it was happening. After a LOT of debugging my own code, I noticed that I was using the version 1.0.8 of date-fns-tz so as last resort after not finding anything in my code I tried to upgrade the library to the latest version (1.0.10) and finally I got the Mon Feb 17 2020 00:00:00 that I was expecting.

Apparently it was a bug on that particular version that matched my use case.

So my first lesson that could be very obvious is always keep your dependencies updated. When we add a dependency we are trusting that everything will work perfectly but every piece of software is susceptible to have bugs and it's something that we need to deal with, especially on open source tools.

A new trick!

In this application that I'm working on, I need to make sure to show the right date and time for certain events independently of where the user is. For example, if something is set to happen on February 21, 2020 at 10:00 am in Chilean time (GMT-3), a user in Mexico City or in Sao Paulo should see that at the same time and not transform into their timezones.

In order to do this, I can open Chrome setting a timezone and even a language. In that way I'm able to test my app on different timezones and check that everything is correct.

TZ='America/Sao_Paulo' open -na "Google Chrome"

This will open a new Google Chrome window as if I were in Sao Paulo timezone wise.

https://imgur.com/a/7maxfc5

Or if run

TZ='America/Bogota' open -na "Google Chrome"

I will get:

https://imgur.com/a/jHo1SSt

With this little trick, I'm able to test my applications as if I were in a different timezone and check if my dates behave in the way that I expect.

Conclusion

Every mistake is an opportunity to learn something new and this issue was very useful for me to learn things that I ignored after years of working with Dates. I sincerely hope you find something useful here.

Discussion

pic
Editor guide