Java - 从 2 个日期按月创建一组日期
Java - create group of dates by Month from 2 dates
使用 Java 8
目标
从两个日期(例如:firstDay 2018-09-01 和 lastDay 2018-11-10),我想创建两个按月创建的 firstDay 和 lastDay 数组。例如:
List<LocalDate> firstDays = [2018-09-01,2018-10-01,2018-11-01]
List<LocalDate> lastDays = [2018-09-30, 2018-10-31,2018-11-10]
最终,我希望这种方法也适用多年(例如:firstDay 2018-12-10 和 lastDay 2019-01-06)。
问题
我现在不知道用什么来实现那个目标。我还在寻找。请问你能帮帮我吗?
您可以像这样使用 plusMonth
:
LocalDate firstDay = LocalDate.parse("2018-09-01");
LocalDate lastDay = LocalDate.parse("2018-11-10");
Long timeBetween = ChronoUnit.MONTHS.between(firstDay, lastDay);
// First day of firstDays is not changeable so add it like it is
List<LocalDate> firstDays = new ArrayList<>(Arrays.asList(firstDay));
// Generate the dates between the start and end date
firstDays.addAll(LongStream.rangeClosed(1, timeBetween)
.mapToObj(f -> firstDay.withDayOfMonth(1).plusMonths(f))
.collect(Collectors.toList()));
// For the lastDays, generate the dates
List<LocalDate> lastDays = LongStream.range(0, timeBetween)
.mapToObj(f -> {
LocalDate newDate = firstDay.plusMonths(f);
return newDate.withDayOfMonth(newDate.lengthOfMonth());
}).collect(Collectors.toList());
// Last day of lastDays is not changeable so add it like it is
lastDays.add(lastDay);
输出
[2018-09-01, 2018-10-01, 2018-11-01]
[2018-09-30, 2018-10-31, 2018-11-10]
以迭代方式处理边缘情况:
LocalDate startDate = LocalDate.of(2018, 9, 1);
LocalDate endDate = LocalDate.of(2018, 11, 10);
List<LocalDate> firstDays = new ArrayList<>();
List<LocalDate> lastDays = new ArrayList<>();
LocalDate firstOfMonth = startDate.withDayOfMonth(1);
LocalDate lastOfMonth = startDate.withDayOfMonth(startDate.lengthOfMonth());
while (firstOfMonth.isBefore(endDate)) {
firstDays.add(firstOfMonth.isBefore(startDate) ? startDate : firstOfMonth);
lastDays.add(endDate.isBefore(lastOfMonth) ? endDate : lastOfMonth);
firstOfMonth = firstOfMonth.plus(1, ChronoUnit.MONTHS);
lastOfMonth = firstOfMonth.withDayOfMonth(firstOfMonth.lengthOfMonth());
}
System.out.println(firstDays);
System.out.println(lastDays);
输出:
[2018-09-01, 2018-10-01, 2018-11-01]
[2018-09-30, 2018-10-31, 2018-11-10]
已经有几个很好的答案了。让我看看我还能不能设计出优雅的东西来。
只创建一个对象列表
首先,我建议你不要两个列表。这是此处描述的反模式:Anti-pattern: parallel collections。由于每个第一天都属于另一个列表中相应的最后一天,所以当您将它们放在一起时,一切都会更方便,更不容易出错。为此,您可以使用一些库 class 或设计您自己的库:
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;
}
}
有了这个 class 我们只需要一个列表:
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);
以上代码片段的输出是:
Intervals: [2018-12-10 - 2018-12-31, 2019-01-01 - 2019-01-06]
第一天和最后一天在同一个月的特殊情况由根本不循环的循环处理,循环后的 add
将单个间隔添加到列表中。
如果需要两个日期列表
如果您坚持使用两个列表,则双参数 datesUntil
方法很方便:
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);
输出:
First days: [2018-12-10, 2019-01-01]
Last days: [2018-12-31, 2019-01-06]
放在一边
起初我觉得 YearMonth
class 会是一个优雅的解决方案。然而,我意识到它需要对第一个月和最后一个月进行特殊处理,所以我认为它不会产生比我们上面的代码更优雅的代码。喜欢的可以试试
使用 Java 8
目标
从两个日期(例如:firstDay 2018-09-01 和 lastDay 2018-11-10),我想创建两个按月创建的 firstDay 和 lastDay 数组。例如:
List<LocalDate> firstDays = [2018-09-01,2018-10-01,2018-11-01]
List<LocalDate> lastDays = [2018-09-30, 2018-10-31,2018-11-10]
最终,我希望这种方法也适用多年(例如:firstDay 2018-12-10 和 lastDay 2019-01-06)。
问题
我现在不知道用什么来实现那个目标。我还在寻找。请问你能帮帮我吗?
您可以像这样使用 plusMonth
:
LocalDate firstDay = LocalDate.parse("2018-09-01");
LocalDate lastDay = LocalDate.parse("2018-11-10");
Long timeBetween = ChronoUnit.MONTHS.between(firstDay, lastDay);
// First day of firstDays is not changeable so add it like it is
List<LocalDate> firstDays = new ArrayList<>(Arrays.asList(firstDay));
// Generate the dates between the start and end date
firstDays.addAll(LongStream.rangeClosed(1, timeBetween)
.mapToObj(f -> firstDay.withDayOfMonth(1).plusMonths(f))
.collect(Collectors.toList()));
// For the lastDays, generate the dates
List<LocalDate> lastDays = LongStream.range(0, timeBetween)
.mapToObj(f -> {
LocalDate newDate = firstDay.plusMonths(f);
return newDate.withDayOfMonth(newDate.lengthOfMonth());
}).collect(Collectors.toList());
// Last day of lastDays is not changeable so add it like it is
lastDays.add(lastDay);
输出
[2018-09-01, 2018-10-01, 2018-11-01]
[2018-09-30, 2018-10-31, 2018-11-10]
以迭代方式处理边缘情况:
LocalDate startDate = LocalDate.of(2018, 9, 1);
LocalDate endDate = LocalDate.of(2018, 11, 10);
List<LocalDate> firstDays = new ArrayList<>();
List<LocalDate> lastDays = new ArrayList<>();
LocalDate firstOfMonth = startDate.withDayOfMonth(1);
LocalDate lastOfMonth = startDate.withDayOfMonth(startDate.lengthOfMonth());
while (firstOfMonth.isBefore(endDate)) {
firstDays.add(firstOfMonth.isBefore(startDate) ? startDate : firstOfMonth);
lastDays.add(endDate.isBefore(lastOfMonth) ? endDate : lastOfMonth);
firstOfMonth = firstOfMonth.plus(1, ChronoUnit.MONTHS);
lastOfMonth = firstOfMonth.withDayOfMonth(firstOfMonth.lengthOfMonth());
}
System.out.println(firstDays);
System.out.println(lastDays);
输出:
[2018-09-01, 2018-10-01, 2018-11-01]
[2018-09-30, 2018-10-31, 2018-11-10]
已经有几个很好的答案了。让我看看我还能不能设计出优雅的东西来。
只创建一个对象列表
首先,我建议你不要两个列表。这是此处描述的反模式:Anti-pattern: parallel collections。由于每个第一天都属于另一个列表中相应的最后一天,所以当您将它们放在一起时,一切都会更方便,更不容易出错。为此,您可以使用一些库 class 或设计您自己的库:
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;
}
}
有了这个 class 我们只需要一个列表:
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);
以上代码片段的输出是:
Intervals: [2018-12-10 - 2018-12-31, 2019-01-01 - 2019-01-06]
第一天和最后一天在同一个月的特殊情况由根本不循环的循环处理,循环后的 add
将单个间隔添加到列表中。
如果需要两个日期列表
如果您坚持使用两个列表,则双参数 datesUntil
方法很方便:
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);
输出:
First days: [2018-12-10, 2019-01-01] Last days: [2018-12-31, 2019-01-06]
放在一边
起初我觉得 YearMonth
class 会是一个优雅的解决方案。然而,我意识到它需要对第一个月和最后一个月进行特殊处理,所以我认为它不会产生比我们上面的代码更优雅的代码。喜欢的可以试试