Java 日期范围,用户希望返回两个给定端点之间的日期流,可以使用 Java 9 为 LocalDate
类新增的 datesUntil
方法。较之 java.util.Date
、java.util.Calendar
以及 java.sql.Timestamp
类,Java 8 引入的 Date-Time API 是一种巨大改进。
Java 日期范围 问题描述
用户希望返回两个给定端点之间的日期流。
Java 日期范围 解决方案
使用 Java 9 为 LocalDate
类新增的 datesUntil
方法。
Java 日期范围 具体实例
较之 java.util.Date
、java.util.Calendar
以及 java.sql.Timestamp
类,Java 8 引入的 Date-Time API 是一种巨大改进。而 Java 9 新增的 datesUntil
方法解决了 Date-Time API 中一个令人头疼的问题:难以方便地创建日期流。
在 Java 8 中,创建日期流最简单的方式是以初始日期为基准,再添加一个偏移量。例如,为返回相隔一周的两个给定端点之间的所有天数,我们可能会写出如例 10-34 所示的代码。
例 10-34 返回两个日期之间的天数(存在问题)
public List<LocalDate> getDays_java8(LocalDate start, LocalDate end) {
Period period = start.until(end);
return IntStream.range(0, period.getDays()) ➊
.mapToObj(start:plusDays)
.collect(Collectors.toList());
}
➊ 实为陷阱!正确实现参见例 10-35。
程序首先计算两个日期之间的 Period
,然后在二者之间创建一个 IntStream
。执行程序,观察结束日期和开始日期相隔一周时的情况:
LocalDate start = LocalDate.of(2017, Month.JUNE, 10);
LocalDate end = LocalDate.of(2017, Month.JUNE, 17);
System.out.println(dateRange.getDays_java8(start, end));
// [2017-06-10, 2017-06-11, 2017-06-12, 2017-06-13,
// 2017-06-14, 2017-06-15, 2017-06-16]
上述代码看似正确,实则有误。如果将结束日期改为与开始日期相隔正好一个月,很容易就能看出问题所在:
LocalDate start = LocalDate.of(2017, Month.JUNE, 10);
LocalDate end = LocalDate.of(2017, Month.JULY, 10);
System.out.println(dateRange.getDays_java8(start, end));
// []
可以看到,程序没有返回任何值。原因在于 period.getDays
方法返回的只是两个天数字段之间的天数,而非两个日期之间的总天数(getMonths
、getYears
等方法同样如此)。如上所示,由于开始日期和结束日期的天数相同,虽然月份不同,结果仍然是一个大小为 0
的范围。
为解决这个问题,应采用实现 TemporalUnit
接口的 ChronoUnit
枚举,它定义了 DAYS
、MONTHS
等多个枚举常量。Java 8 的正确实现如例 10-35 所示。
例 10-35 返回两个日期之间的天数(正确实现)
public List<LocalDate> getDays_java8(LocalDate start, LocalDate end) {
Period period = start.until(end);
return LongStream.range(0, ChronoUnit.DAYS.between(start, end)) ➊
.mapToObj(start:plusDays)
.collect(Collectors.toList());
}
➊ 正确无误
我们也可以使用 iterate
方法,但需要了解两个日期之间的天数,如例 10-36 所示。
例 10-36
LocalDate
的迭代
public List<LocalDate> getDaysByIterate(LocalDate start, int days) {
return Stream.iterate(start, date -> date.plusDays(1))
.limit(days)
.collect(Collectors.toList());
}
好在 Java 9 引入的新方法使问题得以简化。LocalDate
类新增了一种名为 datesUntil
的方法,其重载形式传入 Period
作为参数。datesUntil
方法的签名如下:
Stream<LocalDate> datesUntil(LocalDate endExclusive)
Stream<LocalDate> datesUntil(LocalDate endExclusive, Period step)
不传入 Period
的 datesUntil
方法实际上相当于将日期增量设置为一天,即 datesUntil(endExclusive)
等效于 datesUntil(endExclusive, Period.ofDays(1))
。
采用 datesUntil
方法返回两个日期之间的天数要简单得多,如例 10-37 所示。
例 10-37 返回两个日期之间的天数(Java 9 实现)
public List<LocalDate> getDays_java9(LocalDate start, LocalDate end) {
return start.datesUntil(end) ➊
.collect(Collectors.toList());
}
public List<LocalDate> getMonths_java9(LocalDate start, LocalDate end) {
return start.datesUntil(end, Period.ofMonths(1)) ➋
.collect(Collectors.toList());
}
❶ 相当于 Period.ofDays(1)
❷ 日期增量为一个月
我们可以使用所有常规的流处理技术对 datesUntil
方法产生的 Stream
操作。