java.time
The Joda-Time team has advised us to migrate to the java.time framework built into Java 8 and later. Much of the java.time framework has been back-ported to Java 6 & 7 and further adapted to Android.
The LocalDate
class represents date-only values without time-of-day and without time zone.
LocalDate start = LocalDate.parse ( "2013-04-17" );
LocalDate stop = LocalDate.parse ( "2013-05-21" );
if ( stop.isBefore ( start ) ) {
System.out.println ( "ERROR - stop before start" );
// FIXME: Handle error.
}
The YearMonth
class represents, well, a year and a month combined. Perfect for tracking your desired results. Using this class makes your code type-safe with guaranteed valid values as opposed to using mere strings or numbers.
YearMonth startYm = YearMonth.from ( start );
YearMonth stopYm = YearMonth.from ( stop );
We create a SortedMap
where a YearMonth
key maps to a Integer
value (the number of days) to collect our results. TreeMap
is our chosen implementation.
SortedMap<YearMonth , Integer> map = new TreeMap<> ();
The example code does not assume we are bridging only a pair of months. If multiple months are in between, we ask YearMonth
for the number of days in that month. We loop YearMonth
by YearMonth
, obtaining a number of days each time.
We do if-else tests for each of five possible cases:
- Single month
- The start and stop are within a single month.
- Multiple months
- On first month
- On any in-between months
- On last month
- Impossible
else
- Should be impossible to reach unless we made a mistake in our logic or coding.
In each case we capture the number of days to collect for that YearMonth
.
When calling the between
method, we must adjust for its use of the Half-Open approach to handling spans of time. In this approach the beginning is inclusive while the ending is exclusive. Generally this is the best route. But the logic in the Question is otherwise, so we adjust. I strongly suggest undoing these adjustments and instead adjust your inputs. Consistent use of Half-Open will make date-time handling much easier.
YearMonth yearMonth = startYm;
do {
int days = 0;
if ( startYm.equals ( stopYm ) ) { // If within the same (single) month.
days = ( int ) ChronoUnit.DAYS.between ( start , stop );
} else if ( yearMonth.equals ( startYm ) ) { // If on the first month of multiple months, count days.
days = ( int ) ChronoUnit.DAYS.between ( start , startYm.plusMonths ( 1 ).atDay ( 1 ) ); // Get first of next month, to accommodate the `between` method’s use of Half-Open logic.
} else if ( yearMonth.isAfter ( startYm ) && yearMonth.isBefore ( stopYm ) ) { // If on the in-between months, ask for the days of that month.
days = yearMonth.lengthOfMonth ();
} else if ( yearMonth.equals ( stopYm ) ) { // If on the last of multiple months.
days = ( int ) ChronoUnit.DAYS.between ( stopYm.atDay ( 1 ).minusDays ( 1 ) , stop ); // Get last day of previous month, to accommodate the `between` method’s use of Half-Open logic.
} else {
System.out.println ( "ERROR - Reached impossible point." );
// FIXME: Handle error condition.
}
map.put ( yearMonth , days ); // Cast long to int, auto-boxed to Integer.
// Prep for next loop.
yearMonth = yearMonth.plusMonths ( 1 );
} while ( ! yearMonth.isAfter ( stopYm ) );
Dump to console.
System.out.println ( "start: " + start + " | stop: " + stop + " | map: " + map );
start: 2013-04-17 | stop: 2013-05-21 | map: {2013-04=14, 2013-05=21}