如何使用 Java Streams API 正确过滤介于开始日期和结束日期之间的日期



所以我一直在使用 Java Streams API,我正在尝试过滤数据库中的日期,其中某物的开始日期在当前日期之前,结束日期在当前日期之后。我已经尝试了多种方法,但到目前为止我尝试过的任何方法似乎都不起作用。

这是我到目前为止尝试过的:

我尝试实现ChronoLocalDate接口,并使用.isBefore() and.isAfter()',但日期没有正确过滤

在这一点上,我现在已经尝试坚持使用LocalDate,并且我尝试使用以下代码将LocalDate.now()转换为yyyy-MM-dd格式(这是我数据库中的日期格式):

LocalDate rightNow = LocalDate.now();
String formatString = rightNow.format(DateTimeFormatter.ISO_LOCAL_DATE);

但是,这不起作用,并导致以下错误:

The method format(DateTimeFormatter) is undefined for the type LocalDate

我从各种 2018 年示例中获得了上面的代码。LocalDate.format()已经贬值了吗?

此外,我无法找到任何使用 LocalDate 在开始日期和结束日期之间进行过滤的相关示例。

这里的任何帮助将不胜感激。提前谢谢。

好吧,您可以使用Java8 streamsLocalDate执行以下操作。

public static void main(String[] args) {
DateTimeFormatter dateFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
String startTextDateFromDB = "2019-03-14";
String endTextDateFromDB = "2019-05-14";
LocalDate startDate = LocalDate.parse(startTextDateFromDB, dateFormatter);
LocalDate endDate = LocalDate.parse(endTextDateFromDB, dateFormatter);
List<LocalDate> getList = getDatesInBetween(startDate, endDate);
System.out.println(Arrays.asList(getList));
}
//return a list of all localdates between two dates
public static List<LocalDate> getDatesInBetween(
LocalDate startDate, LocalDate endDate) {
//get numbers between two dates
long numbersBetween = ChronoUnit.DAYS.between(startDate, endDate);
//collect each localdate from start date until end date 
return IntStream.iterate(0, i -> i + 1)
.limit(numbersBetween)
.mapToObj(i -> startDate.plusDays(i))
.collect(Collectors.toList());        
}
}

下面是如何应用过滤器的示例。我假设你有一个包含Foo元素的列表,这个类有两个字段:startDateendDate

LocalDate now = LocalDate.now();
List<LocalDate> dates = dbEntries.stream()
.filter(t -> t.getStartDate().compareTo(now) < 0 && t.getEndDate().compareTo(now) > 0)
.collect(Collectors.toList());

Spring Boot 可能能够自动将数据库中的日期转换为适当的临时实例。也许使用注释

在 SQL 中过滤

所以我一直在使用 Java Streams API,并且我正在尝试过滤数据库中的日期,

这是一个矛盾。如果将数据库内的日期作为查询的一部分进行筛选(通常是最佳方法),则不需要 Java 流。

SQL 中的半开查询

编写查询。我假设我们正在查询一对数据类型类似于 SQL 标准DATE类型的列,以获取没有一天中时间和时区的仅日期值。

在此查询代码中,我们使用半开放方法,其中开头是包容性的,而结尾是独占的。这通常是处理时间跨度的最佳方法。

String sql = "SELECT * FROM event_ WHERE start_ <= ? AND stop_ > ? ;" ;

今天

我们需要将今天的日期传递到该?占位符中。

时区对于确定日期至关重要。对于任何给定时刻,日期因区域而异。例如,法国巴黎午夜过后几分钟是新的一天,而在魁北克蒙特利尔仍然是"昨天"。

如果未指定时区,那么 JVM 将隐式应用其当前缺省时区。该默认值可能在运行时(!)期间随时更改,因此结果可能会有所不同。最好将所需/预期的时区显式指定为参数。如果严重,请与用户确认区域。

Continent/Region的格式指定正确的时区名称,例如America/MontrealAfrica/CasablancaPacific/Auckland。切勿使用 2-4 个字母的缩写,例如ESTIST,因为它们不是真正的时区,不是标准化的,甚至不是唯一的(!)。

ZoneId z = ZoneId.of( "America/Montreal" ) ;  
LocalDate today = LocalDate.now( z ) ;

如果要使用 JVM 的当前缺省时区,请请求它并作为参数传递。如果省略,代码阅读起来会变得模棱两可,因为我们不确定您是否打算使用默认值,或者您是否像许多程序员一样没有意识到这个问题。

ZoneId z = ZoneId.systemDefault() ;  // Get JVM’s current default time zone.
LocalDate today = LocalDate.now( z ) ;

占位符的传递值

我们有今天的日期,所以把它交给你准备好的发言稿。

myPreparedStatement.setObject( 1 , today ) ;
myPreparedStatement.setObject( 2 , today ) ;

Java 中的过滤器

通常,最好在 SQL 中执行尽可能多的过滤,在数据库的服务器端执行。强大的企业级数据库引擎(如 Postgres)针对此类工作进行了高度优化。

但是,也许您有理由在 Java 而不是数据库中执行此操作。

您必须在 Java 中逐行检索,从 JDBC 中的结果集中提取。所以你不妨过滤一下。这意味着不需要 Java 流。只需比较开始值和停止值的LocalDate值即可。

我尝试实现 ChronoLocalDate 接口,并使用 .isBefore() 和 .isAfter()',但日期没有正确过滤

你似乎对java.time在这方面是如何工作的感到困惑。ChronoLocalDate接口不是由您实现的。该接口已经在ISO 8601日历系统的LocalDate类中实现,该系统通常在世界其他地区的西方使用。该接口是为实现各种日历系统的作者而存在的,而不是为你和我存在的。至少已经为Java实现了大约十几个这样的日历系统,包括一些与Java捆绑在一起的日历系统,如伊斯兰日历,日本日历和泰国日历。

顺便说一下,java.timeJavaDoc解释说,你通常应该避免使用更通用的接口。如上所述,它们的存在主要是为了那些实施日历系统的人的利益。在日常业务编程中,您通常应该使用更具体的类。例如,LocalDate而不是ChronoLocalDate

要比较LocalDate对象,请调用LocalDate::isBeforeisAfterisEqualequalscompareTo方法

List< Event > events = new ArrayList<>() ;
ResultSet rs = stmt.executeQuery(query);
while (rs.next()) {
…
LocalDate start = rs.getObject( "start_" , LocalDate.class ) ;
LocalDate stop = rs.getObject( "stop_" , LocalDate.class ) ;
if( ( ! start.isAfter( today ) ) && ( stop.isBefore( today ) ) { 
Event event = … ;
events.add( event ) ;
} else { 
// Skip this row. Does not meet our criterion of containing today's date.
}
}

LocalDateRange

使用ThreeTen-Extra库使此类工作变得更加容易。此项目为java.time类添加了功能。

对于您的特定情况,您可能会发现LocalDateRange类很有帮助。此类将时间跨度表示为一对LocalDate对象。有用的比较方法包括abutscontainsoverlaps等。默认情况下,这些方法根据半开放方法工作,但您可以选择指定完全关闭(开始和结束都包括)。

让我们使用这个类来修改上面示例代码中的比较。

List< Event > events = new ArrayList<>() ;
ResultSet rs = stmt.executeQuery(query);
while (rs.next()) {
…
LocalDate start = rs.getObject( "start_" , LocalDate.class ) ;
LocalDate stop = rs.getObject( "stop_" , LocalDate.class ) ;
LocalDateRange dateRange = LocalDateRange.of( start , stop ) ;   // Instantiate the date range.
if( ( dateRange.contains( today ) ) {                            // Call `LocalDateRange::contains` rather than write our own comparison logic.
Event event = … ;
events.add( event ) ;
} else { 
// Skip this row. Does not meet our criterion of containing today's date.
}
}

智能对象,而不是哑字符串

LocalDate rightNow = LocalDate.now();String formatString = rightNow.format(DateTimeFormatter.ISO_LOCAL_DATE);

为什么要生成字符串?鉴于您的问题陈述,我认为不需要字符串。使用日期时间值时,请使用日期时间对象。不要对日期时间业务逻辑使用字符串。

教程

提示:通读 Oracle 免费提供的java.time教程。

所以我发现了问题所在。我从java.joda而不是java.time导入了错误的LocalDate

所以从这里开始,我不再使用ChronoLocalDate,而是来自java.timeLocalDate,我的过滤现在工作得很好。我犯了非常愚蠢的错误,但我想通了。

谢谢所有试图帮助的人。

相关内容

  • 没有找到相关文章

最新更新