1
votes

I want to count the months between two dates, and only compare with their months while ignoring the days, so 2012-01-31 and 2012-02-01 should have 1 month difference between them.

I do this with joda-time:

import org.joda.time.*;
PeriodType yearMonth = PeriodType.yearMonthDay().withDaysRemoved();
LocalDate dt1 = new LocalDate(2012, 1, 31);
LocalDate dt2 = new LocalDate(2012, 2, 1);
int months = new Period(dt1, dt2, yearMonth).getMonths();
System.out.println(months);

But I got printed output:

0

Although I used .withDaysRemoved(), but it's not working. Do I misuse it?

2

2 Answers

0
votes

java.time

The Joda-Time project is now in maintenance mode. The team advises migration to the java.time classes.

LocalDate

As in Joda-Time, a LocalDate represents a date-only value without time-of-day and without a time zone.

LocalDate start = LocalDate.of ( 2012 , Month.JANUARY , 31 );
LocalDate stop = LocalDate.of ( 2012 , Month.MARCH , 1 );

Establish the place to hold our results.

long countMonths = 0;

YearMonth

The YearMonth class represents, well, a year and a month. This is the conduit to considering whole months, as you want, rather than number of days.

YearMonth ymStart = YearMonth.from ( start );
YearMonth ymStop = YearMonth.from ( stop );

This YearMonth class has methods for comparison such as isAfter, isBefore, and equals.

The ChronoUnit class can calculate elapsed months between a pair of Temporal objects. The YearMonth class fortunately implements Temporal and works with ChronoUnit.

if ( ymStart.isAfter ( ymStop ) ) {
    // Handle error condition. Unexpected input values.
} else if ( ymStart.equals ( ymStop ) ) {
    countMonths = 0;
} else { // Else different months.
    countMonths = ChronoUnit.MONTHS.between ( ymStart , ymStop );
}

Dump to console.

System.out.println ( "start: " + start + " | stop: " + stop + " | countMonths: " + countMonths );

start: 2012-01-31 | stop: 2012-03-01 | countMonths: 2

Half-Open

The results above are based on the Half-Open approach to defining a span of time where the beginning is inclusive while the ending is exclusive. This approach is commonly used with date-time work, including throughout the java.time classes.

So, for example, lunch period starts at the first moment of the noon hour and runs up to, but not including, the first moment of 1 PM.

Another example: A week would start on a Monday and run up to, but not including, the following Monday. Or in this case, a pair of year-months from January and going up to, but not including, March; they yield a span of 2 months.

I believe using the Half-Open approach throughout your code leads to more clarity and less bugs. But if you or your users demand otherwise, add one to the results above.

About java.time

The java.time framework is built into Java 8 and later. These classes supplant the troublesome old date-time classes such as java.util.Date, .Calendar, & java.text.SimpleDateFormat.

The Joda-Time project, now in maintenance mode, advises migration to java.time.

To learn more, see the Oracle Tutorial. And search Stack Overflow for many examples and explanations.

Much of the java.time functionality is back-ported to Java 6 & 7 in ThreeTen-Backport and further adapted to Android in ThreeTenABP (see How to useā€¦).

The ThreeTen-Extra project extends java.time with additional classes. This project is a proving ground for possible future additions to java.time. You may find some useful classes here such as Interval, YearWeek, YearQuarter, and more.

0
votes

The month arithmetic of LocalDate will and must always use the day-of-month for its calculations. The fact that you have specified the period type such that the days shall not be used only means: The final result shall not show any days (but the day-of-month-component of LocalDate is internally still used).

However, if you use another type, namely YearMonth, then this type defines a different month arithmetic:

LocalDate dt1 = new LocalDate(2012, 1, 31);
LocalDate dt2 = new LocalDate(2012, 2, 1);

YearMonth ym1 = new YearMonth(dt1.getYear(), dt1.getMonthOfYear());
YearMonth ym2 = new YearMonth(dt2.getYear(), dt2.getMonthOfYear());

PeriodType yearMonth = PeriodType.yearMonthDay();
System.out.println(new Period(ym1, ym2, yearMonth).getMonths()); // 1
System.out.println(new Period(ym1, ym2, yearMonth.withDaysRemoved()).getMonths()); // 1

Here you can also see that - in context of YearMonth - suppressing the days in PeriodType is not relevant.