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() {