There are a couple of good answers already. Allow me to see if I can still devise something elegant.
Create just one list of objects
First, I suggest that you don’t want two lists. That’s an anti-pattern described here: Anti-pattern: parallel collections. Since each first day belongs to a corresponding last day from the other list, everything will be more convenient and less error-prone when you keep them together. For this purpose you may use some library class or devise your own:
public class DateInterval {
LocalDate firstDay;
LocalDate lastDay;
public DateInterval(LocalDate firstDay, LocalDate lastDay) {
if (lastDay.isBefore(firstDay)) {
throw new IllegalArgumentException("Dates in wrong order");
}
this.firstDay = firstDay;
this.lastDay = lastDay;
}
// getters and other stuff
@Override
public String toString() {
return "" + firstDay + " - " + lastDay;
}
}
With this class we need just one list:
LocalDate firstDayOverall = LocalDate.of(2018, Month.DECEMBER, 10);
LocalDate lastDayOverall = LocalDate.of(2019, Month.JANUARY, 6);
if (lastDayOverall.isBefore(firstDayOverall)) {
throw new IllegalStateException("Overall dates in wrong order");
}
LocalDate firstDayOfLastMonth = lastDayOverall.withDayOfMonth(1);
LocalDate currentFirstDay = firstDayOverall;
List<DateInterval> intervals = new ArrayList<>();
while (currentFirstDay.isBefore(firstDayOfLastMonth)) {
intervals.add(new DateInterval(currentFirstDay, currentFirstDay.with(TemporalAdjusters.lastDayOfMonth())));
currentFirstDay = currentFirstDay.withDayOfMonth(1).plusMonths(1);
}
intervals.add(new DateInterval(currentFirstDay, lastDayOverall));
System.out.println("Intervals: " + intervals);
The output from the above code snippet is:
Intervals: [2018-12-10 - 2018-12-31, 2019-01-01 - 2019-01-06]
The special case of first and last day being in the same month is handled by the loop not looping at all and the add
after the loop adding the single interval to the list.
If two lists of dates are required
If you do insist on two lists, the two-arg datesUntil
method is handy:
List<LocalDate> firstDays = new ArrayList<>();
firstDays.add(firstDayOverall);
// first day not to be included in first days
LocalDate endExclusive = lastDayOverall.withDayOfMonth(1).plusMonths(1);
List<LocalDate> remainingFirstDays = firstDayOverall.withDayOfMonth(1)
.plusMonths(1)
.datesUntil(endExclusive, Period.ofMonths(1))
.collect(Collectors.toList());
firstDays.addAll(remainingFirstDays);
System.out.println("First days: " + firstDays);
// Calculate last days as the day before each first day except the first
List<LocalDate> lastDays = remainingFirstDays.stream()
.map(day -> day.minusDays(1))
.collect(Collectors.toCollection(ArrayList::new));
lastDays.add(lastDayOverall);
System.out.println("Last days: " + lastDays);
Output:
First days: [2018-12-10, 2019-01-01]
Last days: [2018-12-31, 2019-01-06]
Aside
At first I had a feeling that the YearMonth
class would make for an elegant solution. I realized, however, that it would have required special treatment of first and last month, so I don’t think it could have led to more elegant code than what we have above. You may try if you like.