0
Date d = new Date(today.getTimeInMillis());
Date d1 = new Date(dueDate.getTimeInMillis());

int daysUntil = (int) ((d1.getTime() - d.getTime())/ (1000 * 60 * 60 * 24));

Using the above code, where today is a calendar set to 00:00 on the current day, and dueDate is set to 00:00 on the date I am comparing today to, my results from this differ.

There is something in this which varies, making my output either x or x+1 where x is the correct answer.

What is the issue here, and what can I do to make it more stable?

TomRichardson
  • 5,933
  • 5
  • 27
  • 30
  • Note: I have managed to fix this by simply adding 1000 to 'dueDate.getTimeInMillis()', however this is more of a workaround. What is causing this issue and how can it be fixed? – TomRichardson Jan 22 '14 at 01:59
  • 3
    Can you print out the value of `today.getTimeInMillis()` and `dueDate.getTimeInMillis()`? – Sotirios Delimanolis Jan 22 '14 at 02:03
  • I wonder if this caused by leap seconds? - in particular, reading the spec for Date, "A second is represented by an integer from 0 to 61; the values 60 and 61 occur only for leap seconds and even then only in Java implementations that actually track leap seconds correctly." So you can't assume a day is 60*60*24 seconds long. – Jems Jan 22 '14 at 02:04
  • 2
    "*`today` is a calendar set to 00:00*" what about milliseconds? They are also part of `getTimeInMillis()` result. – Pshemo Jan 22 '14 at 02:08

2 Answers2

1

Vague Question

You do not provide actual values, so we cannot determine precisely the problem. We do not know what the today and dueDate variables are.

Outmoded

The question is now outmoded, as the troublesome old date-time classes including java.util.Date/.Calendar have been supplanted by the new java.time framework. See Tutorial. Defined by JSR 310, inspired by Joda-Time, and extended by the ThreeTen-Extra project.

In java.time:

  • An Instant is a moment on the timeline in UTC.
  • A ZoneId represents a time zone. Use proper time zone names, never the 3-4 letter codes like "EST" or "IST" as they are neither standardized nor unique.
  • Conceptually, ZonedDateTime = Instant + ZoneId.

ThreeTen-Extra

Unfortunately, java.time does not include a facility for calculating days elapsed between date-time values. We can use the ThreeTen-Extra project and its Days class with between method to provide that calculation. The ThreeTen-Extra project is a collection of features deemed non-essential for java.time during the JSR process.

ZoneId zoneId = ZoneId.of ( "America/Montreal" );
ZonedDateTime now = ZonedDateTime.now ( zoneId );
ZonedDateTime then = now.minusDays ( 4 );
ZonedDateTime due = now.plusDays ( 3 );
Integer days = org.threeten.extra.Days.between ( then , due ).getAmount ();

Dump to console.

System.out.println ( "From then: " + then + " to due: " + due + " = days: " + days );

From then: 2015-10-31T16:01:13.082-04:00[America/Montreal] to due: 2015-11-07T16:01:13.082-05:00[America/Montreal] = days: 7

Joda-Time

For Android or older versions of Java, use the excellent Joda-Time library.

The Days class is smart and handles anomalies such as Daylight Saving Time (DST).

Note that unlike java.util.Date, a Joda-Time DateTime object knows its own time zone.

// Specify a time zone rather than rely on default.
DateTimeZone timeZone = DateTimeZone.forID( "America/Regina" ); // Or "Europe/London".

DateTime now = new DateTime( timeZone );
DateTime startOfToday = now.withTimeAtStartOfDay();

DateTime fewDaysFromNow = now.plusDays( 3 );
DateTime startOfAnotherDay = fewDaysFromNow.withTimeAtStartOfDay();

Days days = Days.daysBetween( startOfToday, startOfAnotherDay );

Dump to console…

System.out.println( days.getDays() + " days between " + startOfToday + " and " + startOfAnotherDay + "." );

When run…

3 days between 2014-01-21T00:00:00.000-06:00 and 2014-01-24T00:00:00.000-06:00.
Basil Bourque
  • 303,325
  • 100
  • 852
  • 1,154
  • Yes, "non-essential for java.time" is the right label for Threeten-Extra regarding the calculation of elapsed days ;-) Instead of using the class `Days` you can simply use `java.time.temporal.ChronoUnit.DAYS.between(then, due)`. No need for Threeten-Extra here , and Java-8 is even simpler - one method call less. – Meno Hochschild Feb 18 '16 at 16:07
1

There are mainly two reasons why your code is broken:

  • second parts or millisecond fractions (you might have overlooked)
  • daylight saving effects

I demonstrate and explain the second reason.

SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
Date d1 = sdf.parse("2016-03-20");
Date d2 = sdf.parse("2016-03-28");
int daysUntil = (int) ((d2.getTime() - d1.getTime()) / (1000 * 60 * 60 * 24));
System.out.println(daysUntil); // 7 (should be 8)

The code was run in timezone "Europe/Berlin". Due to the change from winter time to summer time causing a jump of clocks by one hour forward on 2016-03-27 at 2 am, there is one hour missing. One day has only 23 hours so the division by 24 yields zero resulting in counting one day less.

What can you do else?

Your workaround adding 1000 milliseconds to dueDate sounds as if you have overlooked possible millisecond deltas in your input. This might solve a special case but will usually not be sufficient to solve the daylight saving problem, too. Whatever you choose on base of java.util.Date it is a more or less an evil hack.

The best I have in mind (within the scope of Android-built-in stuff) is to construct an instance of java.util.GregorianCalendar and to add successively one day after one until you have passed the due-date, and then count how many days you have added. Not elegant and errorprone because varying millisecond parts can easily be overlooked here, too.

Otherwise you can try various external libraries for this task. There are four available on Android which can calculate elapsed days in an easy way.

Meno Hochschild
  • 42,708
  • 7
  • 104
  • 126