Thursday, June 20, 2013

Working with Timezones in Java

Timezones are confusing to say the least. Here are four things to keep in mind when working with timezones in Java.

1. Timezones are relative to UTC

UTC, which stands for "Coordinated Universal Time", is always the same no matter where on the planet you are. The term "GMT" is often used as well. While GMT is pretty much identical to UTC, it's not exactly the same. UTC is scientifically defined, while GMT is not.

A timezone is an offset from UTC. For example, my timezone is currently 4 hours behind UTC. However, a timezone's offset may change depending on the time of year. For example, my timezone is 4 hours behind UTC for half the year (daylight savings time) and 5 hours behind UTC for the other half of the year (standard time). This is why it's preferable to represent timezones using their IDs (such as "America/New_York", described in more detail below) instead of their offsets (such as "-0400"). An offset can change depending on the time of year, but a timezone ID encapsulates these offset variations.

Fun fact: You might have noticed that the letters in the acronym "UTC" don't match up with "Coordinated Universal Time". "UTC" actually arose as a compromise between the English version "CUT" (Coordinated Universal Time), and the French version "TUC" (Temps Universel Coordonné).

2. Date objects use UTC

Whenever you call the toString() method on a java.util.Date object (for example, by printing the object to the console), it will generate a string that looks something like this:

Thu Jun 20 13:23:52 EDT 2013

This might lead you to believe that this is the exact timestamp that the object is holding. Not necessarily. Internally, Date objects store their timestamps in UTC. When you call toString(), it converts the UTC timestamp to your JVM's default timezone.

3. Timezones are clothing

Think of timezones as just different sets of clothing a timestamp can wear. It can put on a sweater, a jacket, or a tuxedo, but underneath it all, it's still a UTC timestamp. To format a Date object using a timezone of your choosing, call the setTimeZone() method on the DateFormat class.

DateFormat formatter = new SimpleDateFormat("HH:mm Z");
TimeZone timezone = TimeZone.getTimeZone("Europe/Madrid");
formatter.setTimeZone(timezone);
System.out.println(formatter.format(new Date()));
//prints: 19:50 +0200

In the example above, I'm printing the current time in Madrid, Spain (and surrounding areas). The "+0200" part describes the timezone's offset from UTC at that moment in time. It shows that the timezone is 2 hours and 0 minutes ahead of UTC. To give another example of an offset, "-0430" would mean that the timezone is 4 hours and 30 minutes behind UTC.

You might be wondering where a list of these timezone identifier strings can be found. A source that I like to use is from the PHP user manual. But the official listing can be found in what's known as the TZ database.

4. The TimeZone class is quirky

Note that if an unrecognized timezone ID is passed into the TimeZone.getTimeZone() method, the method will return an object representing the "GMT" timezone. It would be less confusing if it just returned null, but who am I to complain.

TimeZone timezone = TimeZone.getTimeZone("Bogus/Timezone");
if ("GMT".equals(timezone.getID())){
  //timezone not found!
} else {
  //timezone found
}

2 comments:

Jason said...

"Think of timezones as just different sets of clothing a timestamp can wear. It can put on a sweater, a jacket, or a tuxedo" ... I like to think of my timezones as wearing a twead sweaters and double pleated trousers :) But seriously, thanks for making something so convoluted seem so simple.

Michael Angstadt said...

np