Monday, 10 September 2012

About Java TimeZone And Calendar Caveats

Learning the proper usage of the Java Calendar class can be challenging, especially when dealing with dates belonging to different time zones. This post describes common caveats. The code examples are available from Github in the TimeZone-And-Calendar-Caveats directory.

Avoid Using Calendar.getInstance()

In order to create a Java Calendar, one can be tempted to use the getInstance() method. The issue with this method is that is delivers a calendar with the default time zone and locale. If an application is installed on multiple PCs, this means different default time zones and locales (unpredictable).

One is better off using Calendar.getInstance(TimeZone tz, Locale l) or Calendar.getInstance(TimeZone tz). Do set a time zone, for example: TimeZone.getTimeZone("GMT+0").

Use Gregorian Calendar Instead Of Calendar

Java's GregorianCalendar handles complicated cases of modern calendars dates. Use them over Calendar whenever possible. A GregorianCalendar should be created with a time zone to avoid unpredictable defaults. For example:

  new GregorianCalendar(TimeZone.getTimeZone("GMT+0"))

Java Date Doesn't Know About Time Zones

Thanks to Peter Lawrey on StackOverflow, I was reminded that the Java date class object does not know about time zones. It only contains 'the specified number of milliseconds since the standard base time known as "the epoch", namely January 1, 1970, 00:00:00 GMT'.

Hence, when one retrieves a date from a calendar with getTime(), this date will be relative to the Greenwich Mean Time (GMT+0) time zone, not the time zone set in the Calendar object. This date can be used to set a date in another Gregorian calendar.

For example:
public static void usingGetInstance() {

    TimeZone tz = TimeZone.getTimeZone("GMT+2");

    SimpleDateFormat sdf
        = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss SSS z");
    sdf.setTimeZone(tz);

    GregorianCalendar gcal1 = new GregorianCalendar(tz);
    gcal1.setTimeInMillis(System.currentTimeMillis());

    System.out.println(sdf.format(gcal1.getTime()));
    Date retr = gcal1.getTime();

    GregorianCalendar gcal2 = new GregorianCalendar(tz);
    gcal2.setTime(retr);

    System.out.println(sdf.format(gcal2.getTime()));

}
The generated output is something like:
2012-09-10 16:12:09 764 GMT+02:00
2012-09-10 16:12:09 764 GMT+02:00

SimpleDateFormat Knows About Time Zones

Java's SimpleDateFormat class knows about time zones, since it relies on a Calendar object under the hood. Unfortunately, this calendar is initialized with the default host TimeZone. In other word, its value depends of the host running the application. It is pretty much unpredictable, unless it is set explicitly.

One can print the date contained in a calendar using its time zone with:
public static void printingCalendarDate() {

    TimeZone tz = TimeZone.getTimeZone("GMT+5");

    GregorianCalendar gcal1 = new GregorianCalendar(tz);
    gcal1.setTimeInMillis(System.currentTimeMillis());

    SimpleDateFormat sdf
        = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss SSS z");

    // Setting the calendar's time zone
    sdf.setTimeZone(gcal1.getTimeZone());

    System.out.println(sdf.format(gcal1.getTime()));

}

Converting A Date Between Time Zones

The following explains how to proceed:
public static void fromTimeZoneToTimeZone() {

    GregorianCalendar gcal1 = new GregorianCalendar(
        TimeZone.getTimeZone("GMT+5"));
    gcal1.setTimeInMillis(System.currentTimeMillis());

    GregorianCalendar gcal2 = new GregorianCalendar(
        TimeZone.getTimeZone("GMT-6"));
    gcal2.setTime(gcal1.getTime());

    SimpleDateFormat sdf
        = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss SSS z");

    sdf.setTimeZone(gcal1.getTimeZone());
    System.out.println(sdf.format(gcal1.getTime()));

    sdf.setTimeZone(gcal2.getTimeZone());
    System.out.println(sdf.format(gcal2.getTime()));

    System.out.println(gcal1.getTime().getTime());
    System.out.println(gcal2.getTime().getTime());

}
The generated output is:
2012-09-10 20:25:19 720 GMT+05:00
2012-09-10 09:25:19 720 GMT-06:00
1347290719720
1347290719720
Although the displayed formatted date varies according to the time zone, the internal date (relative to GMT) is the same.

About Calendar Constants

The Java Calendar class defines several constants. The gotchas are:
  • HOUR operates between 0 and 1, HOUR_OF_DAY operates between 0 and 23.
  • DAY_OF_WEEK starts with Sunday=1, Monday=2, etc...
  • MONTH starts with January=0, February=1, etc...
  • ...but DAY_OF_MONTH starts with 1, 2, 3, etc...