6

I need to get the difference in months between two dates, I'm using Joda Time, the problem is this:

DateTime date1 = new DateTime().withDate(2015, 2, 1);
DateTime date2 = new DateTime().withDate(2015, 1, 1);
Months m = Months.monthsBetween(date1, date2);
int monthDif = m.getMonths();//this return 0 

it returns 0 because there is no month in the middle of the two dates, I need to return the difference in months not a few months in between, and add 1 would be problematic when the dates are the same.

Carlos Lopez
  • 117
  • 2
  • 2
  • 9
  • 1
    Even though the above question is about days, it is very similar to your question and the solution is the same, call `DateTime#toLocalDate()` to pass `LocalDate` to `Months#monthsBetween`, instead of `DateTime`. – Tom Jan 13 '16 at 17:40
  • 1
    Why are you using `new Datetime().withDate()` in the first place? It creates DateTime instance of current moment and then changes it (leaving time fields as is). If you want to create just new specific date without time then just use [LocalDate](http://www.joda.org/joda-time/apidocs/org/joda/time/LocalDate.html) and then `Months.monthsBetween(date1, date2)` will work fine. – Ruslan Stelmachenko Jan 13 '16 at 17:44

3 Answers3

20

Changing the first date to 2015-02-02, Joda correctly returns 1 month:

DateTime date1 = new DateTime().withDate(2015, 2, 2);
DateTime date2 = new DateTime().withDate(2015, 1, 1);

System.out.println(Months.monthsBetween(date2, date1).getMonths());
// Returns 1.

So my guess is that because you didn't provide a time portion, Joda cannot be precise about exactly which point in time of 2015-01-01 date2 refers to. You might have as well referred to 23:59:59, in which case a full month wouldn't have elapsed yet, technically.

If you provide a zero time portion explicitly, it works as you initially expected:

DateTime date1 = new DateTime().withDate(2015, 2, 1).withTime(0, 0, 0, 0);
DateTime date2 = new DateTime().withDate(2015, 1, 1).withTime(0, 0, 0, 0);

System.out.println(Months.monthsBetween(date2, date1).getMonths());
// Returns 1.

Therefore, I recommend you specify a 00:00:00 time portion in each date explicitly.

raulk
  • 2,809
  • 15
  • 32
6

While other answers are correct they still mask the real problem.

it returns 0 because there is no month in the middle of the two dates

No. It returns 0 because there is time part of DateTime object. You creating two istances of DateTime filled with current moment in time (with hours, minutes, seconds and milliseconds) and then modify just date part. There is no reasons to do it if you want to compare just two dates. use LocalDate instead.

LocalDate date1 = new LocalDate(2015, 2, 1);
LocalDate date2 = new LocalDate(2015, 1, 1);
Months m = Months.monthsBetween(date1, date2);
int monthDif = Math.abs(m.getMonths());//this return 1

Also need to pay attention to the fact that despite the fact that Months docs say nothing about it, Month can contain negative value if first date is after second date. So we need to use Math.abs to really count the number of months between two dates.

The docs say:

Creates a Months representing the number of whole months between the two specified partial datetimes.

But it isn't true. It really calculates the difference in months. Not the number of months.

Ruslan Stelmachenko
  • 4,987
  • 2
  • 36
  • 51
  • 1
    Yes, my fault. It actually returns `-1` because of `date1` is after `date2`. The `Months.monthsBetween` docs never say that *the period in months* can be negative. :) But it also true for the question and accepted answer and doesn't change anything. I edit my answer to use `Math.abs`. – Ruslan Stelmachenko Jan 13 '16 at 18:40
  • I guess it would be better to explain to OP (in the answer) why it returns -1 with the current code and to use `Months.monthsBetween(date2, date1);` instead. It like calling `X.compareTo(Y)` vs `Y.compareTo(X)`, the result will also be inverted :D. – Tom Jan 13 '16 at 18:47
-1

The way this is calculated depends on the business logic that is to be used. Each month varies in length. One option would be to, in the monthsBetween() function, get the start of the month for both date1 and date2, and compare that.

Something like:

DateTime firstOfMonthDate1 = new DateTime(date1.getYear(), date1.getMonthOfYear(), 1, 0, 0);
DateTime firstOfMonthDate2 = new DateTime(date2.getYear(), date2.getMonthOfYear(), 1, 0, 0);
Months m = Months.monthsBetween(firstOfMonthDate1, firstOfMonthDate2)
CJBS
  • 15,147
  • 6
  • 86
  • 135