From 4878865d2e1bb92fd3d228fdf763467b510bf930 Mon Sep 17 00:00:00 2001 From: renechoi Date: Sun, 2 Aug 2026 13:34:47 +0900 Subject: [PATCH] Fix the overflow bound direction in Instants.toMillisSince(Instant) The Javadoc states that a result greater than Long.MAX_VALUE is bound to Long.MAX_VALUE and one lesser than Long.MIN_VALUE to Long.MIN_VALUE, but the bound was selected from the sign of the instant's epoch second. The value being bound is the duration from that instant to now, whose sign is the opposite, so both reachable overflow cases returned the wrong end: Instant.MIN gave Long.MIN_VALUE where the contract asks for Long.MAX_VALUE, and Instant.MAX the reverse. Bind on the sign of the duration instead. The two tests that pinned the previous behavior are updated; both fail without this change. --- .../apache/commons/lang3/time/Instants.java | 9 ++++++--- .../commons/lang3/time/InstantsTest.java | 18 ++++++++---------- 2 files changed, 14 insertions(+), 13 deletions(-) diff --git a/src/main/java/org/apache/commons/lang3/time/Instants.java b/src/main/java/org/apache/commons/lang3/time/Instants.java index 8532afaf9fa..094cb431982 100644 --- a/src/main/java/org/apache/commons/lang3/time/Instants.java +++ b/src/main/java/org/apache/commons/lang3/time/Instants.java @@ -17,6 +17,7 @@ package org.apache.commons.lang3.time; +import java.time.Duration; import java.time.Instant; /** @@ -86,11 +87,13 @@ public static Instant toInstant(final Instant instant, final Instant defaultInst * @return long The duration in milliseconds since the given Instant. */ public static long toMillisSince(final Instant instant) { - final Instant instant2 = toInstant(instant); + // The sign of the duration is the opposite of the sign of the instant's epoch second: an instant far in the past + // yields a large positive duration, an instant far in the future a large negative one. Bind on the duration. + final Duration duration = DurationUtils.since(toInstant(instant)); try { - return DurationUtils.since(instant2).toMillis(); + return duration.toMillis(); } catch (final ArithmeticException e) { - return toBound(instant2, Long.MIN_VALUE, Long.MAX_VALUE); + return duration.isNegative() ? Long.MIN_VALUE : Long.MAX_VALUE; } } diff --git a/src/test/java/org/apache/commons/lang3/time/InstantsTest.java b/src/test/java/org/apache/commons/lang3/time/InstantsTest.java index 0a7bfdb9f47..1da2587478d 100644 --- a/src/test/java/org/apache/commons/lang3/time/InstantsTest.java +++ b/src/test/java/org/apache/commons/lang3/time/InstantsTest.java @@ -179,23 +179,21 @@ void testToMillisSinceFutureInstantIsNegative() { } /** - * {@link Instant#MAX} (positive epoch second): the huge negative duration from Instant.MAX to now - * overflows {@code long} millis; the bound is {@link Long#MAX_VALUE} because the instant's epoch - * second is positive. + * {@link Instant#MAX} is in the future, so the duration from it to now is negative and underflows {@code long} + * millis; the bound is {@link Long#MIN_VALUE}. */ @Test - void testToMillisSinceInstantMaxOverflowReturnsMaxValue() { - assertEquals(Long.MAX_VALUE, Instants.toMillisSince(Instant.MAX)); + void testToMillisSinceInstantMaxUnderflowReturnsMinValue() { + assertEquals(Long.MIN_VALUE, Instants.toMillisSince(Instant.MAX)); } /** - * {@link Instant#MIN} (negative epoch second): the huge positive duration from Instant.MIN to now - * overflows {@code long} millis; the bound is {@link Long#MIN_VALUE} because the instant's epoch - * second is negative. + * {@link Instant#MIN} is in the past, so the duration from it to now is positive and overflows {@code long} + * millis; the bound is {@link Long#MAX_VALUE}. */ @Test - void testToMillisSinceInstantMinOverflowReturnsMinValue() { - assertEquals(Long.MIN_VALUE, Instants.toMillisSince(Instant.MIN)); + void testToMillisSinceInstantMinOverflowReturnsMaxValue() { + assertEquals(Long.MAX_VALUE, Instants.toMillisSince(Instant.MIN)); } /**