16

I want to check if a LocalTime is midnight. For this use case midnight is defined as anything in between 23:59 and 00:01. That is a range of 2 minutes.

private final LocalTime ONE_MINUTE_BEFORE_MIDNIGHT = LocalTime.of(23, 59, 0);
private final LocalTime ONE_MINUTE_AFTER_MIDNIGHT = LocalTime.of(0, 1, 0);

I have a method

public boolean isAtMidnight(LocalTime time) {
    return time.isAfter(ONE_MINUTE_BEFORE_MIDNIGHT)
        && time.isBefore(ONE_MINUTE_AFTER_MIDNIGHT);
}

This method is always returning false. Even for LocalTime.MIDNIGHT. it should, however, return true.

How can I check if the time is +-1 minute from midnight?

Andronicus
  • 25,419
  • 17
  • 47
  • 88
isADon
  • 3,433
  • 11
  • 35
  • 49

4 Answers4

18

The solution is to use || instead of &&:

public boolean isAtMidnight(LocalTime time) {
    return time.isAfter(ONE_MINUTE_BEFORE_MIDNIGHT) || time.isBefore(ONE_MINUTE_AFTER_MIDNIGHT);
}

This is so counter-intuitive, isn't it? The trick is that 00:00:01 are not after 23:59, so that's always going to fail. This is so because LocalTime.isAfter or LocalTime.isBefore assumes that those are times of the same day.

Tamas Rev
  • 7,008
  • 5
  • 32
  • 49
17

Instead of checking if time is after 23:59 and before 00:01, you should check if it is after 23:59 or before 00:01.

public boolean isAtMidnight(LocalTime time){
    return time.isAfter(ONE_MINUTE_BEFORE_MIDNIGHT) || time.isBefore(ONE_MINUTE_AFTER_MIDNIGHT);
}

If we look at the implementation for LocalTime#isAfter, we see the following:

public boolean isAfter(LocalTime other) {
    return compareTo(other) > 0;
}

Looking at LocalTime#compareTo:

@Override
public int compareTo(LocalTime other) {
    int cmp = Integer.compare(hour, other.hour);
    if (cmp == 0) {
        cmp = Integer.compare(minute, other.minute);
        if (cmp == 0) {
            cmp = Integer.compare(second, other.second);
            if (cmp == 0) {
                cmp = Integer.compare(nano, other.nano);
            }
        }
    }
    return cmp;
}

We can see that two instances of LocalTime are first compared by their respective hours, then minutes, then seconds, and finally nanoseconds. For LocalTime#compareTo to return a value greater than 0 to satisfy LocalTime#isAfter, the hour of the first LocalTime instance must be greater than the second instance's. This is not true for 00:00 and 23:59, hence why your method returns false. The same analysis can be done for LocalTime#isBefore, and you'll arrive at the same result.

Keep in mind that you can just check against LocalTime.MIDNIGHT if you want to be exact, but I assume you're considering any time within a 1-minute range to be "midnight" (including seconds).

Jacob G.
  • 28,856
  • 5
  • 62
  • 116
3

If you really need to do this that way, it cannot fulfil both statements. Consider using or, this code returns true for me:

public static void main(String[] args)
{
    LocalTime ONE_MINUTE_BEFORE_MIDNIGHT = LocalTime.of(23, 59, 0);
    LocalTime ONE_MINUTE_AFTER_MIDNIGHT = LocalTime.of(0, 1, 0);
    LocalTime md = LocalTime.MIDNIGHT;
    System.out.println(md.isBefore(ONE_MINUTE_AFTER_MIDNIGHT) || md.isAfter(ONE_MINUTE_BEFORE_MIDNIGHT));
}
Andronicus
  • 25,419
  • 17
  • 47
  • 88
3

As already explained, you have to use || instead of &&. I think is more readable if you reorganize the variables in the condition this way:

public boolean isAtMidnight(LocalTime time) {
    return ONE_MINUTE_BEFORE_MIDNIGHT.isBefore(time) || ONE_MINUTE_AFTER_MIDNIGHT.isAfter(time);
}

One minute before midnight is before...

Juan Carlos Mendoza
  • 5,736
  • 7
  • 25
  • 50