Java has no standard classes for handling pure dates without times. It does
have the following classes:
- java.util.Date : for handling date/timestamps
presuming GMT. It is the lemon of Java. It is mostly replaced by
GregorianCalendar. It is used mainly as if it were a Long wrapper for a date/timestamp.
- java.util.GregorianCalendar : a date/timestamp and
timezone. The timezone is usually ignored.
- java.util.SimpleDateFormat : for converting dates to
and from human-comprehensible strings.
- java.util.TimeZone : represents a timezone. Watch the
capital Z. Watch out, Java does not name the timezones in the way Americans are
familiar with.
These are among the worst bits of code ever written. You can do quite a bit with
long timestamp = System.currentTimeMillis();
If you use pure longs, you don’t have to deal
with timezones, leap years, months with variable number of days, regional
differences, bulky objects, slow code and all the gotchas in the Date
and Calendar classes. SQL can’t screw up a long.
It surely can a SQLDate. Think hard to keep the bulk
of your application dealing with simple longs. Only
use Date, Calendar or CalendarFormat
when you need to input or output human-readable dates/times.
There are a ton of surprises and bugs awaiting the unwary users of these classes.
java.util.Date
Gotchas
The java.util.Date class is crawling with gotchas.
It is a disgrace. It is the lemon of Java and deserves a giant string of
raspberries. In JDK 1.2, Date has largely been
deprecated, and replaced by something even more complicated, Calendar/
GregorianCalendar.
The key to understanding Date is that it is not
a date class, but a timestamp class. Inside each date is stored the number of
milliseconds since 1970-01-01 UTC. It does not
record the timezone or the timestamp in local time. You choose the timezone when
you display the date with code like this:
With Date, you can’t have a date without a
time. Keep in mind that when you display the same Date with different timezones
you will sometimes get 1999-12-14, and sometimes 1999-12-25.
In contrast GregorianCalendar objects do contain a TimeZone
reference along with the date and time. Like Dates,
they are initialised with the current date and time. Unfortunately, if they are
not sure about the user’s timezone, the Sun classes quietly revert to GMT,
and in early versions of GregorianCalendar, seem to
revert to PST if you so much as breathe on them. DateFormats
also have TimeZone that is similarly erratic. For
code to work, your clients must have configured the TimeZone
correctly in the OS. You can’t help them configure it.
- The deprecated new Date(
String ) cannot be trusted to recognise timezone
TLAs such as EST.
- Inconsistent capitalization. java.util.TimeZone, but Date.getTimezoneOffset.
- Inconsistent naming. We have System.currentTimeMillis
but Calendar.getTime InMillis. Note also
that new Date objects are initialised to the current
date and time, not to a null value. This gives you two ways to get the current
time.
- Documentation is never clear on when you are using local and when UTC.
- Months are numbered starting at January=0, rather than 1 as everyone else on the
planet does. Yet days start at 1 as do days of the week with Sunday=1, Monday=2,…
Saturday=7. GregorianCalendar uses the same idiotic
convention. Be thankful for small mercies. DateFormat.parse
behaves in the traditional way with 1=January, though reputedly its isLenient
mechanism does not work.
- getFirstDayOfWeek return Sunday=1 in the USA and
Monday=2 in France.
- TimeZone.getOffset wants a Sunday=1-based day of
week, which it ignores other than sometimes to check validity. The bug only
shows up on Sundays with an InvalidArgumentException
in some JVMs if you pass it a Sunday=0-based dayOfWeek. Your bug can thus easily
leak through into production.
- Dates prior to 1970 are not handled in JDK 1.0 but are
handled in JDK 1.1 and later.
- Monday is day 2, (half expects Monday to be day 0, the other half expects it to
be day 1, only the folks at Sun expect 2.). This is not properly documented.
- For Date, year 0=1900, year 100=2000. This has been
fixed in Calendar. Be careful if you are using both Date and Calendar in the
same program.
- There is no reserved value for a null date.
- Dates are stored internally with a date and time in
GMT, and automatically converted to local time. If you are not careful a date
stored as Saturday can come back a day earlier or later.
- In JDKs before 1.1.7, DateFormat uses PST (Pacific
Standard Time) as the default not GMT or local time as you might expect. To make
it use local time you must do:
DateFormat.setTimeZone( TimeZone.getDefault() );
To get UTC, without any daylight saving correction, you can use:
DateFormat.setTimeZone( TimeZone.getTimeZone("UTC") );
You must configure your TimeZone property in your OS (for example, in the
Windows Control Panel) for TimeZone.getDefault
to work. The default timezone will be automatically configured in Java once you
configure the correct timezone in your OS. However, selecting the right timezone
is more than just selecting how many hours off GMT you are. You have to select
the correct set of rules for when daylight savings changes. Java does everything
internally in GMT and converts for display, so it is not enough just to manually
reset your PC clock in the spring and fall. The configuring process can fail for
exotic parts of the earth because the timezone tables either in the OS or Java
are imperfect. Unfortunately, if you actually live in the GMT timezone, Java
will think you want GMT standard time without any daylight savings changes. The
best solution to this I know so far when your timezone is not properly supported
is to roll your own TimeZone object with the proper
daylight savings switchover dates.
Alternatively, for applications, you can override the timezone on the java.exe
command line with something like this:
java.exe -Duser.timezone=Europe/London HelloWorld
Make sure the -D goes before the classname, or it will be interpreted as an arg
to your HelloWorld class. You may have to do this if the JDK does not properly
recognise the name of the timezone that your OS calls it. The names for
timezones used in Java comes from a list maintained at NIH by Arthur David Olson.
Unfortunately, the common names such as EST can mean
Eastern Standard Time: GMT+10 (in Australia)
Eastern Standard Time: GMT-5 (in the US)
so they are not suitable for international use. Pacific Time is called America/Los_Angeles
instead of America/PST. You have to get out an atlas to find out if you are
north of Los Angeles or South of Alaska. The idea is America/Los_Angeles means
PST/PDT, as appropriate. PDT would imply a daylight saving correction even in
winter. It would have made more sense to call it America
Pacific Time or America PST/PDT. America/Los_Angeles
only has meaning for Californians. When testing timezone-sensitive code, keep in
mind that the switchover between daylight and standard time does not happen at
midnight.
TimeZone: a list of available timezones, and how your machine in currently configured
- The people who wrote Date and Calendar
need to be sent to a class in English composition. The key to clarity is a
consistent vocabulary. They seem to think day, date, time
and timestamp are synonymous. They muddle them hopelessly. The Date
class is actually a timestamp, not a date. The method names are misleading. Date.getDay
gets you the day of the week. GregorianCalendar.get(Calendar.DATE)
or Date.getDate gets you the day of the month. Date.getTime
gets you a date/time stamp milliseconds since 1970, in Date,
but gets you a Date object in GregorianCalendar.
java.sql.Date and java.sql.Timestamp
are just thin wrappers around java.util.Date. As
such, you can interconvert using the getTime and setTime
methods with a long milliseconds since 1970 Jan 1
intermediary, or simply cast to Date. In JDK 1.4+ the Calendar.getTimeInMillis()
has been made public, so you can do it in one step. Date/Calendar needs to be
rewritten to use consistent vocabulary. Roman emperors and popes started the
calendar out in a rather confusing manner. Oracle uses Date
to mean a timestamp. C uses the January=0 convention. Sun authors had a failure
of nerve to break with Byzantine traditions of their predecessors.
- Dates and Calendars are
bulky. They are not a suitable way to carry around date information in RAM.
Happily, Date boils down to a Long when serialised
for external storage. There needs to be a minimalist core TimeStamp class, that
has no internal baggage, for storing raw TimeStamps. In the meantime, I
recommend you store them as longs or Longs.
- Within the Date class, even though the dates are
stored in GMT, they come out field by field in local time, e.g. when using getDay
and getMonth. there is a way to find out which
local time it is using with getTimezoneOffset, but
there is no way to set the timezone you want to use. You can set the default
local timezone when formatting, but it does not apply to your calculations based
on getMonth etc. The calculations you are doing may have nothing to do with
local time. To add insult to injury, if the client did not properly configure
the timezone, the calculations could have been quietly done relative to PST (early
JDKs) or GMT, in more recent JDKs. BigDate sidesteps
the entire issue by working with pure dates, no times no timezones.
- The list of possible timezones is incomplete and ambiguously defined. For
example, "BST" as a timezone refers to Bangkok Standard Time, not
British Summer Time. In Solaris, when you want to express your date in the MET
timezone (continental european timezone), you get GMT + 3h30 : Teheran time.
- DateFormat.parse is broken in JDK 1.2.1. It is fixed in 1.2.2.
- Oracle SQL databases expect dates stored to be presented in UTC, yet when you
retrieve them they automatically convert to local time. This is not what you
want. A servlet’s local time has no relation to the final client’s
local time. Store dates as longs or ints to avoid Oracle’s foolish
meddling.
One way out is to use my BigDate class
which handles dates from 999,999 BC to 999,999 AD. Sun has deprecated most of
the Date methods and replaced them with GregorianCalendar
(which knows the TimeZone unlike Date). Date is still
used for storing a date. GregorianCalendar would be
far too bulky. GregorianCalendar has not nearly as
many limitations as Date, it has got rid of those
ridiculous 1900-based years, however it is obscenely complicated, still relies
on the old Date class and maintains a lot of the Date lunacy such as 0-based
months. Happily the documentation in JDK 1.2 is better, though ambiguity whether
local or UTC parameters are wanted still plagues. Sun tends to be careless about
documenting units of measure. For example in early JDK’s you never knew if TimeZone
offsets were measured in milliseconds, seconds or minutes. getTimeZoneOffset
returns minutes, but GregorianCalendar.get(Calendar.ZONE_OFFSET)
returns milliseconds. See essay on
Dates and Calendars .
Learning More
Sun’s Javadoc on the
Date class : available: