java.time.Duration.between(Temporal start, Temporal end) looks like this, in outline:
try {
return ofNanos(start.until(end, NANOS));
} catch (...) {
long secs = start.until(end, SECONDS);
...adjust for nanosecond difference...
}
I think the rationale was that start.until(end, NANOS) will only overflow if the difference is more nanoseconds than can fit in a signed long, which is about 292 years. Nobody's ever going to be looking at the duration between two times that far apart, right? So we don't care about the exception cost.
It turns out, though, that this is a bit of a foot-gun. We had this plausible-looking method:
public static Instant saturatedAdd(Instant instant, Duration duration) {
if (Duration.between(instant, Instant.MAX).compareTo(duration) < 0) {
return Instant.MAX;
}
return instant.plus(duration);
}
(I'm leaving out the handling of negative Duration values.)
The idea is that we'll add the Duration to the Instant, but if the result is too big for an Instant then we'll return Instant.MAX. So for example you can use Duration.ofSeconds(Long.MAX_VALUE) to mean an infinite duration, and it will work plausibly with this method. The logic is to look at the distance between the given Instant and the maximum possible one. If the given Duration is more than that then return Instant.MAX.
The problem of course is that Duration.between(instant, Instant.MAX) will internally throw in the usual case, so this code ends up being hugely more expensive than expected. When we discovered this we rewrote it to avoid Duration.between.
I think it should be fairly straightforward to rewrite Duration.between so it doesn't rely on exception handling. Would something like this work?
public static Duration between(Temporal startInclusive, Temporal endExclusive) {
long secDiff = endExclusive.getLong(INSTANT_SECONDS) - startInclusive.getLong(INSTANT_SECONDS);
long nanoDiff = endExclusive.getLong(NANO_OF_SECOND) - startInclusive.getLong(NANO_OF_SECOND);
return Duration.ofSeconds(secDiff, nanoDiff);
}
I don't think we even need logic to handle borrows from the nanosecond subtraction, since Duration.ofSeconds will adjust correctly.
try {
return ofNanos(start.until(end, NANOS));
} catch (...) {
long secs = start.until(end, SECONDS);
...adjust for nanosecond difference...
}
I think the rationale was that start.until(end, NANOS) will only overflow if the difference is more nanoseconds than can fit in a signed long, which is about 292 years. Nobody's ever going to be looking at the duration between two times that far apart, right? So we don't care about the exception cost.
It turns out, though, that this is a bit of a foot-gun. We had this plausible-looking method:
public static Instant saturatedAdd(Instant instant, Duration duration) {
if (Duration.between(instant, Instant.MAX).compareTo(duration) < 0) {
return Instant.MAX;
}
return instant.plus(duration);
}
(I'm leaving out the handling of negative Duration values.)
The idea is that we'll add the Duration to the Instant, but if the result is too big for an Instant then we'll return Instant.MAX. So for example you can use Duration.ofSeconds(Long.MAX_VALUE) to mean an infinite duration, and it will work plausibly with this method. The logic is to look at the distance between the given Instant and the maximum possible one. If the given Duration is more than that then return Instant.MAX.
The problem of course is that Duration.between(instant, Instant.MAX) will internally throw in the usual case, so this code ends up being hugely more expensive than expected. When we discovered this we rewrote it to avoid Duration.between.
I think it should be fairly straightforward to rewrite Duration.between so it doesn't rely on exception handling. Would something like this work?
public static Duration between(Temporal startInclusive, Temporal endExclusive) {
long secDiff = endExclusive.getLong(INSTANT_SECONDS) - startInclusive.getLong(INSTANT_SECONDS);
long nanoDiff = endExclusive.getLong(NANO_OF_SECOND) - startInclusive.getLong(NANO_OF_SECOND);
return Duration.ofSeconds(secDiff, nanoDiff);
}
I don't think we even need logic to handle borrows from the nanosecond subtraction, since Duration.ofSeconds will adjust correctly.