【问题标题】:Weekend filter for Java 8 LocalDateTimeJava 8 LocalDateTime 的周末过滤器
【发布时间】:2016-04-26 06:44:40
【问题描述】:

我想编写一个布尔值函数,如果给定的LocalDateTime 位于两个特定时间点之间,则返回 true,否则返回 false。

如果给定日期在格林威治标准时间周五 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 时间库(或任何其他现有过滤器方法)的简单解决方案。

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

【问题讨论】:

  • A LocalDateTime 没有时区信息 - 所以在这里说 GMT 没有意义...
  • 正如 assylias 评论的那样,如果您真的在 UTC 时间轴上处理实际时刻,您应该使用 Instant 对象。 LocalDateTime 类确实代表时间轴上的时刻;它代表了对可能时刻的模糊概念。

标签: java filter java-8 java-time


【解决方案1】:

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

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

【讨论】:

    【解决方案2】:

    一个简单的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 时创建一个新实例,因此您可能希望创建一个静态最终 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);
    

    【讨论】:

      【解决方案3】:

      时间查询

      java.time 框架包括一个用于询问日期时间值的架构:Temporal QueryTemporalQuery 接口的一些实现可以在复数命名的TemporalQueries 类中找到。

      您也可以编写自己的实现。 TemporalQueryfunctional interface,这意味着它声明了一个方法。方法是queryFrom

      这是我实施TemporalQuery 的第一次尝试,所以请谨慎对待。这是完整的课程。免费使用 (ISC License),但风险自负。

      棘手的部分是问题的要求是周末由 UTC 定义,而不是时区或传递的日期时间值的偏移量。所以我们需要将传递的日期时间值调整为UTC。虽然Instant 在逻辑上是等价的,但我使用OffsetDateTimeoffset of UTC,因为它更灵活。具体来说,OffsetDateTime 提供了一个getDayOfWeek 方法。

      CAVEAT:我不知道我是否以正统的方法做事,因为我没有完全理解 java.time 设计的基础,正如它的创建者所期望的那样。具体来说,我不确定我将TemporalAccessor ta 转换为java.time.chrono.ChronoZonedDateTime 是否正确。但它似乎运作良好。

      如果这个类与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 );
      

      运行时。

      周末是:WeekendFri2200ToSun2300UtcQuery{ 22:00Z | [周五、周六、周日] | 23:00Z }

      现在:2016-04-26T20:35:01.014-04:00[America/Montreal] 在周末:false

      friday1000: 2016-04-29T10:00-04:00[America/Montreal] 在周末:false

      friday2330: 2016-04-29T23:30-04:00[America/Montreal] 在周末:是的

      Local… 不代表本地

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

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

      【讨论】:

      • 感谢这个详细的答案!不知道 TemporalQuery 接口。
      • 作为一般规则,在实现Temporal* 接口时永远不要检查和转换,而是使用.from(ta)。因此使用接受InstantZonedDateTimeOffsetDateTime 作为输入的Instant.from(ta)。对于可重用的实用程序方法,我不相信 UTC 是正确的方法。相反,我会使用LocalDateTime.from(ta) 并将时区问题留给调用者(因为算法本身不需要偏移量)。如果为查询创建静态方法,它可以用作Boolean w = dateTime.query(isWeekend())跨度>
      【解决方案4】:

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

      计划

      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;
          }
      
       }
      

      【讨论】:

      • Thx,我有类似的东西,我很好奇是否有一个用于各种过滤的小库。
      【解决方案5】:

      希望这会有所帮助:

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

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

      【讨论】:

        【解决方案6】:

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

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

        然后你可以在流中应用它

        someListOfDates.stream()
           .filter(isWeekend)
           .forEach(System.out::println);
        

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

        【讨论】:

          【解决方案7】:

          提出了许多好的解决方案,使用 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());
          }
          

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 2017-08-14
            • 2020-12-31
            • 2017-12-19
            • 2020-07-21
            • 2015-12-19
            • 2018-12-17
            • 1970-01-01
            相关资源
            最近更新 更多