Calculating the difference in days between two dates in Java sounds like a trivial task, but it turns out to be otherwise. To keep track of time, Java counts the number of milliseconds from the start of January 1, 1970 and this is called the epoch. Many programmers then think that subtracting the millisecond timestamps of two moments and dividing by the number of milliseconds per day is the right way to calculate the difference between two dates. The code below is as simple as it could be, but it is also as wrong. Here is why:
import java.util.GregorianCalendar;
public class DatesDifference {
public static void main(String[] args) {
// Creates a calendar on Thu Jan 01 00:00:00 GMT 2009
// Yes, month parameter is 0 (zero) and it represents January
// Might be better to use Calendar.JANUARY instead, but using the number
// keeps the programmer's mind sharp for date inconsistencies. :)
long milis1 = new GregorianCalendar(2009,0,1,0,0,0).getTimeInMillis();
// Creates a calendar on Wed Jan 14 00:00:00 GMT 2009
long milis2 = new GregorianCalendar(2009,0,14,0,0,0).getTimeInMillis();
// Miliseconds in a day
long milisInADay = 24L*60L*60L*1000L;
long daysDifference = (milis2 - milis1) / milisInADay;
System.out.println("Days difference = " + daysDifference);
}
}
The output of this code would be this:
Days difference = 13
Which is correct, i.e. there are 13 days of difference between the Jan 1st and Jan 14th of the same year.
You will find below a slightly different version of the code above, which includes a little bit more of information on the output for debugging purposes and different dates for the calculation.
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.TimeZone;
public class DatesDifference {
public static void main(String[] args) {
// Creates a calendar on Thu Jan 01 00:00:00 GMT 2009
Calendar gc1 = new GregorianCalendar(2009,9,1,0,0,0);
long milis1 = gc1.getTimeInMillis();
// Creates a calendar on Wed Jan 14 00:00:00 GMT 2009
Calendar gc2 = new GregorianCalendar(2009,9,29,0,0,0);
long milis2 = gc2.getTimeInMillis();
// Miliseconds in a day
long milisInADay = 24L*60L*60L*1000L;
long daysDifference = (milis2 - milis1) / milisInADay;
System.out.println("Days difference = " + daysDifference);
System.out.println("gc1 => " + gc1.getTimeZone());
System.out.println("gc2 => " + gc2.getTimeZone());
System.out.println("Date is => " + new Date());
}
}
Here is the output of the execution of this code:
Days difference = 28
gc1 => sun.util.calendar.ZoneInfo[id="GMT",offset=0,dstSavings=0,useDaylight=false,transitions=0,lastRule=null]
gc2 => sun.util.calendar.ZoneInfo[id="GMT",offset=0,dstSavings=0,useDaylight=false,transitions=0,lastRule=null]
Date is => Thu Sep 10 05:31:21 GMT 2009
Every thing looks fine, because logically enough, the number of days between Oct 1st and Oct 29th of the same year is indeed 28.
It is, however, very simple to break this code. Just pass a timezone information to the Virtual Machine which includes DST information, like for example, "America/Sao_Paulo" (the parameter would be -Duser.timezone=America/Sao_Paulo). And now here is the new output:
Days difference = 27
gc1 => sun.util.calendar.ZoneInfo[id="America/Sao_Paulo",offset=-10800000,dstSavings=3600000,useDaylight=true,transitions=129,lastRule=java.util.SimpleTimeZone[id=America/Sao_Paulo,offset=-10800000,dstSavings=3600000,useDaylight=true,startYear=0,startMode=3,startMonth=9,startDay=15,startDayOfWeek=1,startTime=0,startTimeMode=0,endMode=3,endMonth=1,endDay=15,endDayOfWeek=1,endTime=0,endTimeMode=0]]
gc2 => sun.util.calendar.ZoneInfo[id="America/Sao_Paulo",offset=-10800000,dstSavings=3600000,useDaylight=true,transitions=129,lastRule=java.util.SimpleTimeZone[id=America/Sao_Paulo,offset=-10800000,dstSavings=3600000,useDaylight=true,startYear=0,startMode=3,startMonth=9,startDay=15,startDayOfWeek=1,startTime=0,startTimeMode=0,endMode=3,endMonth=1,endDay=15,endDayOfWeek=1,endTime=0,endTimeMode=0]]
Date is => Thu Sep 10 02:40:51 BRT 2009
Now we have a serious problem, because the exact same code behaves differently (and erroneous) depending on the timezone (offset and DST) information.
Comments
Post new comment