Thanks! I think that your experiment confirms that we do _not_ need subtraction, at least initially. A single, focused method for saturated addition should be enough. I'll update PR ( https://github.com/openjdk/jdk/pull/27549 ), which you are welcome to review.
Stephen, are you okay with this? -Pavel On Mon, Oct 13, 2025 at 6:56 PM Éamonn McManus <[email protected]> wrote: > > On Thu, 9 Oct 2025 at 07:25, Pavel Rappo <[email protected]> wrote: >> >> Out of curiosity, how is Instants.saturatedSubtract(Instant, Duration) used? > > > That is an excellent question, and it prompted me to look into saturated > addition and subtraction in a bit more detail. As an experiment, I tried > running all of Google's internal tests against a modified Instants class > where saturatedAdd and saturatedSubtract behaved identically to regular > Instant.add/subtract(Duration). That led to hundreds of test failures, > although I think there might have been fewer than 10 distinct root causes. > Then I retried the failing tests with only saturatedSubtract being changed. > All but one of the previously-failing tests now passed. That shows that > saturatedAdd has a much wider applicability than saturatedSubtract. > > I get the impression from looking at some samples that people are often using > saturatedSubtract out of an abundance of caution rather than having a real > reason to think the subtraction might overflow. Many of the examples I saw > were using fixed-length durations, which could only overflow if being > subtracted from Instant.MIN or thereabouts. > > The one test that failed with saturatedSubtract was testing code that does > something like this: > > Instant finalizationTime = ...; > boolean recentlyFinalized = > finalizationTime.isAfter( > Instants.saturatedSubtract(now, minAgeForDeletion)); > > The idea is that something can only be deleted if it was not "recently > finalized". If it was finalized less than minAgeForDeletion time ago then it > can't be deleted. In the failing test, minAgeForDeletion is Durations.MAX, > which signals that something can never be deleted (it will always be recently > finalized). > The code could just as easily do this: > > boolean recentlyFinalized = > now.isBefore( > Instants.saturatedAdd(finalizationTime, minAgeForDeletion)); > > So I don't think this is a particularly convincing use case for > saturatedSubtract. The main reason for having it would be that it might be > surprising to have add but not subtract. > > Éamonn > > On Thu, 9 Oct 2025 at 07:25, Pavel Rappo <[email protected]> wrote: >> >> Out of curiosity, how is Instants.saturatedSubtract(Instant, Duration) used? >> >> On Wed, Oct 8, 2025 at 2:13 AM Éamonn McManus <[email protected]> wrote: >> > >> > Yes, we have utility classes Instants and Durations, and Instants includes >> > methods Instants.saturatedAdd(Instant, Duration) and >> > Instants.saturatedSubtract(Instant, Duration). Each has a modest number of >> > uses in the codebase, about three orders of magnitude less than the number >> > of classes that reference Instant or Duration. >> > >> > On Tue, 7 Oct 2025 at 14:18, Pavel Rappo <[email protected]> wrote: >> >> >> >> Éamonn, Kurt, >> >> >> >> Is there any saturating arithmetic for instant + duration in your code >> >> base? >> >> >> >> >> >> >> >> On Fri, Sep 5, 2025 at 8:58 PM Éamonn McManus <[email protected]> wrote: >> >> > >> >> > As promised, Kurt and I have examined some of the uses of our >> >> > Durations.MAX constant. This is a summary of what we see in a random >> >> > sample of 30 out of about 700 usages in Google's (giant) codebase. >> >> > >> >> > First, about half of the usages specifically concern deadlines. Many of >> >> > them involve a method that sets an RPC deadline and where it is >> >> > explicitly documented that you should use Durations.MAX to mean no >> >> > deadline (or equivalently, infinite deadline). >> >> > >> >> > Several other usages concern deadline-adjacent concepts such as >> >> > time-to-live or cache expiration delay. >> >> > >> >> > A number of usages specifically compare a Duration against >> >> > Durations.MAX to recognize the "infinite duration" value. >> >> > >> >> > One usage uses our internal Sleeper interface to do >> >> > sleeper.sleep(Durations.MAX), for an indefinite sleep (until >> >> > interrupted). >> >> > >> >> > In a couple of places, there is a maximum allowed Duration for some >> >> > operation, and a user-supplied Duration value is capped by this >> >> > maximum. When no maximum is needed, the cap is Durations.MAX. This is >> >> > similar to the "sentinel" use case I mentioned earlier. >> >> > >> >> > One case is using Durations.MAX in a test, to ensure that a function >> >> > works correctly for all Duration values including the largest one. It >> >> > is testing a Kotlin extension function that allows you to write e.g. >> >> > 5.minutes: >> >> > >> >> > val n = Durations.MAX.toMinutes() >> >> > >> >> > assertThat(n.minutes).isEqualTo(Duration.ofMinutes(n)) >> >> > >> >> > assertFailsWith<ArithmeticException> { (n + 1).minutes } >> >> > >> >> > >> >> > In summary, I think the most interesting use cases fall into these >> >> > categories: >> >> > >> >> > to express an effectively-infinite Duration, possibly accompanied by >> >> > special-case logic to optimize the exact value Durations.MAX; >> >> > >> >> > to express the absence of an optional cap on a user-supplied Duration >> >> > value; >> >> > >> >> > to test that code works correctly even with extreme Duration values. >> >> > >> >> > >> >> > Of these, the first is potentially fragile because of the overflow >> >> > problems we discussed. The other two seem unproblematic, though. >> >> > >> >> > >> >> > >> >> > On Thu, 4 Sept 2025 at 15:02, Éamonn McManus <[email protected]> >> >> > wrote: >> >> >> >> >> >> Two typical use cases: >> >> >> >> >> >> // 1. Sentinel >> >> >> Duration min = Duration.MAX; >> >> >> for (var foo : something()) { >> >> >> if (foo.duration().compareTo(min) < 0) { >> >> >> min = foo.duration(); >> >> >> } >> >> >> } >> >> >> >> >> >> // 2. "Forever" >> >> >> void frob(Optional<Duration> optionalTimeout) { >> >> >> Duration timeout = optionalTimeout.orElse(Duration.MAX); >> >> >> Instant start = Instant.now(); >> >> >> boolean done = false; >> >> >> while (!done && startTime.until(Instant.now()).compareTo(timeout) < >> >> >> 0) {...} >> >> >> } >> >> >> >> >> >> The second case illustrates why this is potentially a bit delicate. >> >> >> You better not write this: >> >> >> >> >> >> void frob(Optional<Duration> optionalTimeout) { >> >> >> Duration timeout = optionalTimeout.orElse(Duration.MAX); >> >> >> Instant deadline = Instant.now().plus(timeout); // oops >> >> >> boolean done = false; >> >> >> while (!done && Instant.now().isBefore(deadline)) {...} >> >> >> } >> >> >> >> >> >> Like Kevin, I am skeptical about Duration.MIN. If it means the most >> >> >> negative Duration, that is just Duration.MAX.negated(); and if it >> >> >> means the smallest positive Duration, that is just Duration.ofNanos(1). >> >> >> >> >> >> On Wed, 3 Sept 2025 at 18:32, Roger Riggs <[email protected]> >> >> >> wrote: >> >> >>> >> >> >>> Hi, >> >> >>> >> >> >>> I'd be interested in the range of use cases for Duration.MAX or MIN. >> >> >>> >> >> >>> But for deadlines, I think the code should compute the deadline from >> >> >>> a Duration of its choice based on the use. >> >> >>> Maybe there is a use for Duration.REALLY_BIG or _SMALL, but that >> >> >>> ignores information about the particular use that is relevant. Its >> >> >>> just sloppy code that doesn't bother to express how long is long >> >> >>> enough to meet operational parameters. >> >> >>> >> >> >>> YMMV, Roger >> >> >>> >> >> >>> On 9/3/25 8:21 PM, Kurt Alfred Kluever wrote: >> >> >>> >> >> >>> Duration.MIN is a whole 'nother bag of worms, because Durations are >> >> >>> signed (they can be positive or negative...or zero). Internally we >> >> >>> also have Durations.MIN, but it's not public ... and along with it, I >> >> >>> left myself a helpful note about naming: >> >> >>> >> >> >>> /** The minimum supported {@code Duration}, approximately -292 >> >> >>> billion years. */ >> >> >>> // Note: before making this constant public, consider that "MIN" >> >> >>> might not be a great name (not >> >> >>> // everyone knows that Durations can be negative!). >> >> >>> static final Duration MIN = Duration.ofSeconds(Long.MIN_VALUE); >> >> >>> >> >> >>> This reminds me of Double.MIN_VALUE (which is the smallest _positive_ >> >> >>> double value) --- we've seen Double.MIN_VALUE misused so much that we >> >> >>> introduced Doubles.MIN_POSITIVE_VALUE as a more descriptive alias. A >> >> >>> large percent of Double.MIN_VALUE users actually want the smallest >> >> >>> possible negative value, aka -Double.MAX_VALUE. >> >> >>> >> >> >>> If we introduce Duration.MIN, I hope it would not be >> >> >>> Duration.ofNanos(1), but rather Duration.ofSeconds(Long.MIN_VALUE). >> >> >>> >> >> >>> On Wed, Sep 3, 2025 at 7:59 PM ecki <[email protected]> wrote: >> >> >>>> >> >> >>>> If you ask me, I don’t find it very useful, It won’t work for >> >> >>>> arithmetrics, even the APIs would have a hard time using it (how do >> >> >>>> you express the deadline) and APIs with a timeout parameter do have >> >> >>>> a good reason for it, better pick “possible” values for better self >> >> >>>> healing and unstuck of systems. In fact I would err on the smaller >> >> >>>> side in combination with expecting spurious wakeups. >> >> >>>> >> >> >>>> BTW, when you introduce MIN as well, maybe also think about min >> >> >>>> precision, min delta or such. Will it always be 1 nano? >> >> >>>> >> >> >>>> Gruß, >> >> >>>> Bernd >> >> >>>> -- >> >> >>>> https://bernd.eckenfels.net >> >> >>>> ________________________________ >> >> >>>> Von: core-libs-dev <[email protected]> im Auftrag von >> >> >>>> Pavel Rappo <[email protected]> >> >> >>>> Gesendet: Donnerstag, September 4, 2025 12:41 AM >> >> >>>> An: Kurt Alfred Kluever <[email protected]> >> >> >>>> Cc: Stephen Colebourne <[email protected]>; core-libs-dev >> >> >>>> <[email protected]> >> >> >>>> Betreff: Re: Duration.MAX_VALUE >> >> >>>> >> >> >>>> This is useful; thanks. It would be good to see more of your data. >> >> >>>> >> >> >>>> My use case is also duration which practically means **forever**. I >> >> >>>> pass it to methods that accept timeouts, and expect these methods to >> >> >>>> correctly interpret it. >> >> >>>> >> >> >>>> One example of a practical interpretation is >> >> >>>> java.util.concurrent.TimeUnit.convert(Duration). This method never >> >> >>>> overflows; instead, it caps at Long.MAX_VALUE nanoseconds, which is >> >> >>>> roughly 292 years. >> >> >>>> >> >> >>>> Would I be okay, if the proposed duration didn't reflect **forever** >> >> >>>> but instead reflected **long enough**? I think so. But it still >> >> >>>> somehow feels wrong to make it less than maximum representable value. >> >> >>>> >> >> >>>> Personally, I'm not interested in calendar arithmetic, that is, in >> >> >>>> adding or subtracting durations. Others might be, and that's okay and >> >> >>>> needs to be factored in. For better or worse, java.time made a choice >> >> >>>> to be unforgiving in regard to overflow and is very upfront about it. >> >> >>>> It's not only proposed Duration.MAX. The same thing happens if you >> >> >>>> try >> >> >>>> this >> >> >>>> >> >> >>>> Instant.MAX.toEpochMilli() >> >> >>>> >> >> >>>> I guess my point is that doing calendar arithmetic on an unknown >> >> >>>> value >> >> >>>> is probably wrong. Doing it on a known huge/edge-case value is surely >> >> >>>> wrong. So back to your data. I would be interested to see what >> >> >>>> triggers overflows for your Durations.MAX. >> >> >>>> >> >> >>>> On Wed, Sep 3, 2025 at 8:45 PM Kurt Alfred Kluever <[email protected]> >> >> >>>> wrote: >> >> >>>> > >> >> >>>> > Hi all, >> >> >>>> > >> >> >>>> > Internally at Google, we've had a Durations.MAX constant exposed >> >> >>>> > for the past 7 years. It now has about 700 usages across our >> >> >>>> > depot, which I can try to categorize (at a future date). >> >> >>>> > >> >> >>>> > While I haven't performed that analysis yet, I think exposing this >> >> >>>> > constant was a bit of a mistake. People seem to want to use MAX to >> >> >>>> > mean "forever" (often in regards to an RPC deadline). This works >> >> >>>> > fine as long as every single layer that touches the deadline is >> >> >>>> > very careful about overflow. The only reasonable thing you can do >> >> >>>> > with MAX is compareTo() and equals(). Attempting to do any simple >> >> >>>> > math operation (e.g., now+deadline) is going to explode. >> >> >>>> > Additionally, decomposing Duration.MAX explodes for any sub-second >> >> >>>> > precision (e.g., toMillis()). >> >> >>>> > >> >> >>>> > As we dug into this, another proposal came up which was something >> >> >>>> > like Durations.VERY_LONG. This duration would be longer than any >> >> >>>> > reasonable finite duration but not long enough to cause an >> >> >>>> > overflow when added to any reasonable time. E.g., a million years >> >> >>>> > would probably satisfy both criteria. This would mean math >> >> >>>> > operations and decompositions won't explode (well, microseconds >> >> >>>> > and nanoseconds still would), and it could safely be used as a >> >> >>>> > relative timeout. >> >> >>>> > >> >> >>>> > As I mentioned above, I'd be happy to try to categorize a sample >> >> >>>> > of our 700 existing usages if folks think that would be useful for >> >> >>>> > this proposal. >> >> >>>> > >> >> >>>> > Thanks, >> >> >>>> > >> >> >>>> > -Kurt Alfred Kluever (on behalf of Google's Java and Kotlin >> >> >>>> > Ecosystem team) >> >> >>>> > >> >> >>>> > On Wed, Sep 3, 2025 at 1:53 PM Pavel Rappo <[email protected]> >> >> >>>> > wrote: >> >> >>>> >> >> >> >>>> >> If I understood you correctly, you think we should also add >> >> >>>> >> Duration.MIN. If so, what use case do you envision for it? Or we >> >> >>>> >> add >> >> >>>> >> if purely for symmetry with Instant? >> >> >>>> >> >> >> >>>> >> On Wed, Sep 3, 2025 at 6:43 PM Pavel Rappo >> >> >>>> >> <[email protected]> wrote: >> >> >>>> >> > >> >> >>>> >> > On Wed, Sep 3, 2025 at 6:06 PM Stephen Colebourne >> >> >>>> >> > <[email protected]> wrote: >> >> >>>> >> > > >> >> >>>> >> > > Hmm, yes. Not sure why that didn't get added in Java 8! >> >> >>>> >> > > The constants would be MAX/MIN as per classes like Instant. >> >> >>>> >> > > Stephen >> >> >>>> >> > >> >> >>>> >> > I thought that naming could be tricky :) The public constant >> >> >>>> >> > Duration.ZERO and the public method isZero() are already there. >> >> >>>> >> > However, it does not preclude us from naming a new constant MAX. >> >> >>>> > >> >> >>>> > >> >> >>>> > >> >> >>>> > -- >> >> >>>> > kak >> >> >>>> >> >> >>> >> >> >>> >> >> >>> -- >> >> >>> kak >> >> >>> >> >> >>>
