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 Java version 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 Java version 1.6 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
Alternatively, for applications, you can override the timezone on the java.exe command line with something
like
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 Java version 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
Date class : available: