CalendarDate - simple, small & effective

[Javadoc] [Download]

Dealing with dates in Java is notoriously difficult. If you ask 10 Java programmers how they would represent "July 10, 2004" you will get about 4 different answers. This has lead to countless avoidable bugs as developers struggle to match their everyday concept of a 'date' to the API. Java needs a clear, simple class which encapsulates a 'date'.

Here are four situations involving dates that are much harder to program in Java than they should be:

java.util.Calendar

The standard Java API for representing and calculating dates in Java is a mess. The commonly-used class - java.util.Calendar - has many faults:
  1. It is very complicated to do date calculations. In fact, there is no simple, efficient way to calculate the days between two dates (e.g. see this thread).
  2. It is not thread-safe or immutable.
  3. It refers to months using a zero-based index
  4. It does not encapsulate the concept of a 'date' - I can have two objects that both represent '10 July 2004' but which are not 'equal' (e.g. if they have different timezones).
  5. The introduction of future 'leap seconds' can change the date represented by a serialized object
More information on this date problem is available here here

Joda-time

One attempt to improve the situation is joda-time This API also has its share of problems:
[Update: this refers to an older version of joda-time. See the response from Joda-time team members below.]
  1. It is far too complicated. There are 100+ new classes with many levels of inheritance and interface implementations.
  2. It replaces standard API classes that aren't broken (like java.util.TimeZone). Yes, the replacement is 'ISO-compliant' and immutable, but that isn't enough to make developers want to learn a new class. And if you want to use any of it, you're forced to reference all these replacement classes.
  3. It doesn't make a clear distinction between a 'date' and a 'time'. For example, org.joda.time.DateOnly implements a ReadableInstant, so a date is also a point in time. This means I can pass a date (of type DateOnly) into a method which expects a time (of type ReadableInstant).
  4. The classes don't match the everyday concepts they try to represent. For example, DateOnly has a getDateTimeZone() method - but what is the timezone of Easter Sunday or John's Birthday?

CalendarDate

A simpler solution is shown below. It is a single class representing a date in the Gregorian Calendar. It is simple, thread-safe, immutable and makes conversions between dates and times explicit. See the javadoc here.

 

import java.io.IOException;
import java.io.Serializable;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.TimeZone;

public final class CalendarDate implements Comparable, Serializable {

    public static final int SUNDAY = 1;
    public static final int MONDAY = 2;
    public static final int TUESDAY = 3;
    public static final int WEDNESDAY = 4;
    public static final int THURSDAY = 5;
    public static final int FRIDAY = 6;
    public static final int SATURDAY = 7;

    public static final int JANUARY = 1;
    public static final int FEBRUARY = 2;
    public static final int MARCH = 3;
    public static final int APRIL = 4;
    public static final int MAY = 5;
    public static final int JUNE = 6;
    public static final int JULY = 7;
    public static final int AUGUST = 8;
    public static final int SEPTEMBER = 9;
    public static final int OCTOBER = 10;
    public static final int NOVEMBER = 11;
    public static final int DECEMBER = 12;

    public static final CalendarDate EARLIEST = new CalendarDate(1600, 1, 1);
    public static final CalendarDate LATEST = new CalendarDate(2999, 12, 31);


    public CalendarDate(int year, int month, int dayOfMonth);
    public CalendarDate(TimeZone tzone, Date instantInTime);
    public CalendarDate(TimeZone tzone);

    public int getDayOfMonth();
    public int getDayOfWeek();
    public int getMonth();
    public int getYear();

    public CalendarDate addDays(int numDays);
    public int daysUntil(CalendarDate otherDay);
    public CalendarDate addMonths(int numMonths);
    public int monthsUntil(CalendarDate otherDay);
    public static boolean isLeapYear(int year);


    public int compareTo(Object other);
    public boolean isBefore(CalendarDate other);
    public boolean isAfter(CalendarDate other);


    public boolean equals(Object other);
    public int hashCode();
    public String toString();

    private void writeObject(java.io.ObjectOutputStream out) throws IOException;
    private void readObject(java.io.ObjectInputStream in) throws IOException;

    public Date toDate(TimeZone timezone);
    public Date toDate(TimeZone timezone, int hour, int min, int sec);


}


Update: The folks at Joda-time responded to my criticism as follows.

From Brian S O'Neill, Joda-Time development team member, 30 November 2004 06:48:
You list four points. On points three and four, I completely agree. DateOnly made no sense to have a time zone. In the latest version, 0.98, we chucked DateOnly. There are now two classes in its place, DateMidnight and YearMonthDay. DateMidnight is pretty much like DateOnly, but YearMonthDay is completely time zone agnostic.

On point two, you question the need to have a new time zone class. We did this primarily to maintain consistency. Different JVMs have different time zone implementations, and some don't properly implement historical transitions. We've updated our FAQ because this is in fact a FAQ. Another reason is performance. The way Joda-Time works, it needs to perform frequent time zone lookups. The Joda time zone class is much faster than the standard one, even the jdk1.4 version.

As for point one, this is a matter of taste. Most of the classes in Joda-Time are not "user level" classes. We've updated our javadocs to reflect this. If you only look at the classes in the main package and in format, the set is much smaller.

Implementing a calendar system from scratch requires a lot of code. You just can't see it with the jdk classes. The implementation code is hidden. Joda-Time aims to provide much more flexibility for those that may wish to extend it.

CalendarDate provides a nice simple interface for performing "common" operations, but it relies internally on the jdk classes. All the complexity is hidden behind many levels of abstraction. CalendarDate can just as well be implemented with Joda-Time and you'd still have the simple interface you desire.

From Stephen Colebourne, Joda-Time development team member, 06 November 2004 17:03:
I would like to go through your comments on the website:

> 1. It is far too complicated. There are 100+ new classes with many levels of inheritance and interface implementations.
When we set out to do a complete cleanroom implementation it was always going to get complicated ;-) Although sometimes even I think there are places that it could have been simpler. However, when you target multiple calendar systems, flexibiilty and high performance, size tends to be the trade-off.

> 2. It replaces standard API classes that aren't broken (like java.util.TimeZone). Yes, the replacement is 'ISO-compliant' and immutable, but that isn't enough to make developers want to learn a new class. And if you want to use any of it, you're forced to reference all these replacement classes.
One problem with the JDK TimeZone is that there is no easy way to replace the timezone rules. These rules are updated 8+ times a year in an OSS database. By having our own timezone we can pick up the latest set of rules. Plus its immutable and faster ;-)

> 3. It doesn't make a clear distinction between a 'date' and a 'time'. For example, org.joda.time.DateOnly implements a ReadableInstant, so a date is also a point in time. This means I can pass a date (of type DateOnly) into a method which expects a time (of type ReadableInstant).
Yeh, that was a dumb mistake. It got corrected over the summer, and the 0.98 release introduces the solution. Now there is a category of date/time classes without time zones. But we also now explicitly support the concept of DateMidnight - a full datetime (with zone) but with the time fixed at midnight.

> 4. The classes don't match the everyday concepts they try to represent. For example, DateOnly has a getDateTimeZone() method - but what is the timezone of Easter Sunday or John's Birthday?
See #3

You may also be interested in http://timeandmoney.sourceforge.net/ although I don't know how active the project is.


SourceForge.net Logo


Written by Mark McLaren (m_k_mclaren at hotmail.com). Last updated 12 October 2004