Weekend filter for Java 8 LocalDateTime
Asked Answered
E

7

16

I want to write a boolean valued function which returns true if the given LocalDateTime falls between two specific points in time, false otherwise.

Specifically I want to have a LocalDateTime filter if a given date is between 22:00 GMT Friday and 23:00 GMT Sunday.

A skeleton could look like this:

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

This is basically a weekend filter and I'm wondering if there is a simple solution with the new Java 8 Time library(or any other existing filter methods).

I know how check for day of week, hour etc. but avoid to reinvent the wheel.

Emera answered 26/4, 2016 at 6:44 Comment(3)
A LocalDateTime doesn't have time zone information - so it doesn't make sense to say GMT here...Brimful
As assylias commented, if you really are working with actual moments on the timeline in UTC, you should be using Instant objects. The LocalDateTime class does not represent moments on the timeline; it represents vague idea about possible moments.Lailalain
Depending on the locale, a weekend is not necessarily Saturday and Sunday. Something to keep in mind with all the answers below that assume it is Saturday and sunday.Monia
G
2

I have written a small program to achieve this

PROGRAM

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

 }
Gallnut answered 26/4, 2016 at 7:30 Comment(1)
Thx, I have something similar and was curious if there is a little library for all kinds of filterings.Emera
C
9

How would you expect such a library to work? You would still need to tell it when your weekend begins and ends and it would end up being not much shorter than the simple

boolean isWeekend(LocalDateTime dt) {
    switch(dt.getDayOfWeek()) {
        case FRIDAY:
            return dt.getHour() >= ...;
        case SATURDAY:
            return true;
        case SUNDAY:
            return dt.getHour() < ...;
        default:
            return false;
    }
}
Cryology answered 26/4, 2016 at 8:15 Comment(0)
K
6

A simple TemporalQuery would do the trick:

static class IsWeekendQuery implements TemporalQuery<Boolean>{

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

It would be called like this (using .now() to get a value to test):

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

Or, specifically in UTC time (using .now() to get a value to test):

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

Going beyond your question, there is no reason to create a new instance of IsWeekendQuery every time it is used, so you might want to create a static final TemporalQuery that encapsulates the logic in a lambda expression:

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);
Kolo answered 27/4, 2016 at 1:51 Comment(0)
L
3

Temporal Query

The java.time framework includes an architecture for asking about a date-time value: Temporal Query. Some implementations of the TemporalQuery interface can be found in the plural-named TemporalQueries class.

You can write your own implementation as well. TemporalQuery is a functional interface, meaning it has a single method declared. The method is queryFrom.

This is my first attempt at implementing TemporalQuery, so take with a grain of salt. Here is the complete class. Free to use (ISC License), but entirely at your own risk.

The tricky part is the Question’s requirement is that the weekend be defined by UTC, not the time zone or offset of the passed date-time value. So we need to adjust the passed date-time value into UTC. While Instant is logically equivalent, I used OffsetDateTime with an offset of UTC as it is more flexible. Specifically the OffsetDateTime offers a getDayOfWeek method.

CAVEAT: I have no idea if I am doing things in an orthodox method as I do not completely comprehend the underpinnings of java.time’s designs as intended by its creators. Specifically I'm not sure if my casting of TemporalAccessor ta to a java.time.chrono.ChronoZonedDateTime is proper. But it seems to be working well enough.

It would be better if this class worked with Instant instances as well as 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 + " }";
    }

}

Let's use that TemporalQuery. While defining the TemporalQuery takes some work, using it is so very simple and easy:

  1. Instantiate a TemporalQuery object.
  2. Apply to our date-time object.
    (any instance of java.time.chrono.ChronoZonedDateTime in our case, such as ZonedDateTime)

In use.

WeekendFri2200ToSun2300UtcQuery query = new WeekendFri2200ToSun2300UtcQuery ();

I added a static description method for debugging and logging, to verify the query’s settings. This is my own invented method, not required by the TemporalQuery interface.

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

First today, Tuesday. Should not be in weekend.

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

Now with this Friday morning. Should not be in weekend.

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

And late on this Friday. Should be TRUE, within weekend.

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

When run.

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… does not mean local

Referring to the Question… saying you want to compare a LocalDateTime to values in UTC (the weekend start/stop) makes no sense. A LocalDateTime has no time zone of offset-from-UTC. While the naming may be counter-intuitive, Local… classes mean they could apply to any locality with no locality in particular. So they have no meaning, they are not a point on the timeline, until you apply a specify offset or time zone.

This entire Answer assumes you were confused about this terminology and did intend to compare an actual moment on the timeline.

Lailalain answered 27/4, 2016 at 0:55 Comment(2)
Thx for this detailed answer! Wasn't aware of the TemporalQuery interface.Emera
As as general rule, never check and cast when implementing a Temporal* interface, use .from(ta) instead. Thus use Instant.from(ta) which accepts Instant, ZonedDateTime and OffsetDateTime as input. For a reusable utility method I'm not convinced that UTC would be the right way to go. Instead I'd use LocalDateTime.from(ta) and leave the time-zone issues to the caller (as the algorithm itself does not require an offset).If you create a static method for the query, it can be used as Boolean w = dateTime.query(isWeekend())Acherman
G
2

I have written a small program to achieve this

PROGRAM

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

 }
Gallnut answered 26/4, 2016 at 7:30 Comment(1)
Thx, I have something similar and was curious if there is a little library for all kinds of filterings.Emera
M
1

Hope this helps:

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

This is the simplest way for doing it without using many private constants.

Mercedezmerceer answered 4/1, 2018 at 6:18 Comment(0)
M
1

Many good solutions are proposed, with Java 8+, found a simpler way if this can still help:

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());
}
Morril answered 12/8, 2021 at 13:11 Comment(0)
E
0

An alternate Java 8+ solution would be to use a Predicate to test whether the date falls on a weekend.

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

Then you can use apply it in a stream like

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

No external dependencies needed. (Though, please use a logger in production.)

Epistemology answered 25/6, 2020 at 19:7 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.