RFR: 8368856: Add a method to safely add duration to instant [v5]
Pavel Rappo
prappo at openjdk.org
Wed Oct 1 11:17:06 UTC 2025
On Tue, 30 Sep 2025 23:06:39 GMT, Pavel Rappo <prappo at openjdk.org> wrote:
>> We recently [discussed] the possibility of introducing saturating arithmetic for deadline computation. Consider this PR as a starting point. Once we agree on the implementation, I'll file a CSR.
>>
>> I created a method in `Instant` to add `Duration`. One could argue that the proper way would be to go all the way and create a method in `Temporal` to add `TemporalAmount`. Or maybe even expand the functionality, and create an additional method in `Temporal` to subtract `TemporalAmount`.
>>
>> My current thinking is that if we were to do that, there would be a lot of expensive, unused code. Saturating logic seems to be only useful for `Instant` and `Duration`.
>>
>> Even if we decide to extend `Temporal` to add/subtract `TemporalAmount`, it could always be done later. From the perspective of `Instant`, `plus(TemporalAmount)` will be just an overload of `plus(Duration)`.
>>
>> [discussed]: https://mail.openjdk.org/pipermail/core-libs-dev/2025-September/151098.html
>
> Pavel Rappo has updated the pull request incrementally with one additional commit since the last revision:
>
> Slightly improve corner case
In 1fe394b I added a test case to exercise behaviour similar to the one discussed previously for `minus`. The test case ultimately does this:
Instant.MAX.plus(Duration.between(Instant.MAX, Instant.MIN))
It looks like the result is `Instant.MIN`, but it isn't. It would be `Instant.MIN` if `plus` didn't overflow intermediately.
---
Let me again explain what happens, if only for future reference for myself. `Instant.MAX` is 31556889864403199.999,999,999 seconds. `Duration.between(Instant.MAX, Instant.MIN)` is -63113904031622399.999,999,999 seconds. The sum of those is -31557014167219200 seconds, which is exactly `Instant.MIN`.
However, the internal representation of `Duration` and `Instant` are not what one might think. While `seconds` can be of either sign, `nanos` are always non-negative, within this range 0 <= nanos <= 999,999,999.
So, negative `Duration` -63113904031622399.999,999,999 is represented as -63113904031622200 seconds plus 1 nanosecond. That is, it's a value that is 1 nanosecond closer to 0 than -63113904031622200 is.
When `Instant.plus(Duration)` is invoked, it first creates an `Instant` whose seconds are the sum of the seconds and whose nanoseconds are those of the original `Instant`. Any nanoseconds from `Duration` are then added to that `Instant` to produce yet another `Instant`, which holds the final result.
The overflow happens in the first step of `plus`, because 31556889864403199 - 63113904031622200 is -31557014167219201, which is less than the -31556889864403200 seconds of `Instant.MIN`. If `plus` proceeded to the second step, which is nanos adjustment, it would get 999,999,999 + 1, which is equal to a whole positive second. It would then add it to -31557014167219201 and get -31557014167219200, which is `Instant.MIN`.
-------------
PR Comment: https://git.openjdk.org/jdk/pull/27549#issuecomment-3355846946
More information about the core-libs-dev
mailing list