You might think this would be simple but I actually spent a fair
amount of time last week tracking down some confusing bugs. The problem
is that the official documentation is pretty sparse and if you Google
for support you will find many answers that are confused or flat-out
wrong. Hopefully this will provide the straight dope.
The key Java classes are as follows:
java.util.TimeZone
This is an abstract class that contains the definition of a time
zone, including that zone’s rules for Daylight Saving Time (or what
Europeans refer to as “Summer Time.”) Any code that needs to explicitly
deal with time zones should use a TimeZone object.
The best way to get a TimeZone is to call the static method
TimeZone.getTimeZone() and pass it the standard “Olson name” such as
“America/New_York” or “Pacific/Honolulu”.
This link lists all the standard time zone names.
You can call TimeZone.getTimeZone(“GMT”) or
TimeZone.getTimeZone(“GMT-5″). None of these GMT-based TimeZone objects
support Daylight Saving Time. (Note: “GMT” and “UTC” mean practically
the same thing. GMT is defined in terms of astronomical observations and
UTC is used for setting atomic clocks. For most practical purposes they
can be treated as identical.)
You can also use common abbreviations e.g.
TimeZone.getTimeZone(“EST”) instead of
TimeZone.getTimeZone(“America/New_York”). This is strongly discouraged.
Depending on circumstances you might get the wrong Daylight Saving Time
behavior or even the totally wrong time zone, since the same 3-letter
abbreviations are used around the world for different time zones.
When displaying a time zone to the user you should probably call
TimeZone.getDisplayName(). Depending on the parameters you pass this
will return a user-friendly value like “Eastern Standard Time”, “EST”,
“Eastern Daylight Time” or “EDT”.
java.util.Calendar
This is an abstract class which serves as a wrapper around two independent values:
- A time, stored as the number of milliseconds since January 1, 1970 00:000:00 GMT.
- A TimeZone object which indicates how the time should be displayed.
(This is an oversimplification. The actual implementation is a a bit
more complicated, but this is close enough as long as you are not
actually digging into the source code.)
Note that the time offset is always supposed to be in GMT. If you see
code samples that make a different assumption (and there are many out
there on the web) ignore them.
Time zone conversions are simple: if you call setTimeZone() on a
Calendar object you get the exact same time but displayed in the new
time zone.
A more complex problem occurs when you get a time string from a user
in a different time zone. If you parse the string “05-01-2012 08:35 AM”
then the parser will generally give you a Calendar object for 8:35 AM in
the computer’s default time zone.
If this is wrong then you will need to change the time offset to
convert it to the correct time. If the time string was supposed to be in
GMT then you can use the folowing code to convert it.
public static Calendar convertToGmt(Calendar c) {
java.util.Date date = c.getTime();
TimeZone tz = c.getTimeZone();
long timeInMilliseconds = date.getTime();
int offsetFromUTC = tz.getOffset(timeInMilliseconds);
Calendar gmtCal = new GregorianCalendar(TimeZone.getTimeZone("GMT"));
gmtCal.setTime(date);
gmtCal.add(Calendar.MILLISECOND, offsetFromUTC);
return gmtCal;
}
If it was supposed to be in a different time zone then you can call
TimeZone.getOffset() for both time zones. The difference between the two
values will give you the number of milliseconds that you need to add to
do the conversion.
This code provides an alternate way to convert between arbitrary time zones.
public static Calendar convertToNewTimeZone(Calendar calendar, TimeZone timezone) {
Calendar newCal = new GregorianCalendar(timezone);
newCal.setLenient(false);
boolean am = newCal.get(Calendar.AM_PM) == Calendar.AM;
newCal.set(Calendar.YEAR, calendar.get(Calendar.YEAR));
newCal.set(Calendar.MONTH, calendar.get(Calendar.MONTH));
newCal.set(Calendar.DATE, calendar.get(Calendar.DATE));
newCal.set(Calendar.HOUR, calendar.get(Calendar.HOUR));
newCal.set(Calendar.MINUTE, calendar.get(Calendar.MINUTE));
newCal.set(Calendar.SECOND, calendar.get(Calendar.SECOND));
newCal.set(Calendar.MILLISECOND, calendar.get(Calendar.MILLISECOND));
boolean ampm = calendar.get(Calendar.AM_PM) == Calendar.PM;
if (am && ampm) { // cal = 0 but we want 1
newCal.roll(Calendar.AM_PM, 1);
} else if (!am && !ampm) { //cal = 1 but we want 0
newCal.roll(Calendar.AM_PM, -1);
}
return newCal;
}
Once again, this gives you a Calendar object with the same wall-clock
time in a different time zone, as opposed to getting the same actual
time in a different time zone.
ISO 8601
To avoid such problems when sending dates and times between different time zones you can use the
ISO 8601 formats commonly used in XML documents. These formats allow an optional trailing time zone indicator e.g.
2012-05-01T08:35:01.123Z
2012-05-01T08:35:01.123-05:00
A “Z” code indicates that the time is GMT. A “-05:00″ indicates a
time zone that is 5 hours behind GMT. In the U.S. this could mean either
“Eastern Standard Time” or “Central Daylight Time”.
Most standard XML libraries can handle these formats.
In the “-05:00″ example above the parser will return a Calendar
subclass whose TimeZone object is “GMT-5″, not “America/New_York” or
“America/Chicago”. You have the correct time but you don’t really know
which official time zone it is.
The time zone indicator is optional. If the document contains
2012-05-01T08:35:01.123
that will be interpreted as being in the receiving computer’s default time zone.
java.util.Date
This is a wrapper around a count of milliseconds since midnight January 1, 1970. There is no associated time zone.
According to the documentation the millisecond count should always be
in GMT, but this is often ignored. You will find many code samples on
the web that attempt to deal with time zones by adding or subtracting
hours. This is NOT recommended.
If you need to deal with time zones you should use a Calendar object.
The Date class has methods like getHours() and getMinutes() which are
all deprecated. If you use them they will return the value in the
computer’s default time zone. Date.toString() will also display in the
computer’s default time zone.
java.sql.Date
This is intended to represent a SQL DATE field. The Java
implementation is a simple wrapper around java.util.Date which makes
sure that the time part is always set to midnight in your computer’s
default time zone.
What is actually stored in the database depends on the implementation
but can be assumed to consist of a year, month and day in some format.
java.sql.Time
This is intended to represent a SQL TIME field. The Java
implementation is a thin wrapper around java.util.Date which makes sure
that the date part is always set to January 1, 1970.
What is actually stored in the database depends on the implementation
but can be assumed to consist of either an offset from midnight or a
combination of hour, minutes and seconds in some format.
There is no support for time zones built in. If time zones are
important the application will have to keep track of them separately.
java.sql.TimeStamp
This intended to represent a SQL TIMESTAMP field. The Java
implementation is similar to java.util.Date in that it contains an
offset from a fixed starting time, but it is much higher precision,
supporting fractions of a microsecond instead of milliseconds.
What is actually stored in the database depends on the implementation but is usually some sort of offset from a starting date.
TimeStamp is similar to java.util.Date in that
- It does not contain a TimeZone.
- The internal offset is supposed to be in GMT and bad things can happen if it is not.
- It is typically displayed in the computer’s default time zone.
Usually you create a TimeStamp from a java.util.Date object. (If you
are starting with a Calendar object just call getTime() on it.) So
basically you are writing out the millisecond offset in the GMT time
zone. If you started with a Calendar its original time zone is lost.
When you read a TimeStamp from the database you are getting back the
time with no time zone information. Calling getDate() will return it as a
java.util.Date object. If you want it in a time zone other than your
computer’s default time zone you can do something like this:
Calendar cal = new GregorianCalendar(desiredTimeZone);
cal.setTime(ts.getDate);
http://bugfox.net/blog/2012/05/04/time-zones-and-daylight-saving-time-in-java/