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 13d25e059 Fix int overflow in DurationFormatUtils.formatPeriod (#1720)
13d25e059 is described below

commit 13d25e0591e640e53f1238e78f6b0e69d0fd6aea
Author: alhuda <[email protected]>
AuthorDate: Sat Jun 20 22:34:34 2026 +0530

    Fix int overflow in DurationFormatUtils.formatPeriod (#1720)
    
    * fix int overflow in DurationFormatUtils.formatPeriod
    
    * Add formatPeriod tests at the bounds of the long input range
---
 .../commons/lang3/time/DurationFormatUtils.java    | 12 ++++----
 .../lang3/time/DurationFormatUtilsTest.java        | 32 ++++++++++++++++++++++
 2 files changed, 38 insertions(+), 6 deletions(-)

diff --git 
a/src/main/java/org/apache/commons/lang3/time/DurationFormatUtils.java 
b/src/main/java/org/apache/commons/lang3/time/DurationFormatUtils.java
index 9e88bf115..07a53b922 100644
--- a/src/main/java/org/apache/commons/lang3/time/DurationFormatUtils.java
+++ b/src/main/java/org/apache/commons/lang3/time/DurationFormatUtils.java
@@ -540,12 +540,12 @@ public static String formatPeriod(final long startMillis, 
final long endMillis,
         end.setTime(new Date(endMillis));
         // initial estimates
         long milliseconds = end.get(Calendar.MILLISECOND) - 
start.get(Calendar.MILLISECOND);
-        int seconds = end.get(Calendar.SECOND) - start.get(Calendar.SECOND);
-        int minutes = end.get(Calendar.MINUTE) - start.get(Calendar.MINUTE);
-        int hours = end.get(Calendar.HOUR_OF_DAY) - 
start.get(Calendar.HOUR_OF_DAY);
-        int days = end.get(Calendar.DAY_OF_MONTH) - 
start.get(Calendar.DAY_OF_MONTH);
-        int months = end.get(Calendar.MONTH) - start.get(Calendar.MONTH);
-        int years = end.get(Calendar.YEAR) - start.get(Calendar.YEAR);
+        long seconds = end.get(Calendar.SECOND) - start.get(Calendar.SECOND);
+        long minutes = end.get(Calendar.MINUTE) - start.get(Calendar.MINUTE);
+        long hours = end.get(Calendar.HOUR_OF_DAY) - 
start.get(Calendar.HOUR_OF_DAY);
+        long days = end.get(Calendar.DAY_OF_MONTH) - 
start.get(Calendar.DAY_OF_MONTH);
+        long months = end.get(Calendar.MONTH) - start.get(Calendar.MONTH);
+        long years = end.get(Calendar.YEAR) - start.get(Calendar.YEAR);
         // each initial estimate is adjusted in case it is under 0
         while (milliseconds < 0) {
             milliseconds += DateUtils.MILLIS_PER_SECOND;
diff --git 
a/src/test/java/org/apache/commons/lang3/time/DurationFormatUtilsTest.java 
b/src/test/java/org/apache/commons/lang3/time/DurationFormatUtilsTest.java
index fdadfff98..1fe9821ba 100644
--- a/src/test/java/org/apache/commons/lang3/time/DurationFormatUtilsTest.java
+++ b/src/test/java/org/apache/commons/lang3/time/DurationFormatUtilsTest.java
@@ -478,6 +478,38 @@ void testFormatPeriodeStartGreaterEnd() {
         assertIllegalArgumentException(() -> 
DurationFormatUtils.formatPeriod(5000, 2500, "yy/MM"));
     }
 
+    @Test
+    void testFormatPeriodLargeFunnelledValue() {
+        final TimeZone gmt = TimeZones.getTimeZone("GMT");
+        // ~69 years, chosen so the seconds count exceeds Integer.MAX_VALUE 
once days/hours/minutes are
+        // funnelled into the single requested field.
+        final long endMillis = 2_175_984_000_000L;
+        assertEquals("2175984000", DurationFormatUtils.formatPeriod(0, 
endMillis, "s", true, gmt));
+        assertEquals("2175984000000", DurationFormatUtils.formatPeriod(0, 
endMillis, "S", true, gmt));
+        // formatPeriod must agree with formatDuration, which reduces the same 
span in long arithmetic.
+        assertEquals(DurationFormatUtils.formatDuration(endMillis, "s"),
+                DurationFormatUtils.formatPeriod(0, endMillis, "s", true, 
gmt));
+    }
+
+    @Test
+    void testFormatPeriodLongRangeBounds() {
+        // A one-millisecond span sitting at the extremes of the long input 
range must still reduce
+        // correctly, confirming formatPeriod handles the whole range of 
millisecond inputs.
+        assertFormatPeriodOneMilli(Long.MAX_VALUE - 1, Long.MAX_VALUE);
+        assertFormatPeriodOneMilli(Long.MIN_VALUE, Long.MIN_VALUE + 1);
+        assertFormatPeriodOneMilli((long) Integer.MIN_VALUE - 1, 
Integer.MIN_VALUE);
+    }
+
+    private void assertFormatPeriodOneMilli(final long startMillis, final long 
endMillis) {
+        final TimeZone gmt = TimeZones.getTimeZone("GMT");
+        assertEquals("1", DurationFormatUtils.formatPeriod(startMillis, 
endMillis, "S", true, gmt));
+        assertEquals("0/0/0/0/0/0.001",
+                DurationFormatUtils.formatPeriod(startMillis, endMillis, 
"y/M/d/H/m/s.S", true, gmt));
+        // formatPeriod must agree with formatDuration over the full range, 
not just spans near the epoch.
+        assertEquals(DurationFormatUtils.formatDuration(endMillis - 
startMillis, "S"),
+                DurationFormatUtils.formatPeriod(startMillis, endMillis, "S", 
true, gmt));
+    }
+
     @SuppressWarnings("deprecation")
     @Test
     void testFormatPeriodISO() {

Reply via email to