A good way to avoid some of the pitfalls of time in computer software is to be explicit about timezones and to use a library that can handle timezones.

If your software is only used in a single timezone, you might think that timezones are not necessary to think about. But being aware of the timezone is necessary in more situations than what most people assume.

If both of these statements are true you probably need to use a timezone aware date-time library:

  • You need to validate a datetime that is not UTC
  • That non-UTC datetime is from a location that observes Daylight Saving Time (DST). For instance most of Europe and North America.

If you make software that only operates in a single timezone do you really need a timezone library? Most likely. If that timezone observes Daylight Saving Time it is very hard to validate any given point in time without it. To validate a datetime for a place that observes DST your code has to take DST into account.

The timezone aware datetime library is not just there to compare one timezone to another. It also knows about when DST happens in any timezone.

The following happens in countries that observe DST: In the spring, an hour (usually) is skipped when the clocks are set forward. That hour simply does not exist in “wall time”. So if your end users work with their local time and you want to make sure if the time they entered is valid, it is not enough to simply check if it is between 00:00:00 and 23:59:59.

02:30:00 on March 30th 2014 might be a valid time, but in some timezones it is not. With a timezone aware timezone library you can validate that.

Here is an example of doing that in Calendar, an Elixir date time library I designed. A “wall time” can be valid in one timezone but not in another:

DateTime.from_erl {{2014, 3, 30}, {2, 30, 00}}, "Europe/London"
{:ok, %Calendar.DateTime{day: 30, hour: 2, min: 30, timezone: "Europe/London" ...}}

DateTime.from_erl {{2014, 3 ,30}, {2, 30, 00}}, "Europe/Copenhagen"
{:error, :invalid_datetime_for_timezone}

Furthermore if the entered date and time is during a switch off of DST - usually in the autumn - the time will be ambiguous. So if you need to compare the entered datetime to other datetimes, you need to ask the user if it the entered datetime was before or after the clocks were set back.

DateTime.from_erl {{2014, 10, 26}, {2, 30, 00}}, "Europe/Copenhagen"
{:ambiguous, %Calendar.AmbiguousDateTime{possible_date_times:
  [%Calendar.DateTime{abbr: "CEST", std_off: 3600, ...},
   %Calendar.DateTime{abbr: "CET", std_off: 0, ...}
  ]}}

UTC and the user time zone

Even if all of the users of a system are in the same timezone, you probably want your server software to work with UTC. UTC does not have the ambiguity that other timezones have. If a user has entered a future datetime and wants to be alerted an hour before, you can compare the server time to the entered datetime and then issue an alert one hour before. But in order to compare UTC to the entered datetime, you need to know what the timezone is. And by timezone I do not mean simply an integer with the UTC offset on a certain date. Because UTC offsets can change. You want a timezone name from the tz database such as America/New_York.

Measuring the interval between two datetimes

Imagine that a user enters two datetimes: 2014 March 30, 01:00:00 and 2014 March 30, 04:00:00. You need to find out how much time there is between the two. Usually the difference would be exactly three hours. But for some timezones, the “spring forward” event, where the clocks are set forward one hour, took place between those two datetimes. For those zones the difference between the two datetimes is two hours, not three. That is quite a big difference.

So if your users, like most people, do not have their watch set to UTC, you would need to know which timezone the entered times belong to in order to do that calculation correctly.

Again, there are timezone libraries that can handle that.

iex> at_four = DateTime.from_erl!({{2014,3,30},{4,00,00}}, "Europe/Stockholm")
iex> at_one =  DateTime.from_erl!({{2014,3,30},{1,00,00}}, "Europe/Stockholm")
iex> DateTime.diff(at_four, at_one)
{:ok, 7200, 0}
# 7200 seconds is two hours in this case

In that example you do not really have to check if the timezone uses DST, and if so when it switches back and forth from DST. All you have to do is input the “wall time” and the timezone. The library will handle the rest.

Adding another timezone

So far the examples have that timezone awareness is necessary even if you have just one user that only uses one timezone. If you suddenly need your software to be used by people in more than one timezone, it is of course easier if your software has already taken timezones into account.

Timezone aware as the default

When I made Calendar, I made sure that the API was designed with timezone awareness as the default.

If you have a datetime and do not explicitly tell the library which timezone it belongs to, you cannot create a valid DateTime struct.

So you have to either provide a valid timezone name or use another module called NaiveDateTime. Naïve - it sounds a bit less confidence inspiring than just DateTime does it not? And it takes longer to type. That is on purpose. My hope is that the name alone makes people think twice before using it over DateTime. There are a few cases where you can get away with ignoring timezones and DST, but you really have to be sure you know what you are doing.

Working with date and time is not easy, but it is not all rocket science either. It is not a secret that DST means that hours are skipped and repeated. Programmers can ignore it and live with the consequences. Or use a library that can handle it.