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));
+ }
}
+