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 (Greenwich Mean Time). 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 time zone. The
time zone is usually ignored.
- java.util.SimpleDateFormat : for converting dates to and from
human-comprehensible strings.
- java.util.TimeZone : represents a time zone. Watch the capital Z. Watch out, Java does not name
the time zones 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 time zones, 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 (Standard Query Language)
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 (Coordinated Universal Time/Temps Universel Coordonné) . It does not record
the time zone or the timestamp in local time. You choose the time zone 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 time zones 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 time zone,
the Sun classes quietly revert to GMT and in early versions of GregorianCalendar, seem to revert to PST (Pacific Standard Time)
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 (Operating System). You can’t help them configure it.
- The deprecated new Date( String ) cannot be trusted to recognise time zone TLAs (Three Letter Acronyms) such as EST (Eastern Standard Time).
- 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 (Java Virtual Machines)
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 (Java Development Kit) 1.0 but are handled in
Java version 1.6 or later 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 (Java Development Kits)
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 time zone will be automatically configured in Java once you configure the correct time zone in your OS. However, selecting the right time zone 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 (Personal Computer) clock in the spring and fall. The
configuring process can fail for exotic parts of the earth because the time zone tables either in the
OS
or Java are imperfect. Unfortunately, if you actually live in the GMT
time zone, Java will think you want GMT
standard time without any daylight savings changes. The best solution to this I know so far when your time
zone is not properly supported is to roll your own TimeZone object with
Alternatively, for applications, you can override the time zone 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 time zone that your OS
calls it. The names for time zones used in Java comes from a list
maintained at NIH (Not Invented Here)
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 (Pacific Daylight Time)
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 time zone-sensitive code,
keep in mind that the switchover between daylight and standard time does not happen at midnight.
TimeZone: a list of available time zones 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 (Random Access Memory). 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 time zone you want to use. You can set the default local time zone 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 time zone, the
calculations could have been quietly done relative to PST
(early JDK s) or GMT, in more recent JDKs. BigDate sidesteps the entire issue by working with pure dates, no times no
time zones.
- The list of possible time zones is incomplete and ambiguously defined. For example,
BST (British Summer Time) as a
time zone refers to Bangkok Standard Time, not British Summer Time. In Solaris, when you want to express your
date in the MET (New York Metropolitan Opera) time zone
(continental European time zone), 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 .
New Date-Time classes
In Java 1.8 there are new Date-Time classes. They are thread-safe and immutable.
- java.time The core of the API (Application Programming Interface) for representing date and time. It includes
classes for date, time, date and time combined, time zones, instants, duration, and clocks. These classes are
based on the calendar system defined in ISO-8601 (International Standards Organisation # 8601 (date/time)), and are immutable and thread-safe.
- java.time.chrono The API for representing calendar systems other than the
default ISO-8601. You can also define your own calendar system.
- java.time.format Classes for formatting and parsing dates and times.
- java.time.temporal Extended API, primarily for framework and library writers,
allowing interoperations between the date and time classes, querying, and adjustment. Fields (TemporalField and
ChronoField) and units (TemporalUnit and ChronoUnit) are defined in this package.
- java.time.zone Classes that support time zones, offsets from time zones, and
time zone rules. If working with time zones, most developers will need to use only ZonedDateTime, and ZoneId or
ZoneOffset
Learning More
Oracle’s Javadoc on
Date class : available: