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:
- Use the javap utility, which is part of J2SE, like so:
javap -private java.util.Calendar
- Examine the source code, which is available in the
src.jar
file included in J2SE - 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.
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.