Java 8 LocalDateTime 的周末过滤器

Weekend filter for Java 8 LocalDateTime

我想编写一个布尔值函数,如果给定 LocalDateTime 落在两个特定时间点之间,则 returns 为真,否则为假。

具体来说,如果给定日期介于格林威治标准时间星期五 22:00 和格林威治标准时间星期日 23:00 之间,我想使用 LocalDateTime 过滤器。

骨骼可能看起来像这样:

public boolean isWeekend(LocalDateTime dateTime) {
    //Checks if dateTime falls in between Friday's 22:00 GMT and Sunday's 23:00 GMT
    //return ...???
}

这基本上是一个周末过滤器,我想知道是否有新的 Java 8 时间库(或任何其他现有过滤器方法)的简单解决方案。

我知道如何检查星期几、小时等,但避免重新发明轮子。

我写了一个小程序来实现这个

计划

public class TestWeekend {
    private static final int FRIDAY = 5;
    private static final int SATURDAY = 6;
    private static final int SUNDAY = 7;
    private static final Integer WEEKEND_START_FRIDAY_CUT_OFF_HOUR = 22;
    private static final Integer WEEKEND_END_SUNDAY_CUT_OFF_HOUR = 23;
    private static List<Integer> weekendDaysList = Arrays.asList(FRIDAY, SATURDAY, SUNDAY);

    public static void main(String []args) throws FileNotFoundException {
        System.out.println(" is weekend - "+isWeekend(LocalDateTime.of(2016,4,22,18,39)));
        System.out.println(" is weekend - "+isWeekend(LocalDateTime.of(2016,4,22,21,59)));
        System.out.println(" is weekend - "+isWeekend(LocalDateTime.of(2016,4,22,22,0)));
        System.out.println(" is weekend - "+isWeekend(LocalDateTime.of(2016,4,23,5,0)));
        System.out.println(" is weekend - "+isWeekend(LocalDateTime.of(2016,4,24,8,0)));
        System.out.println(" is weekend - "+isWeekend(LocalDateTime.of(2016,4,24,22,59)));
        System.out.println(" is weekend - "+isWeekend(LocalDateTime.of(2016,4,24,23,0)));
        System.out.println(" is weekend - "+isWeekend(LocalDateTime.of(2016,4,25,11,5)));
    }

    public static  boolean isWeekend(LocalDateTime dateTime) {
        System.out.print("Date - "+dateTime+" , ");
        if(weekendDaysList.contains(dateTime.getDayOfWeek().getValue()) ){
            if(SATURDAY ==  dateTime.getDayOfWeek().getValue()){
                return true;
            }
            if(FRIDAY == dateTime.getDayOfWeek().getValue() && dateTime.getHour() >=WEEKEND_START_FRIDAY_CUT_OFF_HOUR){
               return true;
            }else if(SUNDAY == dateTime.getDayOfWeek().getValue() && dateTime.getHour()  < WEEKEND_END_SUNDAY_CUT_OFF_HOUR ){
                return   true;
            }
        }
        //Checks if dateTime falls in between Friday's 22:00 GMT and Sunday's 23:00 GMT
         return false;
    }

 }

您希望这样的库如何工作?你仍然需要告诉它你的周末何时开始和结束,它最终不会比简单的

短多少
boolean isWeekend(LocalDateTime dt) {
    switch(dt.getDayOfWeek()) {
        case FRIDAY:
            return dt.getHour() >= ...;
        case SATURDAY:
            return true;
        case SUNDAY:
            return dt.getHour() < ...;
        default:
            return false;
    }
}

时间查询

java.time 框架包括用于询问日期时间值的体系结构:Temporal Query. Some implementations of the TemporalQuery interface can be found in the plural-named TemporalQueries class。

您也可以编写自己的实现。 TemporalQuery is a functional interface, meaning it has a single method declared. The method is queryFrom.

这是我第一次尝试实现TemporalQuery,所以请三思而后行。这是完整的 class。免费使用 (ISC License),但风险完全由您自己承担。

棘手的部分是问题的要求是周末由 UTC 定义,而不是时区或传递的日期时间值的偏移量。所以我们需要将传递的日期时间值调整为 UTC。虽然 Instant 在逻辑上是等价的,但我使用了 OffsetDateTime with an offset of UTC as it is more flexible. Specifically the OffsetDateTime offers a getDayOfWeek 方法。

警告: 我不知道我是否在用正统的方法做事,因为我没有完全理解 java.time 设计的基础它的创造者。具体来说,我不确定将 TemporalAccessor ta 转换为 java.time.chrono.ChronoZonedDateTime 是否正确。但它似乎运行良好。

如果这个 class 与 Instant 个实例以及 ChronoZonedDateTime/ZonedDateTime.

一起工作会更好
package com.example.javatimestuff;

import java.time.LocalDate;
import java.time.LocalTime;
import java.time.ZoneId;
import java.time.ZonedDateTime;

/**
 * Answers whether a given temporal value is between Friday 22:00 UTC
 * (inclusive) and Sunday 23:00 UTC (exclusive).
 *
 * @author Basil Bourque. 
 * 
 * © 2016 Basil Bourque
 * This source code may be used according to the terms of the ISC License (ISC). (Basically, do anything but sue me.)
 * https://opensource.org/licenses/ISC
 *
 */
public class WeekendFri2200ToSun2300UtcQuery implements TemporalQuery<Boolean> {

    static private final EnumSet<DayOfWeek> WEEKEND_DAYS = EnumSet.of ( DayOfWeek.FRIDAY , DayOfWeek.SATURDAY , DayOfWeek.SUNDAY );
    static private final OffsetTime START_OFFSET_TIME = OffsetTime.of ( LocalTime.of ( 22 , 0 ) , ZoneOffset.UTC );
    static private final OffsetTime STOP_OFFSET_TIME = OffsetTime.of ( LocalTime.of ( 23 , 0 ) , ZoneOffset.UTC );

    @Override
    public Boolean queryFrom ( TemporalAccessor ta ) {
        if (  ! ( ta instanceof java.time.chrono.ChronoZonedDateTime ) ) {
            throw new IllegalArgumentException ( "Expected a java.time.chrono.ChronoZonedDateTime such as `ZonedDateTime`. Message # b4a9d0f1-7dea-4125-b68a-509b32bf8d2d." );
        }

        java.time.chrono.ChronoZonedDateTime czdt = ( java.time.chrono.ChronoZonedDateTime ) ta;

        Instant instant = czdt.toInstant ();
        OffsetDateTime odt = OffsetDateTime.ofInstant ( instant , ZoneOffset.UTC );
        DayOfWeek dayOfWeek = odt.getDayOfWeek ();
        if (  ! WeekendFri2200ToSun2300UtcQuery.WEEKEND_DAYS.contains ( dayOfWeek ) ) {
            // If day is not one of our weekend days (Fri-Sat-Sun), then we know this moment is not within our weekend definition.
            return Boolean.FALSE;
        }
        // This moment may or may not be within our weekend. Very early Friday or very late Sunday is not a hit.
        OffsetDateTime weekendStart = odt.with ( DayOfWeek.FRIDAY ).toLocalDate ().atTime ( START_OFFSET_TIME );  // TODO: Soft-code with first element of WEEKEND_DAYS.
        OffsetDateTime weekendStop = odt.with ( DayOfWeek.SUNDAY ).toLocalDate ().atTime ( STOP_OFFSET_TIME );  // TODO: Soft-code with last element of WEEKEND_DAYS.

        // Half-Open -> Is equal to or is after the beginning, AND is before the ending.
        // Not Before -> Is equal to or is after the beginning.
        Boolean isWithinWeekend = (  ! odt.isBefore ( weekendStart ) ) && ( odt.isBefore ( weekendStop ) );

        return isWithinWeekend;
    }

    static public String description () {
        return "WeekendFri2200ToSun2300UtcQuery{ " + START_OFFSET_TIME + " | " + WEEKEND_DAYS + " | " + STOP_OFFSET_TIME + " }";
    }

}

让我们使用它 TemporalQuery。虽然定义 TemporalQuery 需要一些工作,但使用它非常简单和容易:

  1. 实例化一个 TemporalQuery 对象。
  2. 应用于我们的日期时间对象。
    (在我们的例子中 java.time.chrono.ChronoZonedDateTime 的任何实例,例如 ZonedDateTime

正在使用中。

WeekendFri2200ToSun2300UtcQuery query = new WeekendFri2200ToSun2300UtcQuery ();

我添加了一个用于调试和日志记录的静态 description 方法,以验证查询的设置。这是我自己发明的方法,TemporalQuery 接口不需要。

System.out.println ( "Weekend is: " + WeekendFri2200ToSun2300UtcQuery.description () );

今天是星期二。应该不是周末。

ZonedDateTime now = ZonedDateTime.now ( ZoneId.of ( "America/Montreal" ) );
Boolean nowIsWithinWeekend = now.query ( query );
System.out.println ( "now: " + now + " is in weekend: " + nowIsWithinWeekend );

现在是这个星期五早上。应该不是在周末。

ZonedDateTime friday1000 = ZonedDateTime.of ( LocalDate.of ( 2016 , 4 , 29 ) , LocalTime.of ( 10 , 0 ) , ZoneId.of ( "America/Montreal" ) );
Boolean friday1000IsWithinWeekend = friday1000.query ( query );
System.out.println ( "friday1000: " + friday1000 + " is in weekend: " + friday1000IsWithinWeekend );

本周五晚些时候。应该是 TRUE,在周末内。

ZonedDateTime friday2330 = ZonedDateTime.of ( LocalDate.of ( 2016 , 4 , 29 ) , LocalTime.of ( 23 , 30 ) , ZoneId.of ( "America/Montreal" ) );
Boolean friday2330IsWithinWeekend = friday2330.query ( query );
System.out.println ( "friday2330: " + friday2330 + " is in weekend: " + friday2330IsWithinWeekend );

当运行.

Weekend is: WeekendFri2200ToSun2300UtcQuery{ 22:00Z | [FRIDAY, SATURDAY, SUNDAY] | 23:00Z }

now: 2016-04-26T20:35:01.014-04:00[America/Montreal] is in weekend: false

friday1000: 2016-04-29T10:00-04:00[America/Montreal] is in weekend: false

friday2330: 2016-04-29T23:30-04:00[America/Montreal] is in weekend: true

Local…不代表本地

提到问题...说您想将 LocalDateTime 与 UTC(周末 start/stop)中的值进行比较是没有意义的。 LocalDateTime 没有与 UTC 偏移的时区。虽然命名可能违反直觉,但 Local… classes 意味着它们可以适用于任何地方,没有特别的地方。所以它们没有意义,它们不是时间轴上的一个点,直到您应用指定的偏移量或时区。

整个答案假定您对这个术语感到困惑,并且确实打算比较时间轴上的实际时刻。

一个简单的 TemporalQuery 就可以解决问题:

static class IsWeekendQuery implements TemporalQuery<Boolean>{

    @Override
    public Boolean queryFrom(TemporalAccessor temporal) {
        return temporal.get(ChronoField.DAY_OF_WEEK) >= 5;
    }
}

它会这样调用(使用.now()得到一个值来测试):

boolean isItWeekendNow = LocalDateTime.now().query(new IsWeekendQuery());

或者,特别是 UTC 时间(使用 .now() 获取要测试的值):

boolean isItWeekendNow = OffsetDateTime.now(ZoneOffset.UTC).query(new IsWeekendQuery());

超出你的问题,没有理由在每次使用时都创建一个新的 IsWeekendQuery 实例,所以你可能想要创建一个 static final TemporalQuery 来封装逻辑一个 lambda 表达式:

static final TemporalQuery<Boolean> IS_WEEKEND_QUERY = 
    t -> t.get(ChronoField.DAY_OF_WEEK) >= 5;

boolean isItWeekendNow = OffsetDateTime.now(ZoneOffset.UTC).query(IS_WEEKEND_QUERY);

希望对您有所帮助:

LocalDateTime localDateTime = LocalDateTime.now(DateTimeZone.UTC);
int dayNum = localDateTime.get(DateTimeFieldType.dayOfWeek());
boolean isWeekend = (dayNum == DateTimeConstants.SATURDAY || dayNum == DateTimeConstants.SUNDAY);

这是最简单的方法,无需使用许多私有常量。

另一种 Java 8+ 解决方案是使用 Predicate 来测试日期是否在周末。

Predicate<LocalDate> isWeekend = date -> DayOfWeek.from(date).get(ChronoField.DAY_OF_WEEK) > 5;

然后你可以像

一样在流中使用它
someListOfDates.stream()
   .filter(isWeekend)
   .forEach(System.out::println);

不需要外部依赖。 (不过,请在生产环境中使用记录器。)

提出了很多好的解决方案,Java 8+,找到了一个更简单的方法,如果这仍然可以帮助:

import static java.time.DayOfWeek.SATURDAY;
import static java.time.DayOfWeek.SUNDAY;
...
Set<DayOfWeek> WEEKEND = EnumSet.of(SATURDAY, SUNDAY);

public boolean isWeekend(LocalDateTime dateTime) {        
    return WEEKEND.contains(dateTime.getDayOfWeek());
}