I need to iterate through the whole hours of an interval of a particular day. So given the time at one whole hour on the day in question, say, 01:00, how do I find the next whole hour? The next whole hour would usually be 02:00, but due to summer time (DST) and other anomalies it could be 01:00 again or 03:00 or maybe even other hours.
In case it’s 01:00 again, this is the result I want, as long as I can distinguish from the first 01:00 — so I will need for example a ZonedDateTime
or an Instant
.
The next whole hour may be “midnight”, the start of the next day, but I have no need to go further into the next day.
Easy, you may say, add one hour. But not everywhere does summer time mean adjusting the clock by exactly one hour, so I may not hit a whole hour again. And at least in the past time zone changes have occurred that turned the clocks forward or backward by funny amounts, for example +0:09:40 (Danish time 1894).
Next idea, add an hour and truncate to whole hours. The following example demonstrates that this does not always give the correct time either.
ZoneId zoneId = ZoneId.of("Australia/Lord_Howe");
ZonedDateTime zdt = ZonedDateTime.of(2026, 4, 5,
1, 0, 0, 0, zoneId);
System.out.println(zdt);
System.out.println(zdt.plusHours(1));
System.out.println(zdt.plusHours(1).truncatedTo(ChronoUnit.HOURS));
Output:
2026-04-05T01:00+11:00[Australia/Lord_Howe]
2026-04-05T01:30+10:30[Australia/Lord_Howe]
2026-04-05T01:00+11:00[Australia/Lord_Howe]
Lord Howe Island has a summer time offset of 30 minutes. When summer time ends and the time approaches 02:00, it’s adjusted to 01:30. Truncating gets us back to the 01:00 where we started. I may repeat the operation, but I will always get back where I started.
For precision: I need the next point in time (the next instant) where the clock in the time zone in question is at a whole hour (minutes, seconds and fraction of second all being 0).
I am assuming that time zone transitions/changes/anomalies do not come within a few hours after each other. While neither politicians, the IANA time zone database nor Java’s data model guarantees this, they tend to come at most twice a year, and I consider it an acceptable assumption. Sure, assumption is the mother of all f*ckups. I will discuss this at the end of the answer.
The discussion under Louis Wasserman’s answer convinced me: If you add one hour to a whole hour and don’t get a whole hour, then you must have crossed a transition and have a new UTC offset. In this situation the job is to find the first whole hour after the transition. We can’t know whether it comes before or after the point we’ve landed at, but it cannot be more than an hour away in either backward or forward direction in time. So try backward first, and if not there, then round forward. So I have rewritten the code and have avoided the loop that some seemed not to like.
/**
* @param original A whole hour of the day
* @return The next whole hour of the day
*/
public static ZonedDateTime nextWholeHour(ZonedDateTime original) {
if (! original.truncatedTo(ChronoUnit.HOURS).equals(original)) {
throw new IllegalArgumentException("The original time must be on a whole hour");
}
ZonedDateTime oneHourLater = original.plusHours(1);
// Does rounding back/down/truncating give a useful result?
ZonedDateTime roundedBack = oneHourLater.truncatedTo(ChronoUnit.HOURS);
if (roundedBack.isAfter(original)) {
return roundedBack;
}
// No, round forward/up instead
ZonedDateTime roundedForward
= oneHourLater.plusHours(1).truncatedTo(ChronoUnit.HOURS);
if (roundedForward.isEqual(original)) {
throw new IllegalStateException("Failed to find next whole hour within two hours from " + original);
}
return roundedForward;
}
Trying your example from Lord Howe:
ZoneId zoneId = ZoneId.of("Australia/Lord_Howe");
ZonedDateTime zdt = ZonedDateTime.of(2026, 4, 5,
1, 0, 0, 0, zoneId);
System.out.println(nextWholeHour(zdt));
Output:
2026-04-05T02:00+10:30[Australia/Lord_Howe]
The result is one and half hours after the starting point because this is the first time we again hit a whole hour on the clock.
My assumption: In theory strange patterns of transitions shortly after each other could cause whole hours to come three within one hour or two more than two hours apart. In the former case my code might miss a whole hour, but the resulting chunk would still not be more than one hour long. Could your users live with that? Where I work, my users would realize that this would be a very special situation and would accept the result. In the latter case I think we are better off throwing an exception than producing a chunk more than two hours long, and users will again understand that this is a special situation that the code could not foresee, and will tell you what they want in the situation.
For the really bulletproof solution, since you say you are iterating over all whole hours in some interval, first find all transitions (clock time gaps and clock time overlaps) within the interval (usually 0, occasionally 1, theoretically more). The ZoneRules
object that you can get from the ZoneId
can give them to you. Then find all whole hours before the first transition, between each pair of consecutive transitions and after the last transition.