javajava-timejsr310

Find next occurrence of a time, like TemporalAdjuster


Is there anything in JSR-310 for finding the next occurrence of a given time? I'm really looking for the same thing as this question but for times instead of days.

For example, starting with a date of 2020-09-17 at 06:30 UTC, I'd like to find the next occurrence of 05:30:

LocalTime time = LocalTime.of(5, 30);
ZonedDateTime startDateTime = ZonedDateTime.of(2020, 9, 17, 6, 30, 0, 0, ZoneId.of("UTC"));

ZonedDateTime nextTime = startDateTime.with(TemporalAdjusters.next(time)); // This doesn't exist

in the above, I'd like nextTime to represent 2020-09-18 at 05:30 UTC, i.e. 05:30 the following morning.

To clarify my expectations, all with a time of 05:30:

----------------------------------------
| startDateTime    | expected nextTime |
| 2020-09-07 06:30 | 2020-09-08 05:30  |
| 2020-09-07 05:00 | 2020-09-07 05:30  |
| 2020-09-07 05:30 | 2020-09-08 05:30  |
----------------------------------------

Solution

  • If you just want this to work with LocalDateTimes and LocalTimes, or any other type of Temporal that has a 24-hour day, the logic is quite simple:

    public static TemporalAdjuster nextTime(LocalTime time) {
        return temporal -> {
            LocalTime lt = LocalTime.from(temporal);
            if (lt.isBefore(time)) {
                return temporal.with(time);
            } else {
                return temporal.plus(Duration.ofHours(24)).with(time);
            }
        };
    }
    

    But doing this for all Temporals that have a time component is actually quite hard. Think about what you'd have to do for ZonedDateTime. Rather than adding 24 hours, we might need to add 23 or 25 hours because DST transitions make a "day" shorter or longer. You can somewhat deal with this by adding a "day" instead:

    public static TemporalAdjuster nextTime(LocalTime time) {
        return temporal -> {
            LocalTime lt = LocalTime.from(temporal);
            if (lt.isBefore(time) || !temporal.isSupported(ChronoUnit.DAYS)) {
                return temporal.with(time);
            } else {
                return temporal.plus(1, ChronoUnit.DAYS).with(time);
            }
        };
    }
    

    However, it still doesn’t always handle gaps and overlaps correctly. For example, when we are asking for the next 01:30 since 01:31, and there is an overlap transition of 1 hour at 02:00, i.e. the clock goes back an hour at 02:00. The correct answer is to add 59 minutes, but the above code will give us a date time in the next day. To handle this case you'd need to do something complicated like in Andreas' answer.

    If you look at the other built in temporal adjusters, they are all pretty simple, so I guess they just didn't want to put in this complexity.