views:

486

answers:

5

Hello folks,

Please your opinion on the following code.

I need to calculate the diff in days between 2 Date objects. It is assured that both Date objects are within the same TimeZone.

public class DateUtils {
public final static long DAY_TIME_IN_MILLIS = 24 * 60 * 60 * 1000;

/**
 * Compare between 2 dates in day resolution.
 * 
 * @return positive integer if date1 > date2, negative if date1 < date2. 0 if they are equal.
 */
public static int datesDiffInDays(final Date date1, final Date date2){
 long date1DaysMS = date1.getTime() - (date1.getTime() % DAY_TIME_IN_MILLIS);
 long date2DaysMS = date2.getTime() - (date2.getTime() % DAY_TIME_IN_MILLIS);

 long timeInMillisDiff = (date1DaysMS - date2DaysMS);
 int ret = (int) (timeInMillisDiff / DAY_TIME_IN_MILLIS); 
 return ret;
}

Can you point to a problem that I might have missed ?

EDIT: @mmyers asked if pass my unit test. Well - Yes. But I have no real experience with dates and I know that is a big subject. Posted below the unit test that I'm using.

public class TestMLDateUtils {

@Test
public final void testDatesDiffInDays() {
 TimeZone.setDefault(TimeZone.getTimeZone("UTC"));

 // 00:00:00.000 1.1.1970
 Calendar cal1970 = Calendar.getInstance();
 cal1970.setTimeInMillis(0);

 Calendar tested = Calendar.getInstance();
 tested.setTimeInMillis(0);

 // Add 1 millisecond, date = 00:00:00.001 1.1.1970
 tested.add(Calendar.MILLISECOND, 1);

 assertTrue(DateUtils.datesDiffInDays(cal1970.getTime(), tested.getTime()) == 0);

 // Add 1 second, date = 00:00:01.001 1.1.1970
 tested.add(Calendar.SECOND, 1);
 assertTrue(DateUtils.datesDiffInDays(cal1970.getTime(), tested.getTime()) == 0);

 // Add 1 minute, date = 00:01:01.001 1.1.1970
 tested.add(Calendar.MINUTE, 1);
 assertTrue(DateUtils.datesDiffInDays(cal1970.getTime(), tested.getTime()) == 0);

 // Add 1 hour, date = 01:01:01.001 1.1.1970
 tested.add(Calendar.HOUR_OF_DAY, 1);
 assertTrue(DateUtils.datesDiffInDays(cal1970.getTime(), tested.getTime()) == 0);

 // date = 23:59:59.999 1.1.1970
 tested.setTimeInMillis(0);
 tested.add(Calendar.MILLISECOND, 999);
 tested.add(Calendar.SECOND, 59);
 tested.add(Calendar.MINUTE, 59);
 tested.add(Calendar.HOUR_OF_DAY, 23);
 //System.out.println("D: " + tested.getTime());
 assertTrue(DateUtils.datesDiffInDays(cal1970.getTime(), tested.getTime()) == 0);

 // date = 00:00:00.000 2.1.1970
 tested.setTimeInMillis(0);
 tested.add(Calendar.DAY_OF_MONTH, 1);
 assertTrue(DateUtils.datesDiffInDays(cal1970.getTime(), tested.getTime()) == -1);
 assertTrue(DateUtils.datesDiffInDays(tested.getTime(), cal1970.getTime()) == 1);

 // date = 00:00:00.001 2.1.1970
 tested.add(Calendar.MILLISECOND, 1);
 assertTrue(DateUtils.datesDiffInDays(cal1970.getTime(), tested.getTime()) == -1);
 assertTrue(DateUtils.datesDiffInDays(tested.getTime(), cal1970.getTime()) == 1);

 // date = 00:00:01.001 2.1.1970
 tested.add(Calendar.SECOND, 1);
 assertTrue(DateUtils.datesDiffInDays(cal1970.getTime(), tested.getTime()) == -1);
 assertTrue(DateUtils.datesDiffInDays(tested.getTime(), cal1970.getTime()) == 1);

 // date = 00:01:01.001 2.1.1970
 tested.add(Calendar.MINUTE, 1);
 assertTrue(DateUtils.datesDiffInDays(cal1970.getTime(), tested.getTime()) == -1);
 assertTrue(DateUtils.datesDiffInDays(tested.getTime(), cal1970.getTime()) == 1);

 // date = 01:01:01.001 2.1.1970
 tested.add(Calendar.HOUR_OF_DAY, 1);
 assertTrue(DateUtils.datesDiffInDays(cal1970.getTime(), tested.getTime()) == -1);
 assertTrue(DateUtils.datesDiffInDays(tested.getTime(), cal1970.getTime()) == 1);

 // date = 13:01:01.001 2.1.1970
 tested.add(Calendar.HOUR_OF_DAY, 12);
 assertTrue(DateUtils.datesDiffInDays(cal1970.getTime(), tested.getTime()) == -1);
 assertTrue(DateUtils.datesDiffInDays(tested.getTime(), cal1970.getTime()) == 1);
}
}
A: 

Date already has this method, look up Date.compareTo(Date) in Javadoc.

Scott Lemke
No, this method is specifically returning the difference as a number of days. compareTo() does not specify _what_ the positive/negative numbers will be.
Michael Myers
compareTo() works on full dates (up to a millisecond difference) I need the answer in a resolution of days. So for example 19.5.2009 20:01:28.555 compared to 19.5.2009 21:01:28.555 should be 0. While 19.5.2009 20:01:28.555 compared to 20.5.2009 20:01:28.555 should be 1
Maxim Veksler
compareTo() is most likely returning only -1, 0 or 1, depending on whether the object in question is less than, equal to or greater than the method's parameter. It's just not obligated to do so.
Curt Sampson
So what would 19.5.2009 20:01:28:555 compared to 21.5.2009 10:10:10:100 return? 1 or 2? The declaration of the question and the code comment lead me to believe it was compareTo();
Scott Lemke
+7  A: 
  • Immediate problem: days can have less than or more than 24 hours due to daylight saving time changes.

  • Secondary problem: normally when people think in days, they really mean "human days" rather than "periods of 24 hours". In other words, many people would say that 7pm-7am the next day is a difference of a day, whereas 7am-7pm the same day is a difference of zero days. Both are 12 hours. At that point, you really need to know the calendar that is being considered.

Of course, this may not matter for your situation, but we don't really know what that is.

  • Third problem: you're using the built-in calendar API instead of Joda Time. That's almost never a good idea - it's horrible and riddled with gotchas and problems. And yes, the regulars here will tell you that's always part of my answer when it comes to Java dates and times - and for good reason. It's really that important.

EDIT: Your test sets the default time zone to be UTC. That's not really a good idea (especially without resetting it in a finally statement). Time zones are tricky, but you should really think about what values you've got, what they mean, and what time zones are involved.

Jon Skeet
That's the third Joda Time answer I've seen today...
Michael Myers
Your link is 404. Don't you mean joda-time.sourceforge.net?
seth
Thank you for the details answer. Good point.Regarding joda time - I am aware of this library and sadly can't use.Regrading the 2 other points - I need to think about this.
Maxim Veksler
Seth: yes, sorry about that. I'm on a phone right now - could someone fix the link for me please?
Jon Skeet
There, done.
Michael Myers
I've fixed the second problem you mentioned.
Maxim Veksler
+1  A: 

The time zone, if any, within the Date object is irrelevant, since you're using getTime(); that "[r]eturns the number of milliseconds since January 1, 1970, 00:00:00 GMT represented by this Date object."

However, you aren't accounting for leap seconds, which some implementations may return. Thus, if the day range you give has one or more leap seconds in it, and your times are near enough to the same time of day, your calculation may be wrong by a day. That said, there appears to be no way to see if any particular implementation accounts for leap seconds or not (I expect that most don't), and the difference is pretty darn trivial anyway.

Curt Sampson
+1  A: 

Another problem which hasn't been mentioned yet is leap seconds. Days may have more or less than 24 * 60 * 60 seconds due to adjustments in UTC time to keep it more or less in synch with the mean solar year. Probably not a big deal for your usage, but you should at least be aware of the possibility.

A good API is what you need if you have non-trivial requirements for dealing with dates and times. The link to Joda Time in Jon Skeet's answer appears to be broken, so here is a link that does work.

A. Levy
+1  A: 

There are many dimensions to a code review; rather than correctness, addressed by others, let me focus a little on style. This will of course be somewhat more subjective than a review concentrating on correctness.

I would inline the "ret" variable. It increases the size of the method without enhancing readability.

I would consider separating the conversion between milliseconds and days into a separate function. Your full class probably performs that division in multiple places. Even if not, it's helpful in that it's easier to name functions that do only one thing.

Speaking of naming, I would rename the function, perhaps to "dayDifference" - abbreviations cause many problems, not least of which is the difficulty of remember which abbreviation was used in which circumstance. If you use none, ever, that particular source of confusion is eliminated. Similarly, I would rename the constant to MILLISECONDS_PER_DAY.

Carl Manaster