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.

The source code for the java.util.TimeZone class's getDefault method shows it eventually invokes the sun.util.calendar.ZoneInfo class's getTimeZone method. This method takes a String parameter that is the ID for the required time zone. The default time zone ID is obtained from the user.timezone (System) property. If the user.timezone property is not defined, it tries to get the ID using a combination of the user.country and java.home (System) properties. If it doesn't succeed in finding a time zone ID, it uses a "fallback" GMT value. In other words, if it can't figure out your time zone ID, it uses GMT as your default time zone.

Note that the System properties are initialized in the java.lang.System class's initProperties method. This is a native method, so the source code is unavailable—unless you want to dig into the native code libraries that come with the J2SE distribution. However, I believe that the System properties are initialized from the Windows registry on Windows systems and from environment variables on Linux/Unix systems. The initProperties method's Javadoc claims that certain properties are "guaranteed to be defined" and lists them. Of the three System properties used by the java.util.TimeZone class's getDefault method, only java.home is listed as a guaranteed property in the Javadoc.

The (proposed) solution

So how do you ensure Java gives you the correct date and time? In my opinion, the best way is to make sure the JVM's default TimeZone class is correct and suitable for your locale. How you ensure the default TimeZone is correct and suitable is a different question. Like most problems in computing, this one has several possible solutions. According to the source code for the java.util.TimeZone.getDefault method, the best way is to set the user.timezone property correctly. You can easily override the value set in the java.lang.System.initProperties method by using the -D command-line option when launching your JVM, for example:

 java -Duser.timezone=Asia/Jerusalem DateTest

This command launches the DateTest class (listed at the beginning of this article) and sets the user.timezone property to Asia/Jerusalem. You can also set the user.timezone property by using the java.lang.System class's setProperty method:

 System.setProperty("user.timezone","Asia/Jerusalem");

If none of the available time zone IDs is suitable for you, then you can create a custom TimeZone and set it as the default time zone using the java.util.TimeZone class's setDefault method—as I did in my ItsInitializer class previously listed in this article.

Remember, most time- and date-related classes in J2SE contain time zone information, including the formatting classes, like java.text.DateFormat, so they are all affected by the JVM's default time zone. While you can ensure correct time zone information for these classes when you create instances of them, it's easier to set the default time zone for the entire JVM once, which ensures that all these classes will use the same default time zone. But, as they say in the classics, "your mileage may vary" (YMMV).

So get to it and tame those date/time Java classes!

Avi Abrami works as a senior software engineer at InterSystems, a company that develops airport information systems in Java using J2SE/J2EE. He has been writing software since 1989, and writing in Java since 1999. He has a bachelor's degree in education and a graduate diploma in computer studies. He is married with three children (and a dog) and lives in Israel.

Learn more about this topic

Join the discussion
Be the first to comment on this article. Our Commenting Policies