Saturday, March 30, 2013

Just date, no time: comparing java.util.Date with org.joda.time.DateMidnight

In my current application I am dealing with date input; no times are involved. Therefore it is important that in my code, every date has the same time so that they compare correctly. All java.util.Date objects have a time attached, and if you use the default constructor, they will reflect the time at which they were created, down to the millisecond. It means that two dates constructed at different times are not the same, as you can see from the below code.

Date date1 = new Date();
SimpleDateFormat formatter = new SimpleDateFormat("yyyy/MM/dd HH:mm:s:S");
Date date1 = new Date();
try {
   Thread.sleep(1000);
} catch (InterruptedException e) {
   e.printStackTrace();
}
Date date2 = new Date();
System.out.println("date1.compareTo(date2) [" + date1.compareTo(date2) + "]");
System.out.println("date 1: " + formatter.format(date1));
System.out.println("date 2: " + formatter.format(date2));

The output of this code is below.

date1.compareTo(date2) [-1]
date 1: 2013/03/30 19:59:58:470
date 2: 2013/03/30 19:59:59:470

So when dealing with dates in Java, I need to make sure all my dates have the same time. I will show below how you can do this with java.util.Date and compare it with a convenience class in Joda Time called org.joda.time.DateMidnight.

Why Joda Time?

  1. Joda Time is more concise and easier to use than Java's in-built time classes. For example, java.util.Date.getHours() is deprecated and says you should use java.util.Calendar.get(Calendar.HOUR_OF_DAY). Joda Time's base time class - org.joda.time.base.AbstractDateTime - has getHourOfDay().
  2. java.util.Date and java.util.Calendar are not thread-safe. Joda time has org.joda.time.DateMidnight and org.joda.time.DateTime which are thread-safe (or org.joda.time.MutableDateTime if you need a version that can be modified - and thus not thread-safe).
    • What makes them not thread-safe? java.util.Date and java.util.Calendar objects can be changed after they are created: they are mutable; they have setter/mutator methods. Even though these methods are deprecated, they are still there and can still introduce subtle bugs in your code if used.
    • java.util.Date's javadoc says to use java.util.Calendar (which usually means java.util.GregorianCalendar) when you need to change the date/time (and then call getTime() to get a new date object with the modified data). To convert dates to strings or vice versa, use java.text.SimpleDateFormat. Unfortunately, none of these are thread-safe: java.util.Date, java.util.Calendar, java.util.GregorianCalendar and java.text.SimpleDateFormat.
    • So what? For single threaded applications this is not important. But for any application that uses multiple threads, it is very important. If logic from more than one thread changes the state of a single object they each rely on, it may make the results un-predictable. For example: thread 1 is changing a calendar object to be some date in 2000 while thread 2 is changing the same calendar object to be some date in 2013. If those instructions happen to execute at the same time, both threads might end up with a date different to what they are expecting. Any web application is multi-threaded. This means that instance variables of these may have their state changed in unpredictable ways in a web-app. There are still ways around this.
      • Use them as local variables that live and die within one single methods's scope.
      • Take steps to make them thread-safe, such as using synchronised blocks or (much better) embedding them in a java.lang.ThreadLocal instance).

Here is my code comparing uses of Java's time with Joda Time.

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;

import org.joda.time.DateMidnight;
import org.joda.time.DateTime;
import org.joda.time.DateTimeConstants;
import org.joda.time.DurationFieldType;
import org.joda.time.format.DateTimeFormat;
import org.joda.time.format.DateTimeFormatter;

public final class TestDates {

   public static void main(String[] args) {
      gregorian();
      joda();
   }

   public static void gregorian() {
      System.out.format("%34s%n", "--- java.util ---");
      SimpleDateFormat formatter = new SimpleDateFormat("yyyy/MM/dd HH:mm:s:S");

      // Default date.
      GregorianCalendar calendar1 = new GregorianCalendar();
      Date date1 = calendar1.getTime();
      System.out.format("%34s %s%n", "Default date", formatter.format(date1));

      // Set date through constructor.
      GregorianCalendar calendar2 =
            new GregorianCalendar(2013, Calendar.APRIL, 30);
      Date date2 = calendar2.getTime();
      System.out.format("%34s %s%n", "Set date through constructor",
            formatter.format(date2));

      // Set date through modifier methods.
      GregorianCalendar calendar3 = new GregorianCalendar();
      calendar3.set(Calendar.YEAR, 2013);
      calendar3.set(Calendar.MONTH, Calendar.APRIL);
      calendar3.set(Calendar.DAY_OF_MONTH, 29);
      calendar3.set(Calendar.HOUR_OF_DAY, 0);
      calendar3.set(Calendar.MINUTE, 0);
      calendar3.set(Calendar.SECOND, 0);
      calendar3.set(Calendar.MILLISECOND, 0);
      Date date3 = calendar3.getTime();
      System.out.format("%34s %s%n", "Set date through modifier methods",
            formatter.format(date3));

      // Parsing a date from a string.
      try {
         Date date4 = formatter.parse("2013/04/29 00:00:0:0");
         System.out.format("%34s %s%n", "Parsing a date from a string",
               formatter.format(date4));
      } catch (ParseException e) {
         System.err.println("Unable to parse date");
         e.printStackTrace();
      }
   }

   public static void joda() {
      System.out.format("%34s%n", "--- org.joda.time ---");
      DateTimeFormatter DATE_FORMAT =
            DateTimeFormat.forPattern("yyyy/MM/dd HH:mm:s:S");

      // Default date.
      DateMidnight date1 = new DateMidnight();
      System.out.format("%34s %s%n", "Default date", DATE_FORMAT.print(date1));

      // Set date through constructor.
      DateMidnight date2 = new DateMidnight(2013, DateTimeConstants.APRIL, 30);
      System.out.format("%34s %s%n", "Set date through constructor",
            DATE_FORMAT.print(date2));

      // Set date based on another date (DateMidnight is immutable, so you
      // cannot change it's state, but it has convenience methods to let you
      // easily make a new date from an existing date.
      DateMidnight date3 = date2.withFieldAdded(DurationFieldType.days(), -1);
      System.out.format("%34s %s%n", "Set date based on another date",
            DATE_FORMAT.print(date3));

      // Parsing a date from a string.
      DateTime date4 = DATE_FORMAT.parseDateTime("2013/04/29 00:00:0:0");
      System.out.format("%34s %s%n", "Parsing a date from a string",
            DATE_FORMAT.print(date4));
   }
}

The output from the code above is here.

                 --- java.util ---
                      Default date 2013/03/30 19:16:56:685
      Set date through constructor 2013/04/30 00:00:0:0
 Set date through modifier methods 2013/04/29 00:00:0:0
      Parsing a date from a string 2013/04/29 00:00:0:0
             --- org.joda.time ---
                      Default date 2013/03/30 00:00:0:0
      Set date through constructor 2013/04/30 00:00:0:0
    Set date based on another date 2013/04/29 00:00:0:0
      Parsing a date from a string 2013/04/29 00:00:0:0

Conclusions

For dealing with just dates (no times), org.joda.time.DateMidnight is less verbose than using a combination of java.util.Calendar and java.util.Date. You can see from the code examples above, that using java.util involves using a calendar to set the date and then using getTime() to get a java.util.Date. With Joda Time, I can work directly with a DateMidnight object. Although I can change the date on a calendar to get a new java.util.Date, with DateMidnight I can use convenience methods on it to get a new DateMidnight with different state.

Further reading.