Cover
Architecture

Dealing with datetimes and timezones in your application.

Introduction
Dealing with datetimes and timezones in your application.

Some of us have had to deal with it and still get traumatized thinking back about it, others can be glad that they have avoided the topic thus far. I'm talking about working with time zones, this is often regarded as one of the more complex problems of software engineering.

https://xkcd.com/1883/

In this blogpost I'd first like to explain the complexity of time zones, for those who have been lucky enough to avoid it, after which I will write several ways on how you could deal with the problem to finally conclude with my preference. Let's get started

The problem

Timezones are very difficult to deal with, partially due to the ever changing rules and partially due to the allowed flexibility in these timezones. Let's take a look at some of these 'edge cases' from timezones, this to give you a feeling on how much fun it is to work with timezones:

  • Lets get started by telling you that timezones are not always in full hours.
       - The majority of the timezones are in full hours, however there are exceptions which are either in xx:30 or xx:45 (Nepal). Going back further in time results in even more funky behavior though, since Amsterdam used to have a +00:19:32.12s timezone.
       - It gets better, because not only can these timezones vary within 1 country, but they can also vary because of your ethnicity. Another extreme example of this is the North Pole, where time has no meaning, meaning that everyone there follows the timezone that is convenient for them.

    These timezones also expand the full range from -12 to +14; Yes, +14, because some countries in the pacific wanted to be the first to celebrate the year 2000.
  • As mentioned before; Timezones are subject to change, meaning that 1 country can suddenly decide to stop supporting DST (Daylight Saving Time)  
       - For example; In 2020, Russia decided to move the timezone of the Volgograd region back with 1 hour.
       - Even if you use a library, which will very likely take care of these updates, you have to put in a lot of effort to make sure that the library is up to date to prevent 'missing' these updates.
  • If you, after finding the list of timezone abbreviations, think that all the timezones have an unique abbrevation, you should take a look at CST.
      - CST is short for:
          - Central Standard Time
          - China Standard Time
          - Cuba Standard Time
      Fun times...
  • Thus we now have changing timezones and non-unqiue abbrevations but surely they cannot come up with any other weirdness, right? Well sometimes countries decided to skip days, meaning that in specific countries some days did not exist!
       - Samoa decided to skip the 30th of December in order to improve their ties with major trading partners.

    And it's not just 1 day, in 1752, the UK lost 11 days as a result of switching calendars. In 1918 Russia did the same trick, resulting in the loss of almost half a month!
  • And lastly we have a (possibly) 'more familiar' edge case which is the jump from standard time to DST and vice versa. Even whilst this is a more familiar edge case, it still has some weirdness going on for itself:
       - DST has no set in stone date when it applies, this varies per country and it's subject to change as well, meaning that countries can move the DST date forward or backward or even scrap DST all together.
       - In Morocco, they ignore the DST whilst the Ramadan is active this in order to help the Muslims to fast during the daylight.

Luckily a lot of the above edge cases have been 'solved' by brave developers who sacrificed their sanity for our gain by developing libraries (e.g. Carbon) or working on the core DateTime logic of a specific language (do use them, implementing this yourself is a road to insanity!). This vastly reduces the pain of working with timezones, however even then it still can cause funky behavior if you're not paying attention; for example:

DST changeover:

What if the date and time are 1:30 am on Nov 1st 2020, right when the US DST ends and the clock moves backwards?
1:30am occured twice that morning in that case, and how do you know which instance was intended?

Adding / removing to a date:

Imagine that you want to send a pre-scheduled e-mail to a customer in Samoa at 30 December 2011, since they skipped this day entirely, how can you guarantee that the e-mail does get send?

Outdated library:

What if Germany decides right now that it's going to move to UTC +12:15 within 2 weeks. How are you gonna ensure that all the scheduled tasks are being done at reasonable times?
If you're using an outdated library / solution, the internal 'edge cases' might not be updated yet to reflect this, meaning that you would have to implement a hack to cover this.

Mental complexity:

The complexity is not only present for developers, but it also affects other parts of the business, like meetings. Imagine that you work with a remote team in Nepal, you would always have to keep in mind that they are not living 'at' the same minute (due to their UTC +5:45 timezone).
How are you gonna coordinate and schedule meetings with them, since their entire concept of the hour differs from you?

And, as you can imagine, the list goes on and on...

Lets look at some solutions, at the data layer, which are commonly being deployed to aid against several of the before mentioned problems!

The 'solutions'

Solution 1: Store datetimes as UTC (timestamps)

One common way to handle the storage of datetimes is to save these as an UTC timestamp the moment data is persisted into the database. These timestamps are then converted on the fly to the timezone of the user.

Pros:
   - No need to deal with timezones in the data layer, since everything is in an uniform format which heavily simplifies things like querying.  
   - Many SQL providers provide out of the box features that aid in the saving of these timestamps. For example by having a dedicated TIMESTAMP datatype.
   - Fairly straightforward and simple to implement.

Cons:
   - Will cause complications in 2038, since the timestamp is then officially about to reach its limit.
   - Doesn't contain timezone specific information, thus if a user enters DST (after saving the timestamp), the saved timestamp will be temporarily off by 1 hour whenever it is retrieved.

Solution 2: Drop the time component from a datetime

Another possible 'solution' is to re-evaluate the feature request itself, this to see if it is truly necessary to store the time (along side the date). It is a valid question to ask, since it does simplify your system.  Out of all the solutions listed here, this is the 'simplest' solution, meaning that it also has the least support for handling time.

Pros:
   - You slightly reduce the scale of the issues with the timezones, the problem however still persists but just at a different scale.

Cons:
   - It is not always possible to get away with this, since you will lose information/precision this way which is not always an option.
   - The problem still persists, it's just less visible.

Solution 3: Store the timezone alongside the datetime

Another option is to store the timezone along with the datetime (e.g. timestamp) information. Doing this will allow you to calculate the correct date on the 'spot'. This is the solution that has the least chance to introduce the beforementioned bugs.

Pros:
   - This way, you save all the details meaning that you should be able to cover all the cases. Since you store the timezone now, you can even update all your data the moment a new timezone modification gets published.  
   - It even supports complex cases, like cases where time is based upon ethnicity.
   - Depending on the datatype that you use (e.g. DATETIME), you could circumvent the 'year 2038' problem that timestamps have.

Cons:
   - Requires a more complicated data model to be able to cope with this format. As a result, this will also (slightly) consume more disk space.
   - If you go for this option, you do have to be careful with the identifier that you use for the timezone. This because, as we've mentioned above, details like the abbreviation are not unique. You could however use the TZ database name listed here which should be an unique identifier.
   - If the code that you use for obtaining details about a timezone gets outdated, you can still run into bugs since you don't have the most recent information. A good write-up about this can be found here.

Conclusion

In the end it is a balance between disk space, time and the impact of possible bugs. So you should ask yourself the following questions:

  • What is the impact if one of these timezone bugs shows up?
  • Is it worth it to invest more time to reduce the risk of this bug showing up?

Based on that you could pick one of the solutions mentioned above. This StackOverflow post contains a good overview of tips and libraries on a per language basis.

I hope you've learned something new about timezones today! Thanks for reading my post and I'd be happy to hear any questions / feedback, just leave a comment down below!

Vasco de Krijger
View Comments
Previous Post

How to build and structure API's in Laravel (including authorization)

Success! Your membership now is active.