I spend more time than I ever wanted over the last decade dealing with time zone related problems. Most of them were caused by bugs introduced by developers (including myself) who didn’t know enough about it.
This post is here to help with that: It’s not a complete coverage of time zones or an introduction. It’s based on my experience what the most common mistakes are and what is least understood. I will not cover specific languages or libraries: These are general informations, which apply to almost any environment.
Every location on earth has a time zone associated with it.
For example Berlin has “Europe/Berlin” and San Francisco has “America/Los_Angeles”.
Every time zone has an offset, which describes how one time zone relates to another. The offset of the time zone is normally expressed as difference to “UTC” (Greenwich). For example “America/Los_Angeles” has the offset “UTC-8” in the winter.
There is no global authority or formal process how a time zone is assigned. Every country decides for itself. But there is a time zone database maintained by the IANA, which documents all timezones: https://www.iana.org/time-zones. This database is the source for almost all libraries dealing with time zones.
One warning about the format: Time zones in the format
Etc/GMT are especially nasty:
Etc/GMT+10 is not
Because time zone rules can change at any time, the list of time zones should be updated every now and then (by updating the language/library used). But to be honest: The practical consequences of out-dated time zones rules are limited to use cases with rather exotic time zones. The most important time zones (US, Europe, China, India, Japan etc) don’t change very often.
Time zone vs offset
One common misconception is that the time zone is ‘UTC-XX’. Technically this might cause no bugs currently for some zones, which don’t use daylight saving time, but it’s still wrong.
For example San Francisco has “UTC-8” in the winter, but “UTC-7” in the summer. Using a fix offset instead of the name is wrong and will probably cause bugs.
Also just because “UTC+8” is the current fix offset for Beijing, it’s not guaranteed to be true tomorrow. As mentioned above, time zone rules are political decisions and can be changed at any time.
So please use a full time zone name whenever possible.
The two most common ways to express a time is the ISO-8601 format or the unix timestamp: (And of course there a countless custom variations)
Unix timestamp (in seconds:)
A few things about that:
It’s just a representation: Changing the offset for the ISO-8601 string doesn’t change the actual time. For example
2016-01-24T10:00:00Zis the same time as
The offset of the the ISO-8601 string is not a real time zone. As mentioned before, an offset is not enough. Just looking at such a representation doesn’t say anything about any time zone.
Again about the offset: To represent a specific point in time you need an offset. There is no real concept of time without an offset to UTC in practice (even if it is just offset
UTC). But in theory time has nothing todo with our notion of offset. This is just how we choose to communicate.
Often time representation and UI view concerns are mixed up: For example when a web app should display a time, it’s expected that the time, which comes from server, is represented with the offset in which it should be shown. This is not good: The information which exact time is meant is something completely different from how the time is shown to the user. The time zone in which a time is shown should always be stored separately.
The Unix timestamp includes a time zone: It’s the seconds (or milliseconds) elapsed since
01.01.1970in UTC. Using the unix format won’t fix magically time zone problems. I have heard it more than once: Just use “unix time”. No … just because the time zone is not directly visible, does not mean you can ignore it.
I recommend to use the ISO-8601 format over the unix timestamp. Because it’s human readable and includes a explicit time zone info, which makes it harder to ignore.
A time without an offset is not a real time
This might be the single biggest cause for time zone related bugs: Developers just forgetting about the time zone.
Technically a date or time without an offset is not a real time. With “real” time I mean a specific point in time (past or future). For example for
2018-11-20 it’s not clear if this is the day in Tokyo or in Paris. You have to add a offset.
What happens a lot is that developers just ignore or forget the think about the time zone. But then the library used to construct a date (or to print a date) assumes some kind of default time zone. Normally this the default time zone of the browser or the default time zone of the server.
I really think this a big API design flaw: There should never be a implicit time zone. I highly recommend always be explicit about the time zone, even if the default one is the right one. In pseudo code:
printNow(TimeZone.getDefault()) is just a lot better than
When you really don’t want a time zone
Sometimes a time zone is really not relevant. What this means is that it’s not important what specific point in time it really is. This is sometimes called a “Local Time” or “Local Date”.
For example a date of birth often does not need a time zone. It’s enough to know that somebody is born on
20.07.1980 and if this is
20.07.1980 in Spain or Japan is simply not relevant.
This concept should not be confused with “print something in local time”. For example the arrival time of a flight is always shown in local time, but it’s still a specific point in time and not a “Local Time”.
One important consequence is that a “Local Time” can’t be compared to a real time. The question if a “Local Time” is before or after an real time can’t be answered because it’s not the same thing. If such a comparison is needed, it’s not a local time.
The problems arise when it’s treated as a real time. Even if there are UI elements for the user (date/time pickers) to make it seem like a real time it should not saved in the same way. Instead I recommend to save a local time as a String.
If a real date is needed it should be converted locally and explicitly for every case and String should be used everywhere else including transportation and persistence.
If the persistence layer really needs a real date it also should be converted just before and after saving/loading. But it’s better to save a String if possible.
I have seen a lot of hacks and workarounds to fix time zone problems with something like “convert this here now to UTC” which where all caused by the mistake to save a local date as a real date.
Some libraries/languages have special “Local Date” support (e.g. Joda-Time in Java or Java 8 built-in). They can be useful but I still prefer to use a String. Because it’s likely that at one point it needs to be converted into JSON or saved in a DB or serialized in some format and than it will be converted into a real date object with some kind of default time zone without thinking about it.
Debugging time zone problems
Of course every environment or system is too unique to have concrete rules for finding time zone problems.
This all adds up and it can happen that it takes days to analyse and fix a time zone problem.
Time zone bugs are very hard to fix and should be avoided in the first place. The most important rule to achieve that is to make everything involving time zones very explicit.
Often the root cause is not enough attention and/or not enough experience/knowledge. I hope this text helps in with that.
Please comment on this article or reach out to me on twitter if you have feedback: @andimarek.