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:
- Instantiate a
TemporalQuery
object.
- 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.
Instant
objects. TheLocalDateTime
class does not represent moments on the timeline; it represents vague idea about possible moments. – Lailalain