Time Zone Bugs: A Few Simple Rules to Never Write one Again
Whenever you pass a time or date, you must always explicitly pass a time zone. Because there are so many ways that time zone bugs can cause major issues in your system, it’s crucial to simply use the time zone and time math library that comes with your language. For instance, you might use something like Time in Ruby or time in Python. Don’t roll your own.
Here’s, quite literally, the long and the short of why:
The Short of It
Here’s the Golden Rule of Programming with Time Zones: whenever you pass a time or date around your system, you must pass it with a time zone.
- When the client makes a request to the server that includes a time or date, it must include the time zone. In most cases, the time zone should be the client’s time zone.
- When the server passes times or dates from method to method, it must always pass a time zone aware time or date.
- When you store the time or date in the database, you must store it with the time zone.
- When the server passes a time or date back to the client, it must pass the time zone along with it.
- This should be obvious, but you should never “fudge” time zones when you are missing one. If you don’t have a time zone where you need one (e.g. wherever you have a time or date), fix the caller. Don’t just make something up or substitute the server’s default time zone.
If you do this consistently and never cheat, you will not have time zone bugs due to time zone offsets.
The Long-winded Explanation
If the only thing you take away from this are the rules above, that is fine. If you want a longer explanation of why this works, here’s more detail:
Why are time zones so hard?
Time zones are hard because we are not used to thinking about times with time zones. We just think of local times and dates. You probably think “it is 2pm” a lot more than you think “it is 2pm Central Daylight Time, which means it is 7pm UTC and 10pm in Helsinki,” etc. Nonetheless, those other ways of stating the time are equally true.
“It is 2pm” is a useful shortcut for day-to-day life; but it is a relative, not absolute, measurement. Like all relative measurements, you can only draw useful comparisons if you know the baseline.
If I drive 2 miles east, and you drive 5 miles east, which one of us is farther east? We don’t know, because we don’t know where each of us started. When you send around times without time zones, that is the math you are trying to do.
In most cases, you must consider three time zones.
In a client-server application, you are usually dealing with three time zones:
- The client’s time zone
- The server’s time zone
The user, whether they are thinking about it or not, is entering their local time. If I schedule an appointment on my doctor’s website, I do so in Central Time, because that is the time zone where I live and where my doctor’s office is.
The server for my doctor’s office could be in a different time zone. Let’s say that it is in California and uses Pacific Time. When I say I want an appointment at 2pm, I mean 2pm CT. For the server to assume I mean 2pm PT is an error (because 2pm PT is 4pm CT).
To further complicate this, there is UTC (Coordinated Universal Time). UTC is the lingua franca of time zones for internet services. Any third-party service my application connects to probably uses UTC. Let’s say my doctor charges my credit card for my appointment at my appointment time, and I can cancel until that time.
Let’s say the server issues a request to charge at 2pm to the processor, but leaves out the time zone. Let’s say the processor accepts the charge and assumes UTC. I will get charged 5 or 6 hours early (depending on daylight-saving time) and lose my ability to cancel early.
By the way, your server might happen to use UTC, but you should not assume so in your code. If you assume UTC, and you or your dev ops team switch you to a non-UTC server, you are in for a world of hurt.
Dates need time zones too.
A common misunderstanding I hear from programmers is that times need time zone information, but dates don’t.
This is wrong. Consider this: as I am writing, it is 5/17 in New Orleans. It is also 5/18 in Sydney. If my hypothetical appointment server is in Sydney, I’m in New Orleans, and the programmer doesn’t send time zones along with the dates, the application could book my appointment on the wrong day.
Let’s talk about daylight-saving time.
Because of daylight-saving time, the UTC offset for the same location can be different from day to day. Sometimes my office is UTC–5, sometimes it is UTC–6.
The easy way to deal with this is to use your language’s built-in time zone descriptors and never store or try to do your own math with UTC offsets.
A word about development environments, time zones, and bugs that only happen in production
Many applications use local time (e.g. what your laptop’s clock says) when in “development mode.” This means as you test on your machine, the time zone of your client is probably the same as your server. This hides time zone bugs until you ship to production and have users around the world. I recommend setting up your server to use a different time zone than your system clock.
Never roll your own date math
Always use a well-tested library for time and date math. If people use your language for any serious work at all, it either ships with a good date library or the community will have created one. Use it.
I know, you are just doing this one simple thing. No reason to use a big library for this. Consider this: what time is it 1 minute after 1:59am? Most of the time, it is 2:00am. Sometimes, it is midnight (when you “fall back” from DST). Sometimes the day after 2/28 is 3/1. Sometimes it is 2/29. This also means that sometimes you can find “this day next year” by adding 365 days, but sometimes you can’t. Do you know the rules for leap years? If you only said “a leap year happens when the year is divisible by 4”, with no other caveats, you are wrong.
Leave it to the experts – they built those libraries for a reason.