Handling common JavaScript issues when using Timezone
My takeaways after implementing calendar appointment booking system using JavaScript, Moment.js and PostgreSQL.
It’s been one year since I inherited and completed a Javascript Booking System project but it kept giving surprises all this period. In this post I would like to share with you some best practices which should be considered while working with dates.
The 3 major issues that you can encounter while working with JavaScript dates are Timezone, UTC and DST, let’s take a closer look to each one.
Common Timezone issues
Outputting a date in your timezone seems to be pretty straightforward. However, probably the first question that will appear while working on this project is: Should I save date with user timezone? So, it depends on your project but my takeaway is never save dates in user’s timezone. I found it a good practice to always convert the date into UTC and save converted value into database. Make sure also to have a separate column in your database to store event timezone (in my case it is the timezone of the user who created the event). When pulling UTC appointment date from database, convert this date back to event local timezone e.g:
//good
moment().utc().format(); // Convert date into UTC and save value
moment.tz('2021-02-13T05:31:18Z', 'America/New_York').format(); // Convert back to user's timezone and display on frontend
Common UTC issues — “Time Travel”
I’m pretty sure you had such situation where you submit an order or some application online after 8 or 9PM and you see the submission date showing with tomorrow’s date. Internally we refer to it as a “Time Travel” issue. This issue usually happens if you:
- save UTC date in DB as
YYYY-MM-DD
instead of a full timestamp inUTC
. e.g.
// wrong
'2021-02-13'// good
'2021-02-13T05:31:18Z'
If you’re converting timestamp into UTC
and getting just date
part without time
(YYYY-MM-DD
), the date will fall into the next day (+1 day offset) which will cause insertion of the wrong date into database, as example try to console.log()
this:
// bad
moment('2021-02-13T19:15:18-05:00').utc().format('YYYY-MM-DD');
// output will be '2021-02-14' instead of '2021-02-13' we can see +1 day offset// good
moment('2021-02-13T19:15:18-05:00').utc().format();
2. another use case would be if you saved full timestamp in UTC but when reading from database you are truncating time part (usually on the front end). The result will be the same as in previous example you’ll have date offset + 1 day, but the bug is located this time on your front end environment.
Solution: Always save into database full date with time stamp in UTC
and convert UTC
date back into user’s timezone then truncate time
part e.g.:
// good
moment('2021-02-14T00:27:58Z').tz('America/New_York').format('YYYY-MM-DD'); // output is 2021-02-13 - converted back to user's timezone
Daylight saving time issue — DST
We went live without having a lot of time for testing and this always kicks you back in the but. Once Daylight Saving Time (further DST) occurred we’ve got one big issue. All recurring appointments were offset by 1 hour, oops. I started debugging and here is what I found:
Ranges
When I looked in the database I realized that startDate
and endDate
were saved as a range repeat_range
in the database, but the main issue is that they were saved as date
only strings without time
. However in another column they stored meeting_time
as strings. When we were pulling data from database these 2 strings were concatenated and without knowing actual timezone the DST change was not applying to them, which caused +-1 hour offset. To keep the post short I will post just what should’ve been done at the begging to avoid this situation instead of going through all the steps I took to get the data fixed.
// bad
['2010-01-01','2021-01-01')// good
['2010-01-01T14:45:00Z','2021-01-01T15:45:00Z') //yyyy-MM-dd’T’HH:mm:ssZ
Solution: Always save into database full date with time stamp and timezone info. e.g.
Summary
I would like to summarize all the takeaways from this article in a list of best practices. Please let me know in the comments if you agree/disagree with them or if you had some similar issues.
Best practices:
- I would definitely recommend using a
UTC
timestamp to indicate a particular point in time. - Always store in the database user’s timezone, later used to convert event in his local time.
- Never save date (
YYYY-MM-DD
) without time (HH:mm:ss
), use full timestampyyyy-MM-dd’T’HH:mm:ssZ
- When using ranges use full timestamp
yyyy-MM-dd’T’HH:mm:ssZ
and do not keep date (YYYY-MM-DD
) and time (HH:mm:ss
) in separate columns to avoidDST
issue.
Even if it seems to be challenging at first. What I would like to outline is to keep things simple, always work with UTC time until right before displaying to the user.
Happy hacking!