G’day guys!
Two posts in two days, I’m on a roll! Anyhoo, today I thought I’d talk a bit about an issue that I ran into a while back involving timezone offsets in JavaScript.
Oh, what happened?
I’m glad you asked! Well, I was writing a JavaScript application that was consuming a REST API, and the API was returning dates in the form of yyyy-mm-dd
. That is, without any time component and without any timezone information. Ideally they would just be returned as ISO strings to be easily consumable, but alas, the world is rarely perfect.
That doesn’t sound so bad. What was the problem?
Well, let’s assume that you’re getting back from the API a value that looks like 2020-10-04
, and you datify it using new Date('2020-10-04')
. You might naively think (like I did) that the resulting date object will hold an underlying value of midnight, on the 4th of October, 2020. In fact, it will have its time offset by the time offset of the device. Eg: if your local time offset is GMT +10, your local date object will have 10 hours added to it, and it’ll point to 10:00am. If all you’re doing is displaying a date, that might not actually be a problem. But if you’re calculating differences between times, then this will be the cause of some interesting bugs.
What’s the actual cause of this?
Well, according to MDN, date-only strings are interpreted as UTC dates, not local dates, so that explains the time offset being added. Even worse though, MDN actually discourages instantiating date objects based on strings - though this does make you wonder how they expect us to parse ISO strings without instantiating by strings.
Okay…well, what are our options?
There are a few, actually.
1. Manually add the time component. The reason why the extra hours are being added (or subtracted, if you have a negative timezone offset) is because the time info isn’t in the original string being passed. So simply adding 00:00:00
to the string before initialising it ought to prevent the weird behaviour with the timezone offset. Having said that, it will still involve initialising with a string, which MDN advises against.
2. Split the string and initialise by parts. If you know that the strings are coming back in yyyy-mm-dd
formats, you could split on -
and use the parts to initialise the date via new Date(yyyy,mm,dd)
.
3. Cater for the timezone offset after creating the date object. This one is the trickiest and requires the most work, but if you can’t count on the format of the dates being consistent, or if the other options aren’t possible for whatever reason, then this might be what you need.
function delocalise(dateObj) {
var localTimezoneMins = dateObj.getTimezoneOffset();
var localTimezoneMs = localTimezoneMins * 60000; //in milliseconds
//Undo whatever time adjustment the Date constructor will do to the local time.
var localTime = new Date(dateObj.getTime() + localTimezoneMs);
return localTime;
}
It’s fairly simple. The first line gets the minutes between the current timezone and UTC, then convert that to milliseconds. Then we add those milliseconds from the timestamp of the current data object and instantiate a new date object with the new timestamp.
Anyhoo, that’s all from me today. Let me know in the comments if you have any other ways to handle this that I didn’t cover. Cheers!
Catch ya!