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: