December 20, 2014
Hot Topics:

Back to Basics with Dates, Calendars, and Formatters

  • December 12, 2007
  • By Rob Lybarger
  • Send Email »
  • More Articles »

Introduction

Working with dates seems like a simple enough task, but there are some pitfalls for the unwary. This article was sparked, in part, by a forum question that seemed innocent enough on the surface: "How can I compute the number of days between two dates?" It brought to mind a blog entry I read, a couple years ago, where someone was flaming Java for having a bug in the number of days in the month of April—he even had code to "prove" it. And of course, being unwary, he fell deep into one of the pitfalls that surround date computations.

But while you're here, I will also show a couple of classes in the standard API that help format messages. One of them is specifically designed for those situations where you need a different format depending on the value of some number, such as singular/plural word forms in a phrase. (It's like two articles in one. What a bonus!)

Background

The really old-timers in the Java crowd—those who started on JDK 1.0—started with the java.util.Date class. It was simple enough to use, but did little to really help a developer do anything other than get and set the component values in some date. It was left to the developer to keep track of which years were leap years, for example. (It isn't as simple as "any year divisible by 4"; there are exceptions, with exceptions.)

Then JDK 1.1 came out, and most of the constructors and methods in the Date class were deprecated, with directions to use the Calendar class instead. However, in the interim, JDBC code subclassed the java.util.Date class into java.sql.Date (and a couple others) to reflect standard SQL storage types. For good or ill, you're stuck with code that throws a Date object (of some variety) around.

Fortunately, translating between a Date and a Calendar object isn't too bad. Refer to the static Calendar.getInstance() method (synonymous with "new Date()" to get a representation of "right now") and the instance methods getTime() and setTime(Date). The getTimeInMillis() and setTimeInMillis(long) instance methods also are valuable because the Date constructor that takes a long parameter (representing a milliseconds value) is the only constructor (other than the no-arg) that survived the deprecation. And, when you want a Date object initialized to a specific value but don't want to raise your hackles over a deprecation warning, you can use a SimpleDateFormat instance to parse a String directly into one. So, you can go back and forth. What's the big deal, then?

Pitfalls

The blog entry I mentioned reading involved someone calculating the number of days in each month by getting the time, in milliseconds, of the first of each month (at midnight, as I recall), taking a difference of those millisecond values, then dividing by "24 * 60 * 60 * 1000", which would be the number of milliseconds in a day. Where this blogger came up short was that he was told April had 29 days. Here's a code snippet to recreate the event:

try {
   SimpleDateFormat sdf = new SimpleDateFormat("dd-MMM-yyyy");
   Date d1 = sdf.parse("01-Apr-2005");
   Date d2 = sdf.parse("01-May-2005");
   long millisInDay = 24 * 60 * 60 * 1000;
   int wrongDiff = (int) ((d2.getTime() -
      d1.getTime()) / millisInDay);
   // 29 days?
   System.out.println("Apr 2005 has "+wrongDiff+" days");
} catch (ParseException pe) { /*ignored*/ }

Clearly, you know otherwise. Curiously, when he played a similar game to compute the number of days in a full year, he got the right number! Although the blogger knew something was wrong, he didn't quite arrive at the right conclusion. The Java libraries were accounting for the loss of an hour in Spring when (most of) the United States enters Daylight Saving Time (DST). (Also, note the integer division effect.)

Note: To play the game this year, check how many days this method reports for March, because DST now starts sooner.

The lesson is that the Calendar class is probably smarter than you are. It knows which years are and are not leap years, so it knows how many days are in any given year. It also knows how to adjust for the lose-an-hour, gain-an-hour DST effects. And, when you know how to take advantage of this, you can reliably count the number of days between two arbitrary dates.

The lesson is also that "24*60*60*1000" is a bad idea, and it is unfortunate that it seems so commonplace in date difference calculation code throughout the Internet. (For that matter, I'll even admit that, when my fingers are typing faster than my brain can think, I'll have typed up that very piece of code, only to remember later that it has hidden faults.) So, what's the right way? Read on.

How Many Days Between...

With the background covered, turn your attention to a question that comes up often enough that I thought it was worth writing an article over: How does one compute the number of days between two arbitrary dates? Such a question might come up in a business workflow management application where, knowing a starting date and a projected ending date, one wants to know how many actual days are between them. Another reason this question might come up is the display of "relative date" information as an optional user convenience. Examples include: "file last updated four days ago" or "message received yesterday" or "meeting today at 2:00 pm."

Noting that the Calendar class has methods to indicate whether one instance is "before" or "after" another instance (and treating the hour, minute, and second fields as being equal, such as at midnight) a naïve way to attack this problem would be to simply enter a while loop, incrementing one of the Calendar objects day fields (for example, DAY_OF_MONTH) and accumulating these increments until the two objects are on the same day, and then returning the accumulated value. This works, but the amount of time needed in the loop increases with the number of days between the dates in question. There is a more efficient way to attack the problem:

int computeDayDiff(Calendar calA, Calendar calB) {
   Calendar firstCal = (calA.before(calB) ? (Calendar)calA.clone() :
                       (Calendar)calB.clone());
   Calendar secondCal = (calA.before(calB) ? calB : calA);

   // first adjust the starting date back to the beginning
   // of the year
   int diff = -firstCal.get(Calendar.DAY_OF_YEAR);
   firstCal.set(Calendar.DAY_OF_YEAR, 1);

   // then adjust the years to have the same value, adding the
   // number of days in that year to the difference accumulator
   while(firstCal.get(Calendar.YEAR)
      < secondCal.get(Calendar.YEAR)) {
      diff += firstCal.getActualMaximum(Calendar.DAY_OF_YEAR);
      firstCal.add(Calendar.YEAR, 1);
   }

   // now both calendar objects are in the same year, so add
   // back the ending "day of the year" value.
   diff += secondCal.get(Calendar.DAY_OF_YEAR);
   return diff;
}




Page 1 of 2



Comment and Contribute

 


(Maximum characters: 1200). You have characters left.

 

 


Enterprise Development Update

Don't miss an article. Subscribe to our newsletter below.

Sitemap | Contact Us

Rocket Fuel