Getting number of (Months / Days / Hours) between 2 Date / Calendar Objects

In an application I am working on, I need to compute the number of time intervals between to date / times. I currently have 2 Date objects, but makeing Calendar objects is no problem. The problem it there doesn't appear to be any 'easy' way to do this; there isn't a method in Date or Calendar to do this. (I'm using Java 1.4).

The best I could come up with would be something like this:

publicint calcInterval(Calendar start, Calendar end,int field){

int count = 0;

if(start.after(end)){

Calendar temp = start;

start = end;

end = temp;

}

Calendar temp = (Calendar)start.clone();

while(!temp.after(end)){

temp.add(field,1);

count++;

}

return count;

}

Is there any API for doing this that I'm missing? Obviously, this implementation isn't efficient for computing 'days' or smaller intervals, however, for anything larger than days (months or years) you run into the problem of know knowing how many days are in a particular month or year, without complex math that the calendar class is supposed to take care of for you. How should this calculation be done?

[1558 byte] By [brisingea] at [2007-10-1 5:28:32]
# 1

There is a straightforward way (may or may not be 'easy'). You can always call getTime() on a java.util.Date object and it will return a long. Subtract these two longs and you will get the milliseconds between two dates. You can then use simple arithmetic to step up to seconds, minutes, days, etc.

Your complication will come when you get to months. There is no easy way around this. What is the interval between Feb 16 and Mar 15? In one implementation, you could simply say 1.0 months (30 days). However, February has 28 (or 29), whereas March has 31 days. So, if you are in insurance and want to pro-rate a uniform 'month', the simplistic solution will not work. In other cases, it might be fine.

One 'hack' you can do to get around this is to calculate your millisecond interval and then pick an arbitrary, fixed date, say 1/1/1970. Create a Calendar object with your fixed date, and then call add(Calendar.SECOND, milliseconds * 1000). You can then call calendar.get(desiredField). Note, this will not work terribly well for something that changes from year to year (such as what week of the year you are in).

- Saish

Saisha at 2007-7-9 13:23:47 > top of Java-index,Administration Tools,Sun Connection...
# 2

BTW, here is a helper class I wrote a while ago. I never came back to it, and it is not fully tested. But it may point you in a direction.

- Saish

public class DateHelper extends Object {

/////////////////////

// Class Variables //

/////////////////////

final static public String SHORT_DATE = "MM/dd"; // 5 Done

final static public String LONG_DATE = "MM/dd/yy"; // 8

final static public String SHORT_TIME = "hh:mm aa"; // 8

final static public String LONG_TIME = "hh:mm:ss aa"; // 11

final static public String SHORT_TIME_MILITARY = "HH:mm"; // 5 Done

final static public String LONG_TIME_MILITARY = "HH:mm:ss"; // 8

final static public String SHORT_DATE_TIME = SHORT_DATE + " " + SHORT_TIME;

final static public String SHORT_DATE_TIME_MILITARY = SHORT_DATE + " " + SHORT_TIME_MILITARY;

final static public String LONG_DATE_TIME = LONG_DATE + " " + LONG_TIME;

final static public String LONG_DATE_TIME_MILITARY = LONG_DATE + " " + LONG_TIME_MILITARY;

final static public String DATE_DELIMITER = "/";

final static public String TIME_DELIMITER = ":";

final static public String DATE_TIME_DELIMITER = " ";

final static protected String MIDNIGHT = "0:00";

static private Date timezoneOffset;

//////////////////

// Constructors //

//////////////////

public DateHelper() {

super();

}

////////////////////

// Public Methods //

////////////////////

static public Date parse(String text)

throws ParseException {

String target = text.trim();

boolean isDate = target.indexOf(DATE_DELIMITER) >= 0;

boolean isTime = target.indexOf(TIME_DELIMITER) >= 0;

// Process based on the length

StringTokenizer tokenizer = new StringTokenizer(target, DATE_TIME_DELIMITER);

String format = "";

if (isDate) {

format = getDateFormat(tokenizer.nextToken());

if (tokenizer.hasMoreTokens())

format = format + DATE_TIME_DELIMITER;

}

if (isTime) {

String timeString = tokenizer.nextToken();

if (tokenizer.hasMoreTokens())

format = format + getTimeFormat(timeString + " " + tokenizer.nextToken());

else

format = format + getMilitaryTimeFormat(timeString);

}

// Parse the date with the applicable format

DateFormat formatter = new SimpleDateFormat(format);

try {

return formatter.parse(text);

}

catch (java.text.ParseException e) {

throw new ParseException ("Unable to parse '" + target + "'", e);

}

}

static public Date parse(String text, String format)

throws ParseException {

DateFormat formatter = new SimpleDateFormat(format);

try {

return formatter.parse(text.trim());

}

catch (java.text.ParseException e) {

throw new ParseException ("Unable to parse '" + text + "'", e);

}

}

static public String format(Date date, String format) {

DateFormat formatter = new SimpleDateFormat(format);

return formatter.format(date);

}

static public double getDaysBetween(Date first, Date second) {

double milliElapsed = second.getTime() - first.getTime();

double daysElapsed = (milliElapsed / 24F / 3600F / 1000F);

return (Math.round(daysElapsed * 100F) / 100F);

}

static public double getHoursBetween(Date first, Date second) {

double milliElapsed = second.getTime() - first.getTime();

double hoursElapsed = (milliElapsed / 3600F / 1000F);

return (Math.round(hoursElapsed * 100F) / 100F);

}

static final public Date add(Date first, Date second)

throws ParseException {

if (timezoneOffset == null)

timezoneOffset = parse(MIDNIGHT);

return new Date(first.getTime() + second.getTime() - timezoneOffset.getTime());

}

static final public Date add(String first, String second)

throws ParseException {

return add(parse(first), parse(second));

}

static final public Date add(Date first, double days, double hours, double minutes) {

double additionalTime = (days * 24F * 3600F * 1000F ) + (hours * 3600F * 1000F) +

(minutes * 60 * 1000);

return new Date(Math.round((double) first.getTime() + additionalTime));

}

static public double getCombinedDays(Date first, Date second) {

double milliCombined = first.getTime() + second.getTime();

double daysCombined = (milliCombined / 24F / 3600F / 1000F);

return (Math.round(daysCombined * 100F) / 100F);

}

static public double getCombinedHours(Date first, Date second) {

double milliCombined = first.getTime() + second.getTime();

double hoursCombined = (milliCombined / 3600F / 1000F);

return (Math.round(hoursCombined * 100F) / 100F);

}

static public Date getNextDay(Date target) {

Date next = new Date(target.getTime() + (24 * 3600 * 1000));

Calendar calendar = new GregorianCalendar();

calendar.setTime(next);

calendar.set(Calendar.MILLISECOND, 0);

return calendar.getTime();

}

static public boolean isSameDay(Date first, Date second) {

Calendar calendar1 = new GregorianCalendar();

calendar1.setTime(first);

Calendar calendar2 = new GregorianCalendar();

calendar2.setTime(second);

return ( (calendar1.get(Calendar.YEAR) == calendar2.get(Calendar.YEAR)) &&

(calendar1.get(Calendar.MONTH) == calendar2.get(Calendar.MONTH)) &&

(calendar1.get(Calendar.DAY_OF_MONTH) == calendar2.get(Calendar.DAY_OF_MONTH)) );

}

// This algorithm can undoubtedly be made MUCH better

static public boolean isConsecutiveDay(Date first, Date second) {

return (Math.abs(getDaysBetween(stripTime(first), stripTime(second))) <= 1.0F);

}

static public Date stripTime(Date dateTime) {

Calendar calendar = new GregorianCalendar();

calendar.setTime(dateTime);

calendar.set(Calendar.HOUR, 0);

calendar.set(Calendar.MINUTE, 0);

calendar.set(Calendar.SECOND, 0);

calendar.set(Calendar.MILLISECOND, 0);

return calendar.getTime();

}

/////////////////////

// Private Methods //

/////////////////////

static private String getDateFormat(String dateString)

throws ParseException {

int length = dateString.length();

if (length == SHORT_DATE.length())

return SHORT_DATE;

else if (length == LONG_DATE.length())

return LONG_DATE;

else

throw new ParseException("Invalid date format '" + dateString + "'");

}

static private String getTimeFormat(String timeString)

throws ParseException {

int length = timeString.length();

if ((length == SHORT_TIME.length()) || (length == SHORT_TIME.length() - 1))

return SHORT_TIME;

else if ((length == LONG_TIME.length()) || (length == LONG_TIME.length() - 1))

return LONG_TIME;

throw new ParseException("Invalid time format '" + timeString + "'");

}

static private String getMilitaryTimeFormat(String timeString)

throws ParseException {

int length = timeString.length();

if ((length == SHORT_TIME_MILITARY.length()) || (length == SHORT_TIME_MILITARY.length() - 1))

return SHORT_TIME_MILITARY;

else if ((length == LONG_TIME_MILITARY.length()) || (length == LONG_TIME_MILITARY.length() - 1))

return LONG_TIME_MILITARY;

throw new ParseException("Invalid military time format '" + timeString + "'");

}

}

Saisha at 2007-7-9 13:23:47 > top of Java-index,Administration Tools,Sun Connection...
# 3

Hello,

we just found a nice solution for "our" definition of monthCount:

public static final int getMonthLapse(GregorianCalendar calStart, GregorianCalendar calEnd) {

calStart = (GregorianCalendar) calStart.clone();

GregorianCalendar calOrgEnd = calEnd;

calEnd = (GregorianCalendar) calEnd.clone();

// convert endDate to inclusive as well

calEnd.add(Calendar.DAY_OF_YEAR, 1);

int count = 0;

Calendar cal;

do {

cal = (Calendar) calStart.clone();

++count;

int day = cal.get(GregorianCalendar.DAY_OF_MONTH);

int month = cal.get(GregorianCalendar.MONTH);

int year = cal.get(GregorianCalendar.YEAR);

cal = new GregorianCalendar(year, month + count, day);

} while (cal.before(calEnd) || cal.equals(calEnd));

int result = count - 1;

return result;

}

FYI: cal.add() and cal.roll() proved to be not usable since they're "correction" for illegal dates killed the desired result...

Hope it helps.

Best regards,

Roland Ramm and Ralph Gutmann.

rgutmanna at 2007-7-9 13:23:47 > top of Java-index,Administration Tools,Sun Connection...
# 4

Hi again,

just a fix. for our last solution were one new test case has not been correctly adressed. The new version is:

public static final int getMonthLapse(GregorianCalendar calStart, GregorianCalendar calEnd) {

// convert endDate to be exclusive, but remember original endOrgDate

GregorianCalendar calOrgEnd = calEnd;

calEnd = (GregorianCalendar) calEnd.clone();

calEnd.add(Calendar.DAY_OF_YEAR, 1);

if (DEBUG)

System.out.println("\nFrom: " + formatCalendar(calStart) + " - To: " + formatCalendar(calOrgEnd) + " (" + formatCalendar(calEnd) + ")");

int count = 0;

Calendar tmpCal;

do {

tmpCal = (Calendar) calStart.clone();

++count;

int orgDay = tmpCal.get(GregorianCalendar.DAY_OF_MONTH);

tmpCal.add(GregorianCalendar.MONTH, count);

int newDay = tmpCal.get(GregorianCalendar.DAY_OF_MONTH);

if (newDay < orgDay)

tmpCal.add(GregorianCalendar.DAY_OF_YEAR, 1);

if (DEBUG)

System.out.print(" - " + formatCalendar(tmpCal));

} while (tmpCal.before(calEnd) || tmpCal.equals(calEnd));

int result = count - 1;

if (DEBUG)

System.out.println("\n - last: " + formatCalendar(tmpCal) + " - result: " + result);

return result;

}

Hope it helps somebody.

Best regards,

Roland Ramm and Ralph Gutmann.

rgutmanna at 2007-7-9 13:23:47 > top of Java-index,Administration Tools,Sun Connection...