A lesson on time zones.
May 3, 2021
I had an unfortunate moment this morning. It started when I turned up to take a developer certification exam for a certain popular communications platform. To ensure there’s no cheating, they use a third-party who proctors the exam. Essentially they watch over a webcam and screen recording software as you take the exam.
When I turned up to take my exam, this proctor maintained that New Zealand time was an hour in the future. They said that I had turned up an hour late and was therefore a “no-show”.
As I have some time while I work this out with them, I’m going to explain the pernicious software bug that caused it. I’ll also explain how you can avoid making the same mistake in your own code.
Fifteen minutes before the exam was due to start, I logged in to prepare, and was confronted with the following message:
Strange. I double-checked my confirmation email, to make sure the date and time were correct:
They were. Concerned, I reached out to support. The exchange went something like this:
Followed by half an hour of slowly establishing that the time was in fact, 9:54 AM in Auckland. (Although by the time we established this it was more like 10:20 AM.)
As a software engineer, I recognised the problem straight away. Here’s my time zone settings:
Auckland, after daylight savings, is currently UTC+12:00. That means it’s 12 hours ahead of Coordinated Universal Time (UTC).
So the first issue is an obvious bug: the “Auckland, Wellington” time zone offset is incorrect.
The second issue is more subtle, and has to do with storing events in the future. When dealing with times in the past, it’s usually safe to normalise the time and store it in UTC. However, when storing dates in the future, you should never store UTC values.
Read on to learn why.
How they probably store meeting times
Here’s the interface for scheduling the exam:
You pick a date and a time in your time zone. You set your time zone like this:
The failure here is two-fold:
- Auckland time is not currently UTC+13:00 (it’s UTC+12:00)
- They probably store the time zone offset (UTC+13:00) instead of the time zone (Pacific/Auckland)
The result is a broken calculation:
- Take the given date and time (29 April 2021, 10 AM)
- Use the time zone offset to figure out the UTC date (subtract 13 hours)
- Store the UTC date
Here’s one way you could do this in Ruby:
Here’s a simplification of the database schema they likely use:
What’s the problem with this?
Offsets ≠ time zones
Offsets are not the same thing as time zones. A time zone’s offset can change through daylight savings shifts. “Auckland time” is sometimes UTC+12:00 and sometimes UTC+13:00.
Daylight savings rules can change unexpectedly
This may seem surprising, but daylight savings can change with little notice. Egypt is notorious for this: they have decided whether to scrap daylight savings days before it’s due to change.
Suppose you were scheduling lunch in Cairo next week. You convert the planned meeting time to UTC time based on an expectation that daylight savings will be observed. The next day, the Egyptian government decides that, in fact, they won’t hold daylight savings this year. Now, your time is incorrect and your lunch companion is left forlornly staring at the menu for an hour.
Removing the time zone destroys information
The simple test for this is that you cannot restore the time zone from a UTC-normalised date. Time zones are not simple additions or subtractions to UTC. Converting future dates to UTC destroys important information, introducing bugs around daylight savings and offsets.
How they should have stored times
First, the time zone setting. Rather than store the UTC offset, they should have stored the name of the time zone. The value I would use for this one from the list of IANA time zones (also known as the tz database):
But it would be even better if they could store the location of the event.
(Note that abbreviations are not suitable because they aren’t unique. Someone who lists their timezone as “CST”, for example, could be referring to Central Standard Time, China Standard Time, Australian Central Time, or Cuba Standard Time. It all depends who you’re asking.)
Here’s a better schema than the one above:
We can now figure out the time using the most up-to-date daylight savings rules.
Notice the correct time zone offset,
In SQL, with Postgres'
- Don’t store future dates in UTC (past dates are okay)
- Store IANA time zones along with the local time
- Never underestimate the complexity of working with time