What's your time zone?

Get a handle on Java date and time classes

Are you struggling with dates and times in your Java programs? When you display date and time data on the computer screen, is it an hour behind what it should be? Or maybe it's an hour ahead, or two hours behind, or worse? When you try to write dates and times to files—or to your database (via Java Database Connectivity (JDBC))—from your Java programs, is the wrong time saved?

I was plagued by these problems for a long time. I couldn't figure out why Java changed the timestamps I gave it. I selected timestamp data from the database and displayed it in my graphical user interface (GUI), where, lo and behold, it would show a different time—one, two, or three hours different from what I expected. I rechecked the value in the database, and it was correct. What on earth was going on?

The investigation

Eventually I decided to investigate this situation. First, I wrote a simple Java class:

 import java.util.*;
public class DateTest {
  public static void main(String[] args) {
    System.out.println("Date = " + new Date());
    System.out.println("Calendar = " + Calendar.getInstance());
  }
}

On Windows 98 with Java 2 Platform, Standard Edition (J2SE) 1.3.1_01, I got:

 Date = Tue May 06 08:13:17 IDT 2003
Calendar = java.util.GregorianCalendar[time=1052197997184,areFieldsSet=true,areAllFieldsSet
=true,lenient=false,zone=java.util.SimpleTimeZone[id=Asia/Jerusalem,offset=7200000,
dstSavings=3600000,useDaylight=true,startYear=0,startMode=1,startMonth=3,startDay=9,
startDayOfWeek=0,startTime=3600000,startTimeMode=0,endMode=1,endMonth=8,endDay=24,
endDayOfWeek=0,endTime=3600000,endTimeMode=0],firstDayOfWeek=1,minimalDaysInFirstWeek=1,
ERA=1,YEAR=2003,MONTH=4,WEEK_OF_YEAR=19,WEEK_OF_MONTH=2,DAY_OF_MONTH=6,DAY_OF_YEAR=126,
DAY_OF_WEEK=3,DAY_OF_WEEK_IN_MONTH=1,AM_PM=0,HOUR=8,HOUR_OF_DAY=8,MINUTE=13,SECOND=17,
MILLISECOND=184,ZONE_OFFSET=7200000,DST_OFFSET=3600000]

On Sun Solaris 7 with J2SE 1.3.1_02, I got:

 Date = Tue May 06 08:13:17 IDT 2003
Calendar = java.util.GregorianCalendar[time=1052197997184,areFieldsSet=true,areAllFieldsSet
=true,lenient=false,zone=java.util.SimpleTimeZone[id=Asia/Jerusalem,offset=7200000,
dstSavings=3600000,useDaylight=true,startYear=0,startMode=1,startMonth=3,startDay=9,
startDayOfWeek=0,startTime=3600000,startTimeMode=0,endMode=1,endMonth=8,endDay=24,
endDayOfWeek=0,endTime=3600000,endTimeMode=0],firstDayOfWeek=1,minimalDaysInFirstWeek=1,
ERA=1,YEAR=2003,MONTH=4,WEEK_OF_YEAR=19,WEEK_OF_MONTH=2,DAY_OF_MONTH=6,DAY_OF_YEAR=126,
DAY_OF_WEEK=3,DAY_OF_WEEK_IN_MONTH=1,AM_PM=0,HOUR=8,HOUR_OF_DAY=8,MINUTE=13,SECOND=17,
MILLISECOND=184,ZONE_OFFSET=7200000,DST_OFFSET=3600000]

And on Linux Mandrake 7.2 with J2SE 1.3.0, I got:

 Date = Mon May 05 21:04:32 GMT+00:00 2003
Calendar = java.util.GregorianCalendar[time=1052168673155,areFieldsSet=true,areAllFieldsSet
=true,lenient=true,zone=java.util.SimpleTimeZone[id=Custom,offset=0,dstSavings=3600000,
useDaylight=false,startYear=0,startMode=0,startMonth=0,startDay=0,startDayOfWeek=0,
startTime=0,startTimeMode=0,endMode=0,endMonth=0,endDay=0,endDayOfWeek=0,endTime=0,
endTimeMode=0],firstDayOfWeek=1,minimalDaysInFirstWeek=1,ERA=1,YEAR=2003,MONTH=4,
WEEK_OF_YEAR=19,WEEK_OF_MONTH=2,DAY_OF_MONTH=5,DAY_OF_YEAR=125,DAY_OF_WEEK=2,
DAY_OF_WEEK_IN_MONTH=1,AM_PM=1,HOUR=9,HOUR_OF_DAY=21,MINUTE=4,SECOND=33,MILLISECOND=155,
ZONE_OFFSET=0,DST_OFFSET=0]

As you can see, the Calendar class seems to have a class member that is a java.util.SimpleTimeZone instance. I can confirm this in several ways:

  1. Use the javap utility, which is part of J2SE, like so:
     javap -private java.util.Calendar
    
    
  2. Examine the source code, which is available in the src.jar file included in J2SE
  3. Use Java's reflection mechanism

In any case, you will discover that the java.util.Calendar class has a private instance member named zone that is a java.util.TimeZone instance, as this part of javap's output shows:

 private java.util.TimeZone zone

When I try the same trick with the

java.util.Date

class, you can see it has the following instance member:

 private transient java.util.Calendar cal;

This means that, indirectly, the Date class also has a TimeZone member.

However, the Javadocs tell us that TimeZone is an abstract class, while SimpleTimeZone is a concrete subclass. Therefore, despite the member definition, the zone member in Calendar is actually a SimpleTimeZone instance (in J2SE 1.3). This can be easily confirmed by investigating the TimeZone class using the methods described above. Indeed, the zone member in Calendar is a SimpleTimeZone instance. Examining the DateTest class's output, it looks like TimeZone has attributes that relate to Daylight Saving Time (DST), namely the following attributes:

  • dstSavings
  • useDaylight
  • startYear
  • startMode
  • startMonth
  • startDay
  • startDayOfWeek
  • startTime
  • startTimeMode
  • endMode
  • endMonth
  • endDay
  • endDayOfWeek
  • endTime
  • endTimeMode

So as you can see, the Date and Calendar classes have a notion regarding Daylight Saving Time. When I started investigating this, it was summer (last year), and in order to adjust for DST on all our servers, we physically moved the system clocks forward one hour. Therefore, I figured that Java wouldn't make adjustments for DST.

First try at a solution

Based on information in the Javadocs and on the DateTest class's output, I figured my best bet was to set the default time zone when the JVM first started. To that end, I created an Initializer class I could run when my application launched. Here is my first effort:

 import java.util.TimeZone;
import java.util.SimpleTimeZone;
public class ItsInitializer {
  private static boolean s_initialized = false;
  private ItsInitializer() {
  }
  public static synchronized void initialize() {
    if (!s_initialized) {
      // Modifies the default time zone, disables the Daylight Saving Time.
      SimpleTimeZone dtz = (SimpleTimeZone) TimeZone.getDefault();
      dtz.setStartRule(0,0,0,0);
      dtz.setEndRule(0,0,0,0);
      TimeZone.setDefault(dtz);
      s_initialized = true;
    }
  }
}

In other words, I change the JVM's default TimeZone so it doesn't have DST rules and won't try to make an adjustment. (Its in the code above is an abbreviation for my employer's name, InterSystems.)

Then J2SE 1.4 came out and I upgraded. Bang! ItsInitializer no longer worked. I immediately investigated. Here is the DateTest class's output for J2SE 1.4.1_01 on Windows 98:

 Date = Tue May 06 05:31:03 IDT 2003
Calendar = java.util.GregorianCalendar[time=1052188263870,areFieldsSet=true,areAllFieldsSet
=true,lenient=true,zone=sun.util.calendar.ZoneInfo[id="Asia/Jerusalem",offset=7200000,
dstSavings=3600000,useDaylight=true,transitions=143,lastRule=java.util.SimpleTimeZone
[id=Asia/Jerusalem,offset=7200000,dstSavings=3600000,useDaylight=true,startYear=0,
startMode=1,startMonth=3,startDay=1,startDayOfWeek=0,startTime=3600000,startTimeMode=0,
endMode=1,endMonth=9,endDay=1,endDayOfWeek=0,endTime=3600000,endTimeMode=0]],
firstDayOfWeek=1,minimalDaysInFirstWeek=1,ERA=1,YEAR=2003,MONTH=4,WEEK_OF_YEAR=19,
WEEK_OF_MONTH=2,DAY_OF_MONTH=6,DAY_OF_YEAR=126,DAY_OF_WEEK=3,DAY_OF_WEEK_IN_MONTH=1,AM_PM=0,
HOUR=5,HOUR_OF_DAY=5,MINUTE=31,SECOND=3,MILLISECOND=870,ZONE_OFFSET=7200000,
DST_OFFSET=3600000]

As you can see, the zone member in class TimeZone in J2SE 1.4 is no longer a SimpleTimeZone instance. The sun.util.calendar.ZoneInfo class has replaced it. Thus, I needed to change ItsInitializer to be compatible with J2SE 1.4:

 import java.util.TimeZone;
import java.util.SimpleTimeZone;
public class ItsInitializer {
  private static boolean s_initialized = false;
  private ItsInitializer() {
  }
  public static synchronized void initialize() {
    if (!s_initialized) {
      // Modifies default time zone, disables Daylight Saving Time.
      TimeZone l_defaultTimeZone = TimeZone.getDefault();
      int l_rawOffset = l_defaultTimeZone.getRawOffset();
      String l_id = l_defaultTimeZone.getID();
      SimpleTimeZone l_simpleTimeZone = new SimpleTimeZone(l_rawOffset,
                                                           l_id,
                                                           0,
                                                           0,
                                                           0,
                                                           0,
                                                           0,
                                                           0,
                                                           0,
                                                           0);
      TimeZone.setDefault(l_simpleTimeZone);
      s_initialized = true;
    }
  }
}

I create a new SimpleTimeZone instance that doesn't have a DST rule and assign it as the JVM's default TimeZone. The second implementation is better than the first because it assumes nothing about the actual Class of the zone member in class Calendar. Note that this second version is also backward compatible—it works well with J2SE 1.3. There's nothing like learning from experience, and I was going to gain even more experience before I finally resolved this issue.

I figured that during Daylight Saving Time, we always physically adjust the computer clock, so we never need to adjust for DST, and therefore always set the default time zone in the JVM without DST rules. Problem solved.

We happily continued developing our applications using the above strategy, and everything was fine. No more date and time discrepancies. When winter came and we physically readjusted our computer clocks back again, we still didn't experience problems with incorrect dates and times in our application, thanks to the good old ItsInitializer class.

But, as I discovered later on, I had made another mistake, and it was inevitably going to come back and bite me.

An unforeseen obstacle

Winter passed and summer came again, and with it, naturally, came Daylight Saving Time. No problem, I thought. Been there, done that.

On the very day we moved to DST, I got timestamp discrepancies. What happened?

This year, we didn't physically adjust our computer clocks. We had configured our Sun computer so that we didn't need to physically adjust the system clock. date is the Unix/Linux command for displaying the current system date and time. date's Solaris version uses timezone configuration files that help it to automatically adjust the display for DST— similar to how Java's Calendar class works. So if the timezone configuration files are installed, you don't need to physically adjust your system clock. I believe Linux behaves in a similar way.

Also, our Windows machines were now running Windows XP, and here again, we didn't physically adjust the system clocks, since you can configure the time zone in Windows XP as well. The problem is some time zones have the option of automatic daylight savings adjustment and some don't, as shown in Figures 1 and 2 below.

Figure 1. Time zone with automatic DST adjustment
Figure 2. Time zone without automatic DST adjustment

Rather than use our "real" time zone (Jerusalem), we used a time zone with the same difference from Greenwich Mean Time (GMT) as us, but with automatic DST adjustment capability, namely Helsinki, Kyiv, Riga, Sofia, Tallinn, Vilnius.

On the Solaris operating system, you configure your locale data, including your time zone, in the /etc/TIMEZONE file. Our /etc/TIMEZONE file contains:

 TZ=Israel

Israel is the name of a file in directory /usr/share/lib/zoneinfo/Asia. The /usr/share/lib/zoneinfo directory contains all the time zone data for all the different time zones in the world. This data is divided into files where each file has the data for a specific world region. Israel is the file with the time zone data for Israel. This file contains the DST rules: when DST starts each year, when it ends, and by how much to adjust the clocks. In Israel, as in other places around the world, DST begins at a different time each year. This is partly due to the fact that we use the Hebrew calendar (as well as the Gregorian calendar), and partly due to Israeli politics. In any case, up-to-date time zone information for different world regions is available on the Internet. One such Website is twinsun.com. Note that these files are data files and not text files. You cannot open them in your favorite text editor or word processor and study their contents. However, for those interested, some Websites have the source data from which the time zone data files are created (see Resources). There is also an open source Java class that can read and interpret the time zone data files' content (see the Java Notes Website).

Time zones and Java

Java is similar to Solaris when it comes to time zone information. A zone ID identifies each time zone. This ID is a String, and in J2SE 1.3 and 1.4, the tzmappings file, which is located in the J2SE installation's jre/lib subdirectory, stores the list of IDs. J2SE 1.3 only contains the tzmappings file, but J2SE 1.4 also contains the actual time zone data files for various world regions. The jre/lib/zi subdirectory stores these files. In J2SE 1.4, the sun.util.calendar.ZoneInfo class gets its DST rules from these files. Also, as with Solaris, these time zone data files are binary data files, not text files, so you can't look at them. Note that the time zone data in J2SE 1.4 is different from that in Solaris.

1 2 Page
Recommended
Join the discussion
Be the first to comment on this article. Our Commenting Policies
See more