Thursday, July 12, 2007

Java - calculate the difference between two dates

Finding the difference between two dates isn't as straightforward as subtracting the two dates and dividing the result by (24 * 60 * 60 * 1000). Infact, its erroneous! 

Going the 'milliseconds way' will lead to rounding off errors and they become most evident once you have a little thing like "Daylight Savings Time" come into the picture.

The Correct Way:

/** Using Calendar - THE CORRECT WAY**/
//assert: startDate must be before endDate
public static long daysBetween(Calendar startDate, Calendar endDate) {
  Calendar date = (Calendar) startDate.clone();
  long daysBetween = 0;
  while (date.before(endDate)) {
    date.add(Calendar.DAY_OF_MONTH, 1);
    daysBetween++;
  }
  return daysBetween;
}
}

or more efficiently, (thanks Mauro), if you're using the Gregorian Calendar:
/** Using Calendar - THE CORRECT (& Faster) WAY**/
/****Needs testing ...... Anyone?****/ 
//assert: startDate must be before endDate
public static long daysBetween(final Calendar startDate, final Calendar endDate) {
 int MILLIS_IN_DAY = 1000 * 60 * 60 * 24;
 long endInstant = endDate.getTimeInMillis();
 int presumedDays = (int) ((endInstant - startDate.getTimeInMillis()) / MILLIS_IN_DAY);
 Calendar cursor = (Calendar) startDate.clone();
 cursor.add(Calendar.DAY_OF_YEAR, presumedDays);
 long instant = cursor.getTimeInMillis();
 if (instant == endInstant)
  return presumedDays;
 final int step = instant < endInstant ? 1 : -1;
 do {
  cursor.add(Calendar.DAY_OF_MONTH, step);
  presumedDays += step;
 } while (cursor.getTimeInMillis() != endInstant);
 return presumedDays;
}



The Nuances:

Lets take, for example, the difference between the two dates
03/24/2007 and 03/25/2007 should be 1 day; 

However, using the millisecond route, you'll get 0 days, if you run this in the UK!


import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.TimeZone;

public class DateTest {

public class DateTest {

static SimpleDateFormat sdf = new SimpleDateFormat("dd-MMM-yyyy");

public static void main(String[] args) {

  TimeZone.setDefault(TimeZone.getTimeZone("Europe/London"));

  //diff between these 2 dates should be 1
  Date d1 = new Date("01/01/2007 12:00:00");
  Date d2 = new Date("01/02/2007 12:00:00");

  //diff between these 2 dates should be 1
  Date d3 = new Date("03/24/2007 12:00:00");
  Date d4 = new Date("03/25/2007 12:00:00");

  Calendar cal1 = Calendar.getInstance();cal1.setTime(d1);
  Calendar cal2 = Calendar.getInstance();cal2.setTime(d2);
  Calendar cal3 = Calendar.getInstance();cal3.setTime(d3);
  Calendar cal4 = Calendar.getInstance();cal4.setTime(d4);

  printOutput("Manual   ", d1, d2, calculateDays(d1, d2));
  printOutput("Calendar ", d1, d2, daysBetween(cal1, cal2));
  System.out.println("---");
  printOutput("Manual   ", d3, d4, calculateDays(d3, d4));
  printOutput("Calendar ", d3, d4, daysBetween(cal3, cal4));
}


private static void printOutput(String type, Date d1, Date d2, long result) {
  System.out.println(type+ "- Days between: " + sdf.format(d1)
                    + " and " + sdf.format(d2) + " is: " + result);
}

/** Manual Method - YIELDS INCORRECT RESULTS - DO NOT USE**/
/* This method is used to find the no of days between the given dates */
public static long calculateDays(Date dateEarly, Date dateLater) {
  return (dateLater.getTime() - dateEarly.getTime()) / (24 * 60 * 60 * 1000);
}

/** Using Calendar - THE CORRECT WAY**/
public static long daysBetween(Calendar startDate, Calendar endDate) {
  Calendar date = (Calendar) startDate.clone();
  long daysBetween = 0;
  while (date.before(endDate)) {
    date.add(Calendar.DAY_OF_MONTH, 1);
    daysBetween++;
  }
  return daysBetween;
}
}

OUTPUT:
Manual - Days between: 01-Jan-2007 and 02-Jan-2007 is: 1
Calendar - Days between: 01-Jan-2007 and 02-Jan-2007 is: 1
---
Manual - Days between: 24-Mar-2007 and 25-Mar-2007 is: 0
Calendar - Days between: 24-Mar-2007 and 25-Mar-2007 is: 1

36 comments:

Rodney Beede said...

What if end is before start (negative days diff)?

Arched Eyebrow said...

assertion added. Thanks!

Agus said...

You can use jodatime to solve your problem http://joda-time.sourceforge.net/

geo said...

Good one. Thanks for sharing.

Anonymous said...

using joda time:

DateTime start = new DateTime(2007, 1, 1, 0, 0, 0, 0);
DateTime end = new DateTime(2009, 1, 1, 0, 0, 0 ,0);

Days days = Days.daysBetween(start, end);
System.out.println(days.getDays());

Chris B said...

You should be aware that the algorithm given may give 'one more day' than you expect.

It gives 1 as the number of days between 2009-02-28 19:00:00 and 2009-02-28 19:00:01.

This may be what you want (it is what I want) - but do check.

Anonymous said...

Very usefull stuff.

Thanks very much.

Javaman said...

You just save my life brother, i was crazy about it

Anonymous said...

thanks for sharing

Anonymous said...

What if the Start date is feb sometime say(20 feb 2011) and end date is march say (3 march 2011) ?
Check the day difference between these two dates.

Anonymous said...

Thank you very much.

Anonymous said...

This also works. Simply get an instance of a Calendar for GMT timezone, it has no offset, daylight saving flag is set to false (sun.util.calendar.ZoneInfo[id="GMT",offset=0,dstSavings=0,useDaylight=false,transitions=0,lastRule=null])
Use the calendar to set the date.

final Calendar cal = Calendar.getInstance(TimeZone.getTimeZone("GMT"));
cal.setTimeInMillis(0);
System.out.println(cal.getTimeZone());

cal.set(Calendar.YEAR, 2011);
cal.set(Calendar.MONTH, 9);
cal.set(Calendar.DAY_OF_MONTH, 29);
cal.set(Calendar.HOUR, 0);
cal.set(Calendar.MINUTE, 0);
cal.set(Calendar.SECOND, 0);
final Date startDate = cal.getTime();

cal.set(Calendar.YEAR, 2011);
cal.set(Calendar.MONTH, 12);
cal.set(Calendar.DAY_OF_MONTH, 21);
cal.set(Calendar.HOUR, 0);
cal.set(Calendar.MINUTE, 0);
cal.set(Calendar.SECOND, 0);
final Date endDate = cal.getTime();

System.out.println((endDate.getTime() - startDate.getTime()) % (1000l * 60l * 60l * 24l));

Jorge said...

Thanks

Java Questions said...

Why not java provides standard way to calculate difference between two dates instead of implementing own solution. I like the joda time way more clean.

Thanks
How to convert String to Date in Java

Bigcalm said...

This is way too complicated for what it is - here's a far more efficient way of doing this...

private Integer daysBetween(Calendar startDate, Calendar endDate) {
long start = startDate.getTimeInMillis();
long end = endDate.getTimeInMillis();
long diff = end - start;
Long daysBetween = diff / 86400000;
return daysBetween.intValue();
}

Anonymous said...

@bigcalm :

Did you read the topic?
Sure your way is more efficient, but it's also sometimes wrong...

Mauro said...

Your daysBetween function optimized for GregorianCalendar is wrong. For instance:

From: Thu Jan 31 00:00:00 CET 2019
To: Fri Mar 29 00:00:00 CET 2019

Correct result: 57
Result calculated by your function: 60
Using Europe/Berlin timezone.

This is just an example, if you do a long test with random dates you can check yourself. By the way, a still correct approach for GregorianCalendar but even more simple and fast is to do an heuristic (based on the naive "wrong" method), followed by an adjustment. That is:

public static int heuristic(Calendar startDate, Calendar endDate)
{
long endInstant = endDate.getTimeInMillis();
int presumedDays =
(int) ((endInstant - startDate.getTimeInMillis()) / MILLIS_IN_DAY);
Calendar cursor = (Calendar) startDate.clone();
cursor.add(Calendar.DAY_OF_YEAR, presumedDays);
long instant = cursor.getTimeInMillis();
if(instant == endInstant)
return presumedDays;
final int step = instant < endInstant? 1: -1;
do
{
cursor.add(Calendar.DAY_OF_MONTH, step);
presumedDays += step;
}
while(cursor.getTimeInMillis() != endInstant);
return presumedDays;
}

Ryan Fernandes said...

Thanks Mauro. Have incorporated your code into the post.

Anonymous said...

I'm interested in why the startDate parameter is cloned? And why isn't the endDate cloned as well?

Anonymous said...

Because the startDate is manipulated (used as a counter), the endDate is not. It would be impolite at least to modify your input parameter as side effect.

Anonymous said...

Hey Ryan Fernandes, thanks for the code. Is there any licensing for this?

Anonymous said...

This is great. thank you

John said...

Doesn't work if file is only an hour older, in fact it runs in a continuous loop due to while (cursor.getTimeInMillis() != endTimeMil) never equalling it should be a <= sign

John said...

I had to add this to make it work

long temp = 0;
do {
cursor.add(Calendar.DAY_OF_MONTH, step);
numberDays += step;
temp = cursor.getTimeInMillis();
} while (temp <= endTimeMil);

Misha Mostov said...

As John has noted equality cursor.getTimeInMillis() != endTimeMil) terminates loop only when there is even number of days b/n start and end dates. I propose the following fix:

int endDay = endDate.get(Calendar.DAY_OF_YEAR);
if (computedDay == endDay) {
return presumedDays;
}

final int step = computedDay < endDay ? 1 : -1;
do {
cursor.add(Calendar.DAY_OF_YEAR, step);
presumedDays += step;
} while (cursor.get(Calendar.DAY_OF_YEAR) != endDay);

Anonymous said...

How to calculate the difference between two date? Before. I am running into a problem.

Anonymous said...

How to calculate the difference between two date? using Before method?. I am running into a problem.

Gianluca Romeo said...
This comment has been removed by the author.
Gianluca Romeo said...

I would suggest something less bad performing as this, any flaws?:

public static void main(String[] args) {

TimeZone ltz = TimeZone.getTimeZone("Europe/London");
Date dateTo = new Date("03/25/2007 12:00:00");
Date dateFrom = new Date("03/24/2007 12:00:00");
System.out.println("days: "+ durationInDays(ltz, dateFrom, dateTo));
}

private static long durationInDays(TimeZone tz, Date dateFrom, Date dateTo) {
long timeTo = dateTo.getTime();
long timeFrom = dateFrom.getTime();
long duration = timeTo - timeFrom;
if (tz.inDaylightTime(dateFrom)) {
if (!tz.inDaylightTime(dateTo)) {
System.out.println("in daylight saving from: "+dateFrom);
System.out.println("not in daylight saving to: "+dateTo);
duration -= 60 * 60 * 1000;
}
} else {
if (tz.inDaylightTime(dateTo)) {
System.out.println("not in daylight saving from: "+dateFrom);
System.out.println("in daylight saving to: "+dateTo);
duration += 60 * 60 * 1000;
}
}
return duration / ( 24 * 60 * 60 * 1000 );
}

jpgravel said...

Interesting but quite a slow algorithm. This code suggestion base it's assertion on the educated guess based on the 1000 * 60 * 60 * 24 value then correct the answer with one day increments.

public static long daysBetween(Calendar startDate, Calendar endDate) {
if (startDate.after(endDate))
return -daysBetween(endDate, startDate);

Calendar date = (Calendar) startDate.clone();
long bigLeap = (endDate.getTimeInMillis() - startDate.getTimeInMillis())
/ 1000 / 60 / 60 / 24;
date.add(Calendar.DAY_OF_YEAR, (int) bigLeap);

long smallStep = 0;
if (date.before(endDate)) {
while (date.before(endDate)) {
date.add(Calendar.DAY_OF_MONTH, 1);
smallStep++;
}
} else if (endDate.before(date)) {
while (endDate.before(date)) {
date.add(Calendar.DAY_OF_MONTH, -1);
smallStep--;
}
}

return smallStep + bigLeap;
}

Anonymous said...

@jpgravel: I tested your code and if you compute the difference between (day/month/year hour:minute:second):
28/7/2009 22:0:0
and
29/7/2009 23:0:0
you get 2 as a result. If we want to compare only the dates, not the time, it is not 2 days, but 1 day of difference.

Anonymous said...

Is there any reason why one would not use the Universal Time Calendar for all date difference calculations?

As UTC does not have daylight savings the simple
days=(UTC_cal1.getTimeInMillis() - UTC_cal1.getTimeInMillis())/(millisecs_in_day)
will work just fine.

Am I missing something?

Pablo M. said...

I need to change this to get the total number of hours between two dates.

Any help?

Jagadeshwaran M said...

how to get the date difference in form of following:


input:date1=12/12/2009;
date2=12/5/2013;
output=date Difference:
dd/mm/yyyy

Anonymous said...

Very usefull.

Thanks so much.

Anonymous said...

Will this piece of code work if start date and end date are in different timezones .
For Eg. If start_date = 9 Aug 2012 12:00:01 am CEST
and end -date = 9 Nov 2012 12:00:00 am CET?