5
\$\begingroup\$

Novice Java developer here. I've never really used a time/date library before and I'm curious how an experienced developer would solve this. You're given 4 ints: startHour, startMinute, endHour and endMinute. Now, check if current time is within the given timeframe. Is there a more clean way of doing this than what I've done here:

private void checkTimeframe(int startHour, int startMinute, int endHour, int endMinute) {
    LocalDateTime now = LocalDateTime.now();
    LocalTime localTimeStart = new LocalTime(startHour, startMinute);
    LocalTime localTimeEnd = new LocalTime(endHour, endMinute);

    LocalDateTime startTime = new LocalDateTime(now.getYear(), now.getMonthOfYear(),
            now.getDayOfMonth(), startHour, startMinute);

    LocalDateTime endTime = new LocalDateTime(now.getYear(), now.getMonthOfYear(),
            now.getDayOfMonth(), endHour, endMinute);

    //Check if start/end is, for instance, 23:00 - 03:00
    if (localTimeStart.isAfter(localTimeEnd) || localTimeStart.equals(localTimeEnd)) {
        endTime = endTime.plusDays(1);
    }

    if ( (now.equals(startTime) || now.isAfter(startTime) ) && now.isBefore(endTime)) {
        System.out.println("Ok, we're within start/end");
    } else {
        System.out.println("Outside start/end");
    }
}
\$\endgroup\$
0

3 Answers 3

2
\$\begingroup\$

to me it looks like a simple check of number between range of numbers. do I would do like this

  1. turn hour and minute into one number that is hhmm to simplify the comparison

  2. now its a simple check between range of numbers, taking into account the case of cross-date boundary

complete code:

private static void checkTimeframe(int startHour, int startMinute, int endHour, int endMinute) {
    // "concatanate" hour and minute into one number
    int startHourMinute = startHour * 100 + startMinute;
    int endHourMinute = endHour * 100 + endMinute;
    LocalDateTime now = LocalDateTime.now();
    int nowHourMinute = now.getHour() * 100 + now.getMinute();

    // if range within date - simple between boundaries check
    if (startHourMinute <= endHourMinute) {
        if (nowHourMinute >= startHourMinute && nowHourMinute <= endHourMinute) {
            System.out.println("Ok, we're within start/end");
        } else {
            System.out.println("Outside start/end");
        }
    // else (cross date boundary range) - check if now date is either within range of yesterday or within range tomorrow  
    } else {
        if (nowHourMinute >= startHourMinute || nowHourMinute <= endHourMinute) {
            System.out.println("Ok, we're within start/end");
        } else {
            System.out.println("Outside start/end");
        }
    }
}
\$\endgroup\$
1
  • \$\begingroup\$ Thanks a lot, really simple and clear using ints as hhmm! \$\endgroup\$
    – yinder
    May 21, 2019 at 15:27
4
\$\begingroup\$

tl;dr

Is there a more clean way of doing this than

Yes. This is more clean, and more importantly, more correct:

ZonedDateTime now = ZonedDateTime.now( ZoneId.of( "Pacific/Auckland" ) ) ;
boolean hit = 
    org.threeten.extra.Interval                          // Represents a span-of-time attached to the timeline. Consists of a pair of `Instant` objects. 
    .of( 
        now
            .with( 
                LocalTime.of( startHour , startMinute )  // Returns a `LocalTime` object.
            )                                            // Returns another `ZonedDateTime` object.
            .toInstant() ,                               // Returns a `Instant` object (a moment as seen with zero offset from UTC). 
        now
            .with( 
                LocalTime.of( endHour , endMinute ) 
            )
            .toInstant() 
    )
    .contains(
        now.toInstant()
    )
;

java.time successor to Joda-Time

The Joda-Time library is in maintenance-mode, with the project now recommending use of its successor, the java.time classes (JSR 310) built into Java 8 and later. Both Joda-Time and java.time are led by the same man, Stephen Colebourne.

Avoid legacy date-time classes

I've never really used a time/date library before

Be aware that you should never use the tragically flawed legacy date-time classes from the earliest versions of Java. Never use Calendar, either Date class, SimpleDateFormat, etc.

and I'm curious how an experienced developer would solve this.

Use java.time classes.

LocalTime

You're given 4 ints: startHour, startMinute, endHour and endMinute.

A time-of-day is represented with LocalTime class.

LocalTime startTime = LocalTime.of( startHour , startMinute ) ;
LocalTime endTime = LocalTime.of( endHour , endMinute ) ;

Current time

Now, check if current time is within the given timeframe.

Determining the current time requires a time zone. For any given moment, the time-of-day (and the date!) varies around the globe by time zone. Either specify your desired time zone, or get the JVM’s current default time zone.

Time zones

Use real time zone names in format of Continent/Region. The 2-4 letter pseudo zones such as CST, IST, and PST are not standardized, and are not even unique(!).

ZoneId zoneId = ZoneId.of( "Asia/Tokyo" ) ;
ZoneId zoneId = ZoneId.systemDefault() ;

Get the current time-of-day.

LocalTime now = LocalTime.now( zoneId ) ;

Comparing

You could compare that to your inputs (but you should not!).

Tip: A shorter way of asking "Is equal to or later than" is "Is not before".

boolean hit = ( ! now.isBefore( startTime ) ) && now.isBefore( endTime ) ; 

Half-Open

Notice we are using the Half-Open definition of a spar-of-time, where the beginning is inclusive while the ending is exclusive. This is nearly always the best way to handle spans of time.

Temporal Anomalies

So… why not compare in that manner, using time-of-day? Because you are neglecting to account for temporal anomalies created by politicians, such as Daylight Saving Time (DST), war, occupation, diplomacy, etc.

To account for these temporal anomalies, apply the time zone to your time-of-day inputs. Read the documentation to be sure you understand and agree with the way the java.time classes handle accounting for the anomalies such as no 2 AM hour on the DST Spring-Ahead cutover, or two hours of 2 AM on the DST Fall-Back cutover.

ZonedDateTime

To account for time zone anomalies, use the ZonedDateTime class.

ZonedDateTime now = ZonedDateTime.now( zoneId ) ;
ZonedDateTime start = ZonedDateTime.of( now.toLocalDate() , startTime , zoneId ) ;
ZonedDateTime end = ZonedDateTime.of( now.toLocalDate() , endTime , zoneId ) ;

See if now is within our target moments.

boolean hit = ( ! now.isBefore( start ) ) && now.isBefore( end ) ;

ThreeTen-Extra

If doing much of this work, add the ThreeTen-Extra library to your project to access its Interval class. This library is also led by Stephen Colebourne.

Interval interval = Interval.of( start.toInstant() , end.toInstant() ) ;
boolean hit = interval.contains( now.toInstant() ) ;  
\$\endgroup\$
1
\$\begingroup\$

Kotlin version of @sharon-ben-asher answer:

fun LocalDateTime.isInTimeFrame(startHour: Int, startMinute: Int, endHour: Int, endMinute: Int): Boolean {
        val startHourMinute = startHour * 100 + startMinute
        val endHourMinute = endHour * 100 + endMinute

        val nowHourMinute: Int = hour * 100 + minute

        return if (startHourMinute <= endHourMinute) {
            // if range within date - simple between boundaries check
            nowHourMinute in startHourMinute..endHourMinute
        } else {
            // else (cross date boundary range) - check if now date is either within range of yesterday or within range tomorrow
            nowHourMinute >= startHourMinute || nowHourMinute <= endHourMinute
        }
    }

And use it like this:

LocalDateTime.parse(dateString, DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")).isInTimeFrame(18,0,6,0)

Is DateTime between 18:00 - 06:00 ?

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.