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?
- 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 usejava.util.Calendar.get(Calendar.HOUR_OF_DAY)
. Joda Time's base time class -org.joda.time.base.AbstractDateTime
- hasgetHourOfDay()
. -
java.util.Date
andjava.util.Calendar
are not thread-safe. Joda time hasorg.joda.time.DateMidnight
andorg.joda.time.DateTime
which are thread-safe (ororg.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
andjava.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 usejava.util.Calendar
(which usually meansjava.util.GregorianCalendar
) when you need to change the date/time (and then callgetTime()
to get a new date object with the modified data). To convert dates to strings or vice versa, usejava.text.SimpleDateFormat
. Unfortunately, none of these are thread-safe:java.util.Date
,java.util.Calendar
,java.util.GregorianCalendar
andjava.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).
- What makes them not thread-safe?
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.
- An article on IBM developerWorks: Joda-Time: You can't escape time. Why not make it easy?
- The Joda Time site itself has excellent documentation, including Javadocs, a Quick start guide, FAQ and detailed User Guide. The front page has a section called Why Joda Time? which is a good read too.