Joe Eames | ng-conf | Oct 2020
Dates in JavaScript and Angular can be dangerous if you don’t know what you’re doing. Let’s look at how to avoid potential bugs by gaining a fundamental understanding of JavaScript and Angular date handling and the quirks that come along with that.
To start off with, you need to understand the ISO date format that the JavaScript ecosystem supports. It’s actually the ISO 8601 format. It’s a fairly straightforward format that looks like this:
yyyy-mm-ddThh:mm:ssTZD
In this example we see that it is the year, month, and day separated by dashes, then a “T” and the time in hours, minutes, seconds, and finally a time zone.
Looks straightforward. The only things I want you to really notice here are that dashes are used, and the timezone is there. The other important point to understand is that EVERYTHING is optional, mostly starting from the right and moving left. SO technically, just a year is fine, and everything else will default.
Here’s the real trick to it though. In most parsing implementations, the timezone is optional, and if its not included, then it defaults to UTC time. And that can cause a nasty bug if you don’t anticipate it.
This really has nothing to do with Angular. This is just the JS ecosystem so far. Let’s say you type in a date in ISO 8601 format:
2020-01-01
And then you parse that into a true date object
new Date('2020-01-01')
Now, if you display this date you just created, what displays will depend heavily on your location in the world. If you are in the UTC timezone or anywhere east of it up to the international date line, you’ll see something on Jan 1st 2020. But if you’re west of UTC (basically the western hemisphere and the Pacific) you’ll see something on December 31st 2019.
Why?
Because you’ll see the date displayed in your local timezone. And midnight on Jan 1st 2020 UTC happened at some point on December 31st if you are west of UTC. In New York, that moment was at 7pm on December 31st. For California, it was 4pm.
This whole thing is a huge problem because dates are not a primitive format in JavaScript. So no matter what your system does, eventually you’ll be handling dates as strings, either in the JSON format (which is essentially just ISO 8601) or in your own compatible format.
And that’s the core of the issue. Now let’s look at Angular.
This really comes down to the date pipe.
The date pipe will both display a date object OR a string in a date format.
For example, {{'01/01/2020' | date}}
will display Jan 1, 2020 in a template.
Now remember, if we use just a plain JavaScript date object, and feed in 2020-01-01
then here in the US the display will show December 31st. So what about the date pipe with a string format?
Using {{'2020-01-01' | date}}
will actually give you the correct result. Jan 1st, 2020. BUT if you do the following (this is with Angular 10, other versions may give other results because this seems fishy to me) {{2020-01}}
then you will get Dec 31, 2019. Remember each piece is optional. So the above specifies the year and month, and the default day of the first is chosen.
Fortunately, we rarely use just month and year especially when it’s a string.
But if we have a date object we create from maybe a string that the user types in, and that string had dashes, we get our problem. Why?
Because the date constructor will interpret ANY string with dashes as 8601, and apply a default timezone of UTC instead of the local timezone.
So if a user types in 2020-01-01
and you convert that to a date, and then display it back to the user, they’ll see “Dec 31, 2019” displayed. But again, ONLY if they are in the western hemisphere.
Solutions
So how do you solve this problem?
There’s a few options:
- Only use slashes.
This means that you have to validate all user input to have slashes, and any dates you get from other sources need to be converted, but that’s easy. Doing a quick split and join for example will fix any problems, since 2020/01/01 will parse using the local timezone instead of UTC.
- Specify the timezone 100% of the time.
I don’t recommend this option since it only kind of solves the problem. If a user in Berlin types in a date (not including a time) then a user in New York will see the day before.
- Understand your storage, and don’t store dates as full time specifications. This is for understanding what you’re storing with a given piece of data. Are you trying to capture a specific moment in time? then you need something like UTC. But if you’re just trying to store the “date”. i.e. if you’re tracking users birthdays, it’s not the moment of birth that matters, it’s the day they celebrate it. In that case, the timezone isn’t a consideration. So don’t store that item as a full date/time specification, such as with the JSON format. Instead store that as a string.
Mostly it’s just important to understand the issue, and the perniciousness of using dashes in dates.
Good luck and happy coding!
ng-conf: The Musical is coming
ng-conf: The Musical is a two-day conference from the ng-conf folks coming on April 22nd & 23rd, 2021. Check it out at ng-conf.org
Top comments (0)