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 e4380908b Fix DurationFormatUtils.formatPeriod() calculation when 
pattern omits M (#1780)
e4380908b is described below

commit e4380908b4675dda241247422d6c3dd021bf1253
Author: gaurav kumar pandey <[email protected]>
AuthorDate: Thu Sep 3 07:34:55 2026 +0530

    Fix DurationFormatUtils.formatPeriod() calculation when pattern omits M 
(#1780)
---
 .../commons/lang3/time/DurationFormatUtils.java    | 55 ++++++----------------
 .../lang3/time/DurationFormatUtilsTest.java        | 27 +++++++++++
 2 files changed, 42 insertions(+), 40 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 85f216773..02c5fd946 100644
--- a/src/main/java/org/apache/commons/lang3/time/DurationFormatUtils.java
+++ b/src/main/java/org/apache/commons/lang3/time/DurationFormatUtils.java
@@ -20,7 +20,6 @@
 import java.util.ArrayList;
 import java.util.Calendar;
 import java.util.Date;
-import java.util.GregorianCalendar;
 import java.util.Objects;
 import java.util.TimeZone;
 import java.util.stream.Stream;
@@ -562,47 +561,23 @@ public static String formatPeriod(final long startMillis, 
final long endMillis,
             hours += HOURS_PER_DAY;
             days -= 1;
         }
-        if (Token.containsTokenWithValue(tokens, M)) {
-            while (days < 0) {
-                days += start.getActualMaximum(Calendar.DAY_OF_MONTH);
-                months -= 1;
-                start.add(Calendar.MONTH, 1);
-            }
-            while (months < 0) {
-                months += 12;
-                years -= 1;
-            }
-            if (!Token.containsTokenWithValue(tokens, y) && years != 0) {
-                while (years != 0) {
-                    months += 12 * years;
-                    years = 0;
-                }
-            }
-        } else {
-            // there are no M's in the format string
-            if (!Token.containsTokenWithValue(tokens, y)) {
-                int target = end.get(Calendar.YEAR);
-                if (months < 0) {
-                    // target is end-year -1
-                    target -= 1;
-                }
-                while (start.get(Calendar.YEAR) != target) {
-                    days += start.getActualMaximum(Calendar.DAY_OF_YEAR) - 
start.get(Calendar.DAY_OF_YEAR);
-                    // Not sure I grok why this is needed, but the brutal 
tests show it is
-                    if (start instanceof GregorianCalendar && 
start.get(Calendar.MONTH) == Calendar.FEBRUARY && 
start.get(Calendar.DAY_OF_MONTH) == 29) {
-                        days += 1;
-                    }
-                    start.add(Calendar.YEAR, 1);
-                    days += start.get(Calendar.DAY_OF_YEAR);
-                }
+        while (days < 0) {
+            days += start.getActualMaximum(Calendar.DAY_OF_MONTH);
+            months -= 1;
+            start.add(Calendar.MONTH, 1);
+        }
+        while (months < 0) {
+            months += 12;
+            years -= 1;
+        }
+        if (!Token.containsTokenWithValue(tokens, y) && years != 0) {
+            while (years != 0) {
+                months += 12 * years;
                 years = 0;
             }
-            while (start.get(Calendar.MONTH) != end.get(Calendar.MONTH)) {
-                days += start.getActualMaximum(Calendar.DAY_OF_MONTH);
-                start.add(Calendar.MONTH, 1);
-            }
-            months = 0;
-            while (days < 0) {
+        }
+        if (!Token.containsTokenWithValue(tokens, M)) {
+            while (months > 0) {
                 days += start.getActualMaximum(Calendar.DAY_OF_MONTH);
                 months -= 1;
                 start.add(Calendar.MONTH, 1);
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 09bb10422..6f3efe5ca 100644
--- a/src/test/java/org/apache/commons/lang3/time/DurationFormatUtilsTest.java
+++ b/src/test/java/org/apache/commons/lang3/time/DurationFormatUtilsTest.java
@@ -804,4 +804,31 @@ void testUnmatchedOptionalTokens() {
         assertIllegalArgumentException(() -> 
DurationFormatUtils.formatDuration(1, "[[s"));
         assertIllegalArgumentException(() -> 
DurationFormatUtils.formatDuration(1, "[s]]"));
     }
+
+    @Test
+    void testFormatPeriodWithoutMonths() {
+        final TimeZone timeZone = TimeZone.getTimeZone("UTC");
+        final Calendar start = Calendar.getInstance(timeZone);
+        start.set(2024, Calendar.DECEMBER, 15, 0, 0, 0);
+        start.set(Calendar.MILLISECOND, 0);
+
+        final Calendar end = Calendar.getInstance(timeZone);
+        end.set(2025, Calendar.JANUARY, 15, 0, 0, 0);
+        end.set(Calendar.MILLISECOND, 0);
+
+        // 31 days elapsed across year boundary
+        assertEquals("0 years 31 days", 
DurationFormatUtils.formatPeriod(start.getTimeInMillis(), 
end.getTimeInMillis(), "y' years 'd' days'", false, timeZone));
+        assertEquals("0y 31d", 
DurationFormatUtils.formatPeriod(start.getTimeInMillis(), 
end.getTimeInMillis(), "y'y 'd'd'", false, timeZone));
+
+        // 361 days elapsed (less than 1 full year)
+        start.set(2024, Calendar.JANUARY, 15, 0, 0, 0);
+        end.set(2025, Calendar.JANUARY, 10, 0, 0, 0);
+        assertEquals("0 years 361 days", 
DurationFormatUtils.formatPeriod(start.getTimeInMillis(), 
end.getTimeInMillis(), "y' years 'd' days'", false, timeZone));
+
+        // Leap year to non-leap year (Feb 29, 2024 to Feb 28, 2025 = 365 days)
+        start.set(2024, Calendar.FEBRUARY, 29, 0, 0, 0);
+        end.set(2025, Calendar.FEBRUARY, 28, 0, 0, 0);
+        assertEquals("0 years 365 days", 
DurationFormatUtils.formatPeriod(start.getTimeInMillis(), 
end.getTimeInMillis(), "y' years 'd' days'", false, timeZone));
+    }
 }
+

Reply via email to