This is an automated email from the ASF dual-hosted git repository.

garydgregory pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/commons-lang.git


The following commit(s) were added to refs/heads/master by this push:
     new bda068076 Add Instants.toMillisSince(Instant) (#1683)
bda068076 is described below

commit bda0680763ad90264b9cca8233bb3369c03f4e13
Author: Gary Gregory <[email protected]>
AuthorDate: Wed Jun 3 07:26:28 2026 -0400

    Add Instants.toMillisSince(Instant) (#1683)
---
 .../org/apache/commons/lang3/time/Instants.java    | 26 +++++++-
 .../apache/commons/lang3/time/InstantsTest.java    | 70 ++++++++++++++++++++--
 2 files changed, 90 insertions(+), 6 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 3c092f96e..2b28e89cd 100644
--- a/src/main/java/org/apache/commons/lang3/time/Instants.java
+++ b/src/main/java/org/apache/commons/lang3/time/Instants.java
@@ -26,6 +26,10 @@
  */
 public class Instants {
 
+    private static long toBound(final Instant instant, final long negBound, 
final long posBound) {
+        return instant.getEpochSecond() < 0 ? negBound : posBound;
+    }
+
     /**
      * Converts an Instant to milliseconds bound to a {@code long} without 
throwing {@link ArithmeticException}.
      * <ul>
@@ -34,7 +38,7 @@ public class Instants {
      * </ul>
      *
      * @param instant The instant to convert, not null.
-     * @return long milliseconds.
+     * @return long The given Instant in milliseconds.
      * @see Instant#toEpochMilli()
      * @see Long#MIN_VALUE
      * @see Long#MAX_VALUE
@@ -43,7 +47,25 @@ public static long toEpochMillis(final Instant instant) {
         try {
             return instant.toEpochMilli();
         } catch (final ArithmeticException e) {
-            return instant.getEpochSecond() < 0 ? Long.MIN_VALUE : 
Long.MAX_VALUE;
+            return toBound(instant, Long.MIN_VALUE, Long.MAX_VALUE);
+        }
+    }
+
+    /**
+     * Converts an Instant to milliseconds sicne that Instant bound to a 
{@code long} without throwing {@link ArithmeticException}.
+     * <ul>
+     * <li>If the duration milliseconds are greater than {@link 
Long#MAX_VALUE}, then return {@link Long#MAX_VALUE}.</li>
+     * <li>If the duration milliseconds are lesser than {@link 
Long#MIN_VALUE}, then return {@link Long#MIN_VALUE}.</li>
+     * </ul>
+     *
+     * @param instant The instant to convert, not null.
+     * @return long The duration in milliseconds since the given Instant.
+     */
+    public static long toMillisSince(final Instant instant) {
+        try {
+            return DurationUtils.since(instant).toMillis();
+        } catch (final ArithmeticException e) {
+            return toBound(instant, 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 a64644b06..365e12282 100644
--- a/src/test/java/org/apache/commons/lang3/time/InstantsTest.java
+++ b/src/test/java/org/apache/commons/lang3/time/InstantsTest.java
@@ -18,6 +18,7 @@
 package org.apache.commons.lang3.time;
 
 import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
 
 import java.time.Instant;
 
@@ -55,14 +56,12 @@ void testToEpochMillisMinValue() {
 
     @Test
     void testToEpochMillisNegativeMillis() {
-        final Instant instant = Instant.ofEpochMilli(-1_000_000L);
-        assertEquals(-1_000_000L, Instants.toEpochMillis(instant));
+        assertEquals(-1_000_000L, 
Instants.toEpochMillis(Instant.ofEpochMilli(-1_000_000L)));
     }
 
     @Test
     void testToEpochMillisNormalInstant() {
-        final Instant instant = Instant.ofEpochMilli(1_000_000L);
-        assertEquals(1_000_000L, Instants.toEpochMillis(instant));
+        assertEquals(1_000_000L, 
Instants.toEpochMillis(Instant.ofEpochMilli(1_000_000L)));
     }
 
     @Test
@@ -80,4 +79,67 @@ void testToEpochMillisOverflowReturnsMaxValue() {
     void testToEpochMillisUnderflowReturnsMinValue() {
         assertEquals(Long.MIN_VALUE, Instants.toEpochMillis(INSTANT_FAR_PAST));
     }
+
+    /**
+     * Epoch instant (time zero): milliseconds since epoch should be positive 
and equal to
+     * roughly the current time in millis (with a generous lower bound).
+     */
+    @Test
+    void testToMillisSinceEpoch() {
+        // As of 2026, ~1.7 trillion ms have elapsed since epoch.
+        assertTrue(Instants.toMillisSince(Instant.EPOCH) > 0);
+    }
+
+    /**
+     * A future instant one second from now; milliseconds since it should be 
negative (roughly -1_000).
+     */
+    @Test
+    void testToMillisSinceFutureInstantIsNegative() {
+        final Instant oneSecondFuture = Instant.now().plusMillis(1_000);
+        final long millis = Instants.toMillisSince(oneSecondFuture);
+        // Duration from a future instant to now is negative.
+        assertTrue(millis <= -900, "Expected millis <= -900 but was " + 
millis);
+    }
+
+    /**
+     * {@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.
+     */
+    @Test
+    void testToMillisSinceInstantMaxOverflowReturnsMaxValue() {
+        assertEquals(Long.MAX_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.
+     */
+    @Test
+    void testToMillisSinceInstantMinOverflowReturnsMinValue() {
+        assertEquals(Long.MIN_VALUE, Instants.toMillisSince(Instant.MIN));
+    }
+
+    /**
+     * Instant.now(); milliseconds since should be very close to zero (within 
a generous tolerance).
+     */
+    @Test
+    void testToMillisSinceNowIsNearZero() {
+        final Instant now = Instant.now();
+        final long millis = Instants.toMillisSince(now);
+        // Allow a generous 5_000 ms window for slow test environments.
+        assertTrue(millis >= 0 && millis < 5_000, "Expected millis in [0, 
5000) but was " + millis);
+    }
+
+    /**
+     * A past instant one second ago; milliseconds since it should be 
approximately 1_000.
+     */
+    @Test
+    void testToMillisSincePastInstantIsPositive() {
+        final Instant oneSecondAgo = Instant.now().minusMillis(1_000);
+        final long millis = Instants.toMillisSince(oneSecondAgo);
+        // Should be at least 1_000 ms (wall time may have advanced slightly 
more).
+        assertTrue(millis >= 1_000, "Expected millis >= 1000 but was " + 
millis);
+    }
 }

Reply via email to