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 85f21677325..02c5fd9462c 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 09bb104226a..6f3efe5ca49 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)); + } } +