You are on page 1of 4

Handling dates in Java

May 11, 2011 Tero Kadenius


Inspired by a dzone article I decided to take a stab on this old issue which I’ve had to tackle quite a few
times over the years.
The date/time API in Java is notoriously painful to work with. Not to make it any easier, there are also
some things that are generally not very well understood about it. Perhaps the biggest misconception is
that Date objects contain time zone and DST information. They do not. Date in Java is only a glorified
long. Ie. it is a wrapper for milliseconds elapsed since 00:00:00 UTC on 1 January 1970, aka the epoch.
Understanding this makes reasoning about time in Java a lot easier.
Okay, so Date is only a bunch of milliseconds. What if I need the representation of some arbitrary date?
Do I calculate it from the epoch? How about printing a date? No worries, DateFormat is here to help.
The API documentation states that “DateFormat is an abstract class for date/time formatting subclasses
which formats and parses dates or time”. DateFormat needs a date pattern which it uses for parsing and
formatting. Once provided, using DateFormat is pretty straight-forward.
1 //parsing a date from string
2 DateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss Z");
3 Date dateOne = df.parse("2011-02-08 10:00:00 +0300");
4
5 //formatting a string representation given a date and a pattern
String dateStr = new SimpleDateFormat("EE, dd MMMM yyyy
6
z").format(dateOne);
7 System.out.println(dateStr); //Tue, 08 February 2011 EET
Since DateFormat is an abstract class, we need an implementation for instantiating it. That’s what we
get with SimpleDateFormat. The cool thing about DateFormat is that it isn’t overly complex to use and
given enough information it handles time zones as well.
1 DateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss Z");
2 Date dateOne = df.parse("2011-02-08 10:00:00 +0300");
3 Date dateTwo = df.parse("2011-02-08 08:00:00 +0100");
4
5 //compare the millisecond representations
6 long timeDiff = dateOne.getTime() - dateTwo.getTime();
7
System.out.println(String.format("difference %s milliseconds.",
8
timeDiff));
9 //difference 0 milliseconds.
Here we parsed two dates from strings and compared them. The (wall clock) time in the dates is
different but since they are in separate time zones, they actually represent the same point in time.
DateFormat looks pretty simple. However, there is more than meets the eye. One could think that
besides the date pattern, DateFormat is essentially a stateless parser/formatter. Wrong! This is hinted by
the methods getCalendar() and setCalendar(Calendar newCalendar). Ie. a Calendar object plays an
essential role in the implementation of DateFormat.
This is where things start to go downhill. First of all, DateFormat is not thread safe. The API document
states this clearly: “Date formats are not synchronized. It is recommended to create separate format
instances for each thread. If multiple threads access a format concurrently, it must be synchronized
externally.” You might be tempted to create a set of static convenience methods using DateFormat and
place them in a util class which can easily be used from all over the codebase. Don’t do that! The
chances are, you will run into hairy concurrency issues.
What if we need to add or subtract seconds or minutes or some other time unit to/from the date? There
are at least a couple of ways of doing this, none of which is particularly elegant and the current Date
API itself doesn’t provide a method for doing so. First of all you can expose the implementation details
and manipulate the milliseconds directly. Date has a method getTime() for that purpose.
01 DateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss Z");
02 Date dateOne = df.parse("2011-02-08 10:00:00 +0200");
03 //2011-02-08 10:00:00 +0200
04
05 //an hour worth of milliseconds
06 long hour = 1000 * 60 * 60;
07
08 //Create a new Date from milliseconds
09 Date dateTwo = new Date(dateOne.getTime() + (2 * hour));
10 System.out.println(df.format(dateTwo));
11 //2011-02-08 12:00:00 +0200
12
13 //Change the milliseconds of an existing date
14 dateTwo.setTime(dateOne.getTime() + (3 * hour));
15 System.out.println(df.format(dateTwo));
16 //2011-02-08 13:00:00 +0200
When you need to convert time units to other time units, a utility class called TimeUnit can save you
time and make the code more readable.
1 //Instead of...
2 long hour = 1000 * 60 * 60;
3 dateTwo.setTime(dateOne.getTime() + (3 * hour));
4
5 //...you could write
6 dateTwo.setTime(dateOne.getTime() + TimeUnit.HOURS.toMillis(3));
You may have noticed that the dates were printed in the same timezone as dateOne was parsed. This is
only a coincidence, since unless explicitly specified, DateFormat uses the system time zone which in
this case just happens to be +0200 (EET in normal time). Consider the following:
1 DateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss Z");
2 Date dateOne = df.parse("2011-02-08 10:00:00 +0100");
3 System.out.println(df.format(dateOne));
4 //2011-02-08 11:00:00 +0200
5
6 df.setTimeZone(TimeZone.getTimeZone("CET"));
7 System.out.println(df.format(dateOne));
8 //2011-02-08 10:00:00 +0100
As you can see, the first day is not formatted with the time zone offset +0100 even though it was parsed
from such a string. If you want to get the String representation of a Date in a certain time zone, you
have to explicitly set a TimeZone object to the DateFormat. Here the TimeZone object was created by
using a static factory method getTimeZone(String timeZoneId). An array of available time zone ids can
be accessed by the method getAvailableIDs(). The time zone offset can also be set using the method
setRawOffset(int offsetMillis). For public constructors, consult the TimeZone API.
I mentioned there are at least couple of ways for changing a date. In addition to manipulating the
milliseconds directly, a Calendar object can be used. Calendar is an abstract base class for calendar
implementations. For instantiating it, you need its implementation, GregorianCalendar.
01 DateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss Z");
02 Date date = df.parse("2010-11-08 10:00:00 +0200");
03
04 Calendar cal = new GregorianCalendar();
05 cal.setTime(date);
06 TimeZone helsinkiTz = TimeZone.getTimeZone("Europe/Helsinki");
07 cal.setTimeZone(helsinkiTz);
08 cal.add(Calendar.HOUR_OF_DAY, 2);
09 Date newDate = cal.getTime();
10
11 df.setTimeZone(helsinkiTz);
12 System.out.println(df.format(newDate));
13 //2010-11-08 12:00:00 +0200
For me, Calendar is the single biggest reason why date/time manipulation in Java is unpleasant to work
with. The API is not very well designed. It is outdated and it also has several misleading characteristics.
By outdated, I refer to its heavy use of int constants. Calendar was added to the Java standard library
before we had enums in the language, but even though enums have been around since Java 1.5, the
Calendar API makes no use of them.
If the code is written using the static final ints in the Calendar class, the code can be semi-readable.
However, because of the way the API was designed, it easily leads to code, where magic numbers or
custom constants are used in place of the named constants in the Calendar class. This is confusing for
the reader and can cause some tricky bugs.
1 //the calendar object from the previous example holding the date
2 //2010-11-08 12:00:00 +0200
3 cal.set(Calendar.MONTH, 1);
What do you think the new Date is? If you thought the month was changed to January, you guessed
wrong. The new month is February. Ie. Calendar.FEBRUARY = 1. Not so easy to spot.
One common case where the date/time API doesn’t shine is when you need to find out some property
of a date. Eg. the day or the month or the year. I’m not too happy about the options provided for this.
You can use DateFormat to format the date to a string and then parse the needed information, but that’s
error-prone and not very elegant. Another way is to create a calendar, set the time zone and use the
method getField() with the correct constant, but this is not very straight-forward either for such a
simple task.
Yet another poorly supported feature is time periods. There are no classes manipulating intervals
between dates, doing calculation for them etc.

Alternatives
Is the JDK standard date/time API the only option? Fortunately not. Perhaps the best-known alternative
is an open source library called Joda-Time. Joda-Time was born out of frustration with the standard
API. Not everyone is happy with Joda-Time either. Another open source library, Date4j is a light-
weight alternative, that may appeal to those who don’t need n+1 classes for handling their dates. Then
there is JSR 310, which is the proposal for the new and improved Date/Time API coming for Java SE 7.
JSR 310 is largely based on Joda-Time. JDK 7 is supposed to be released later this year. We’ll see…

You might also like