Handling common JavaScript issues when using Timezone

My takeaways after implementing calendar appointment booking system using JavaScript, Moment.js and PostgreSQL.

Alexandr Ursu
JavaScript in Plain English

--

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:

  1. save UTC date in DB as YYYY-MM-DD instead of a full timestamp in UTC. 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

example of wrong ranges without timestamp

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.

we cannot see timezone info here since DBeaver converts automatically into a human readable date

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 timestamp yyyy-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 avoid DST 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!

--

--