Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Java TimeUnit.MILLISECONDS.toDays() gives wrong result

I'm trying to calculate the difference between two days in amount of days. For some reason comparing 01-03-2013 and 01-04-2013 gives the result 30, as does comparing 01-03-2013 and 31-03-2013

Calendar cal = Calendar.getInstance();
cal.clear();

cal.set(2013, Calendar.MARCH, 1);
Date start = cal.getTime();

cal.set(2013, Calendar.APRIL, 1);
Date end = cal.getTime();

long days = TimeUnit.MILLISECONDS.toDays(end.getTime() - start.getTime());
System.out.println("!!! Amount of days : " + String.valueOf(days));

>> 30

cal.set(2013, Calendar.MARCH, 1);
start = cal.getTime();

cal.set(2013, Calendar.MARCH, 31);
end = cal.getTime();

days = TimeUnit.MILLISECONDS.toDays(end.getTime() - start.getTime());
System.out.println("!!! Amount of days : " + String.valueOf(days));

>> 30

Why is this?

like image 794
Morri Avatar asked Feb 03 '14 09:02

Morri


2 Answers

You will get those results if daylight savings started in your time zone on 31 March.

Between 1 March and 1 April, you have 30 24-hour days and one 23-hour day, because of the start of daylight savings. If you divide the total number of milliseconds by 24 x 60 x 60 x 1000, then you will get 30 plus 23/24. This gets rounded down to 30.

like image 182
Dawood ibn Kareem Avatar answered Sep 28 '22 07:09

Dawood ibn Kareem


Time Zone

The correct answer by David Wallace explains that Daylight Saving Time or other anomalies affects the results of your code. Relying on default time zones (or outright ignoring time zones) will get you into this kind of trouble.

Make Span Inclusive-Exclusive

Also, the proper way to define a span of time is to make the beginning inclusive and the ending exclusive. So if you want the month of March, you need to go from first moment of first day to first moment of first day after March (April 1).

For lengthy discussion of this idea, see my other answers such as this one and this one.

Here's a diagram of mine lifted from other answers:

Timeline showing ( >= start of day 1 ) and ( < start of day 8 )

Joda-Time

The java.util.Date/Calendar classes bundled with Java are notoriously troublesome. Avoid them. Use either Joda-Time, or in Java 8, the new java.time.* package (inspired by Joda-Time).

The Joda-Time 2.3 library provides classes dedicated to spans of time: Period, Duration, and Interval. That library also has some handy static utility methods, such as Days.daysBetween.

Joda-Time's DateTime objects do know their own time zone, unlike java.util.Date/Calendar which seem to have a time zone but do not.

// Specify a timezone rather than rely on default.
DateTimeZone timeZone = DateTimeZone.forID( "Europe/Paris" );

DateTime marchFirst = new DateTime( 2013, DateTimeConstants.MARCH, 1, 0, 0, 0, timeZone );
DateTime aprilFirst = new DateTime( 2013, DateTimeConstants.APRIL, 1, 0, 0, 0, timeZone );

int days = Days.daysBetween( marchFirst, aprilFirst).getDays();

Dump to console…

System.out.println( "marchFirst: " + marchFirst );
System.out.println( "aprilFirst: " + aprilFirst ); // Note the change in time zone offset in the output.
System.out.println( "days: " + days );

When run, notice:

  • The correct answer: 31
  • The difference in time zone offset because of Daylight Saving Time in France.
marchFirst: 2013-03-01T00:00:00.000+01:00
aprilFirst: 2013-04-01T00:00:00.000+02:00
days: 31
like image 43
Basil Bourque Avatar answered Sep 28 '22 08:09

Basil Bourque