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 b509e0b67 Keep DurationFormatUtils.lexx() from merging repeated fields 
split by a literal (#1738)
b509e0b67 is described below

commit b509e0b678a8f8d1258260ee8eaa329560976c6d
Author: Gary Gregory <[email protected]>
AuthorDate: Thu Jul 2 14:44:30 2026 +0000

    Keep DurationFormatUtils.lexx() from merging repeated fields split by a
    literal (#1738)
    
    - Sort members.
    - Remove some vertical whitespace
---
 src/changes/changes.xml                            |   1 +
 .../lang3/time/DurationFormatUtilsTest.java        | 133 +++++++--------------
 2 files changed, 46 insertions(+), 88 deletions(-)

diff --git a/src/changes/changes.xml b/src/changes/changes.xml
index 03d463780..01a71beba 100644
--- a/src/changes/changes.xml
+++ b/src/changes/changes.xml
@@ -216,6 +216,7 @@ java.lang.NullPointerException: Cannot invoke
     <action issue="LANG-1828" type="fix" dev="ggregory" due-to="Dhruv 
Aggarwal, Gary Gregory">Fix OOM in StringUtils.leftPad/rightPad when size is 
Integer.MIN_VALUE (#1736).</action>
     <action                   type="fix" dev="ggregory" due-to="alhudz, Gary 
Gregory">Fix Strings.CI.indexOf() returning out-of-range index for empty search 
(#1737).</action>
     <action issue="LANG-1827" type="fix" dev="ggregory" due-to="Sarankumar 
Baskar, Gary Gregory">Support escaped single quotes ('') in DurationFormatUtils 
format patterns (#1700).</action>
+    <action                   type="fix" dev="ggregory" due-to="alhudz, Gary 
Gregory">Keep DurationFormatUtils.lexx() from merging repeated fields split by 
a literal (#1738).</action>
     <!-- ADD -->
     <action                   type="add" dev="ggregory" due-to="Gary 
Gregory">Add JavaVersion.JAVA_27.</action>
     <action                   type="add" dev="ggregory" due-to="Gary 
Gregory">Add SystemUtils.IS_JAVA_27.</action>
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 11d81c35e..572682e9a 100644
--- a/src/test/java/org/apache/commons/lang3/time/DurationFormatUtilsTest.java
+++ b/src/test/java/org/apache/commons/lang3/time/DurationFormatUtilsTest.java
@@ -49,6 +49,22 @@ class DurationFormatUtilsTest extends AbstractLangTest {
 
     private static final int FOUR_YEARS = 365 * 3 + 366;
 
+    private static Arguments[] testLANG1827() {
+        final long twoHours = Duration.ofHours(2).toMillis();
+        final long twoHoursThirtyMin = 
Duration.ofHours(2).plusMinutes(30).toMillis();
+        final long oneDayTwoHours = Duration.ofDays(1).plusHours(2).toMillis();
+        return new Arguments[] {
+                Arguments.of("escaped quote inside literal", "2 o'clock", 
twoHours, "H' o''clock'"),
+                Arguments.of("escaped quote at start of literal", "it's 2 
hours", twoHours, "'it''s 'H' hours'"),
+                Arguments.of("escaped quote outside literal", "2'30", 
twoHoursThirtyMin, "H''m"),
+                Arguments.of("multiple escaped quotes", "it's been 1 day's and 
2 hour's", oneDayTwoHours,
+                        "'it''s been 'd' day''s and 'H' hour''s'"),
+                Arguments.of("standalone escaped quote", "2h'30m", 
twoHoursThirtyMin, "H'h'''m'm'"),
+                Arguments.of("escaped quote inside optional block", "2 
hour's", twoHours, "[d' day''s ']H' hour''s'"),
+                Arguments.of("existing literal behavior", "2 hours 30 
minutes", twoHoursThirtyMin, "H' hours 'm' minutes'")
+        };
+    }
+
     private void assertEqualDuration(final String expected, final int[] start, 
final int[] end, final String format) {
         assertEqualDuration(null, expected, start, end, format);
     }
@@ -123,21 +139,6 @@ void testAlternatingLiteralOptionals() {
                 
DurationFormatUtils.formatDuration(Duration.ofDays(1).plusHours(1).plusMinutes(1).plusSeconds(1).plusMillis(1).toMillis(),
 format));
     }
 
-    @Test
-    void testRepeatedTokenSeparatedByLiteral() {
-        final long fiveHours = Duration.ofHours(5).toMillis();
-        // the same field letter on either side of a literal are two separate 
fields, not one padded field
-        assertEquals("5x5", DurationFormatUtils.formatDuration(fiveHours, 
"HxH", false));
-        assertEquals("5x5", DurationFormatUtils.formatDuration(fiveHours, 
"H'x'H", false));
-        // each H is a single-width field, so padding does not turn either 
into "05"
-        assertEquals("5x5", DurationFormatUtils.formatDuration(fiveHours, 
"HxH", true));
-        // separated by an optional block
-        assertEquals("5x5", DurationFormatUtils.formatDuration(fiveHours, 
"H[x]H", false));
-        assertEquals("2-2", 
DurationFormatUtils.formatDuration(Duration.ofDays(2).toMillis(), "d-d", 
false));
-        // adjacent repeats still collapse into a single padded field
-        assertEquals("05", DurationFormatUtils.formatDuration(fiveHours, "HH", 
true));
-    }
-
     /** See https://issues.apache.org/bugzilla/show_bug.cgi?id=38401 */
     @Test
     void testBugzilla38401() {
@@ -178,24 +179,17 @@ void testEdgeDurations() {
         assertEqualDuration("12", new int[] { 2005, 0, 15, 0, 0, 0 }, new 
int[] { 2006, 0, 15, 0, 0, 0 }, "MM");
         assertEqualDuration("12", new int[] { 2005, 0, 15, 0, 0, 0 }, new 
int[] { 2006, 0, 16, 0, 0, 0 }, "MM");
         assertEqualDuration("11", new int[] { 2005, 0, 15, 0, 0, 0 }, new 
int[] { 2006, 0, 14, 0, 0, 0 }, "MM");
-
         assertEqualDuration("01 26", new int[] { 2006, 0, 15, 0, 0, 0 }, new 
int[] { 2006, 2, 10, 0, 0, 0 }, "MM dd");
         assertEqualDuration("54", new int[] { 2006, 0, 15, 0, 0, 0 }, new 
int[] { 2006, 2, 10, 0, 0, 0 }, "dd");
-
         assertEqualDuration("09 12", new int[] { 2006, 1, 20, 0, 0, 0 }, new 
int[] { 2006, 11, 4, 0, 0, 0 }, "MM dd");
         assertEqualDuration("287", new int[] { 2006, 1, 20, 0, 0, 0 }, new 
int[] { 2006, 11, 4, 0, 0, 0 }, "dd");
-
         assertEqualDuration("11 30", new int[] { 2006, 0, 2, 0, 0, 0 }, new 
int[] { 2007, 0, 1, 0, 0, 0 }, "MM dd");
         assertEqualDuration("364", new int[] { 2006, 0, 2, 0, 0, 0 }, new 
int[] { 2007, 0, 1, 0, 0, 0 }, "dd");
-
         assertEqualDuration("12 00", new int[] { 2006, 0, 1, 0, 0, 0 }, new 
int[] { 2007, 0, 1, 0, 0, 0 }, "MM dd");
         assertEqualDuration("365", new int[] { 2006, 0, 1, 0, 0, 0 }, new 
int[] { 2007, 0, 1, 0, 0, 0 }, "dd");
-
         assertEqualDuration("31", new int[] { 2006, 0, 1, 0, 0, 0 }, new int[] 
{ 2006, 1, 1, 0, 0, 0 }, "dd");
-
         assertEqualDuration("92", new int[] { 2005, 9, 1, 0, 0, 0 }, new int[] 
{ 2006, 0, 1, 0, 0, 0 }, "dd");
         assertEqualDuration("77", new int[] { 2005, 9, 16, 0, 0, 0 }, new 
int[] { 2006, 0, 1, 0, 0, 0 }, "dd");
-
         // test month larger in start than end
         assertEqualDuration("136", new int[] { 2005, 9, 16, 0, 0, 0 }, new 
int[] { 2006, 2, 1, 0, 0, 0 }, "dd");
         // test when start in leap year
@@ -204,30 +198,21 @@ void testEdgeDurations() {
         assertEqualDuration("137", new int[] { 2003, 9, 16, 0, 0, 0 }, new 
int[] { 2004, 2, 1, 0, 0, 0 }, "dd");
         // test when end in leap year but less than end of feb
         assertEqualDuration("135", new int[] { 2003, 9, 16, 0, 0, 0 }, new 
int[] { 2004, 1, 28, 0, 0, 0 }, "dd");
-
         assertEqualDuration("364", new int[] { 2007, 0, 2, 0, 0, 0 }, new 
int[] { 2008, 0, 1, 0, 0, 0 }, "dd");
         assertEqualDuration("729", new int[] { 2006, 0, 2, 0, 0, 0 }, new 
int[] { 2008, 0, 1, 0, 0, 0 }, "dd");
-
         assertEqualDuration("365", new int[] { 2007, 2, 2, 0, 0, 0 }, new 
int[] { 2008, 2, 1, 0, 0, 0 }, "dd");
         assertEqualDuration("333", new int[] { 2007, 1, 2, 0, 0, 0 }, new 
int[] { 2008, 0, 1, 0, 0, 0 }, "dd");
-
         assertEqualDuration("28", new int[] { 2008, 1, 2, 0, 0, 0 }, new int[] 
{ 2008, 2, 1, 0, 0, 0 }, "dd");
         assertEqualDuration("393", new int[] { 2007, 1, 2, 0, 0, 0 }, new 
int[] { 2008, 2, 1, 0, 0, 0 }, "dd");
-
         assertEqualDuration("369", new int[] { 2004, 0, 29, 0, 0, 0 }, new 
int[] { 2005, 1, 1, 0, 0, 0 }, "dd");
-
         assertEqualDuration("338", new int[] { 2004, 1, 29, 0, 0, 0 }, new 
int[] { 2005, 1, 1, 0, 0, 0 }, "dd");
-
         assertEqualDuration("28", new int[] { 2004, 2, 8, 0, 0, 0 }, new int[] 
{ 2004, 3, 5, 0, 0, 0 }, "dd");
-
         assertEqualDuration("48", new int[] { 1992, 1, 29, 0, 0, 0 }, new 
int[] { 1996, 1, 29, 0, 0, 0 }, "M");
-
         // this seems odd - and will fail if I throw it in as a brute force
         // below as it expects the answer to be 12. It's a tricky edge case
         assertEqualDuration("11", new int[] { 1996, 1, 29, 0, 0, 0 }, new 
int[] { 1997, 1, 28, 0, 0, 0 }, "M");
         // again - this seems odd
         assertEqualDuration("11 28", new int[] { 1996, 1, 29, 0, 0, 0 }, new 
int[] { 1997, 1, 28, 0, 0, 0 }, "M d");
-
     }
 
     @Test
@@ -278,28 +263,20 @@ void testFormatDuration() {
     void testFormatDurationHMS() {
         long time = 0;
         assertEquals("00:00:00.000", 
DurationFormatUtils.formatDurationHMS(time));
-
         time = 1;
         assertEquals("00:00:00.001", 
DurationFormatUtils.formatDurationHMS(time));
-
         time = 15;
         assertEquals("00:00:00.015", 
DurationFormatUtils.formatDurationHMS(time));
-
         time = 165;
         assertEquals("00:00:00.165", 
DurationFormatUtils.formatDurationHMS(time));
-
         time = 1675;
         assertEquals("00:00:01.675", 
DurationFormatUtils.formatDurationHMS(time));
-
         time = 13465;
         assertEquals("00:00:13.465", 
DurationFormatUtils.formatDurationHMS(time));
-
         time = 72789;
         assertEquals("00:01:12.789", 
DurationFormatUtils.formatDurationHMS(time));
-
         time = 12789 + 32 * 60000;
         assertEquals("00:32:12.789", 
DurationFormatUtils.formatDurationHMS(time));
-
         time = 12789 + 62 * 60000;
         assertEquals("01:02:12.789", 
DurationFormatUtils.formatDurationHMS(time));
     }
@@ -618,6 +595,17 @@ void testJiraLang281() {
         assertEqualDuration("09", new int[] { 2005, 11, 31, 0, 0, 0 }, new 
int[] { 2006, 9, 6, 0, 0, 0 }, "MM");
     }
 
+    @ParameterizedTest
+    @MethodSource
+    void testLANG1827(final String label, final String expected, final long 
durationMillis, final String format) {
+        assertEquals(expected, 
DurationFormatUtils.formatDuration(durationMillis, format), label);
+    }
+
+    @Test
+    void testLANG1827UnmatchedQuote() {
+        assertIllegalArgumentException(() -> 
DurationFormatUtils.lexx("'unmatched"));
+    }
+
     @Test
     void testLANG815() {
         final Calendar calendar = Calendar.getInstance();
@@ -635,33 +623,6 @@ void testLANG981() { // unmatched quote char in lexx
         assertIllegalArgumentException(() -> 
DurationFormatUtils.lexx("'yMdHms''S"));
     }
 
-    private static Arguments[] testLANG1827() {
-        final long twoHours = Duration.ofHours(2).toMillis();
-        final long twoHoursThirtyMin = 
Duration.ofHours(2).plusMinutes(30).toMillis();
-        final long oneDayTwoHours = Duration.ofDays(1).plusHours(2).toMillis();
-        return new Arguments[] {
-                Arguments.of("escaped quote inside literal", "2 o'clock", 
twoHours, "H' o''clock'"),
-                Arguments.of("escaped quote at start of literal", "it's 2 
hours", twoHours, "'it''s 'H' hours'"),
-                Arguments.of("escaped quote outside literal", "2'30", 
twoHoursThirtyMin, "H''m"),
-                Arguments.of("multiple escaped quotes", "it's been 1 day's and 
2 hour's", oneDayTwoHours,
-                        "'it''s been 'd' day''s and 'H' hour''s'"),
-                Arguments.of("standalone escaped quote", "2h'30m", 
twoHoursThirtyMin, "H'h'''m'm'"),
-                Arguments.of("escaped quote inside optional block", "2 
hour's", twoHours, "[d' day''s ']H' hour''s'"),
-                Arguments.of("existing literal behavior", "2 hours 30 
minutes", twoHoursThirtyMin, "H' hours 'm' minutes'")
-        };
-    }
-
-    @ParameterizedTest
-    @MethodSource
-    void testLANG1827(final String label, final String expected, final long 
durationMillis, final String format) {
-        assertEquals(expected, 
DurationFormatUtils.formatDuration(durationMillis, format), label);
-    }
-
-    @Test
-    void testLANG1827UnmatchedQuote() {
-        assertIllegalArgumentException(() -> 
DurationFormatUtils.lexx("'unmatched"));
-    }
-
     @Test
     void testLANG982() { // More than 3 millisecond digits following a second
         assertEquals("61.999", DurationFormatUtils.formatDuration(61999, 
"s.S"));
@@ -695,7 +656,6 @@ void testLexx() {
             createTokenWithCount(DurationFormatUtils.m, 1),
             createTokenWithCount(DurationFormatUtils.s, 1),
             createTokenWithCount(DurationFormatUtils.S, 1) }, 
DurationFormatUtils.lexx("yMdHmsS"));
-
         // tests the ISO 8601-like
         assertArrayEquals(new DurationFormatUtils.Token[] {
             createTokenWithCount(DurationFormatUtils.H, 2),
@@ -705,7 +665,6 @@ void testLexx() {
             createTokenWithCount(DurationFormatUtils.s, 2),
             createTokenWithCount(new StringBuilder("."), 1),
             createTokenWithCount(DurationFormatUtils.S, 3) }, 
DurationFormatUtils.lexx("HH:mm:ss.SSS"));
-
         // test the iso extended format
         assertArrayEquals(new DurationFormatUtils.Token[] {
             createTokenWithCount(new StringBuilder("P"), 1),
@@ -723,7 +682,6 @@ void testLexx() {
             createTokenWithCount(new StringBuilder("."), 1),
             createTokenWithCount(DurationFormatUtils.S, 3),
             createTokenWithCount(new StringBuilder("S"), 1) }, 
DurationFormatUtils.lexx(DurationFormatUtils.ISO_EXTENDED_FORMAT_PATTERN));
-
         // test failures in equals
         final DurationFormatUtils.Token token = 
createTokenWithCount(DurationFormatUtils.y, 4);
         assertEquals(token, token);
@@ -772,61 +730,60 @@ void testOptionalLiteralSpecialCharacters() {
 
     @Test
     void testOptionalToken() {
-
-        //make sure optional formats match corresponding adjusted non-optional 
formats
+        // make sure optional formats match corresponding adjusted 
non-optional formats
         assertEquals(
                 DurationFormatUtils.formatDuration(915361000L, 
"d'd'H'h'm'm's's'"),
                 DurationFormatUtils.formatDuration(915361000L, 
"[d'd'H'h'm'm']s's'"));
-
         assertEquals(
                 DurationFormatUtils.formatDuration(9153610L, "H'h'm'm's's'"),
                 DurationFormatUtils.formatDuration(9153610L, 
"[d'd'H'h'm'm']s's'"));
-
         assertEquals(
                 DurationFormatUtils.formatDuration(915361L, "m'm's's'"),
                 DurationFormatUtils.formatDuration(915361L, 
"[d'd'H'h'm'm']s's'"));
-
         assertEquals(
                 DurationFormatUtils.formatDuration(9153L, "s's'"),
                 DurationFormatUtils.formatDuration(9153L, 
"[d'd'H'h'm'm']s's'"));
-
         assertEquals(
                 DurationFormatUtils.formatDuration(9153L, "s's'"),
                 DurationFormatUtils.formatDuration(9153L, 
"[d'd'H'h'm'm']s's'"));
-
         assertEquals(
                 DurationFormatUtils.formatPeriod(9153610L, 915361000L, 
"d'd'H'h'm'm's's'"),
                 DurationFormatUtils.formatPeriod(9153610L, 915361000L, 
"[d'd'H'h'm'm']s's'"));
-
         assertEquals(
                 DurationFormatUtils.formatPeriod(915361L, 9153610L, 
"H'h'm'm's's'"),
                 DurationFormatUtils.formatPeriod(915361L, 9153610L, 
"[d'd'H'h'm'm']s's'"));
-
         assertEquals(
                 DurationFormatUtils.formatPeriod(9153L, 915361L, "m'm's's'"),
                 DurationFormatUtils.formatPeriod(9153L, 915361L, 
"[d'd'H'h'm'm']s's'"));
-
         assertEquals(
                 DurationFormatUtils.formatPeriod(0L, 9153L, "s's'"),
                 DurationFormatUtils.formatPeriod(0L, 9153L, 
"[d'd'H'h'm'm']s's'"));
-
-        //make sure optional parts are actually omitted when zero
-
+        // make sure optional parts are actually omitted when zero
         assertEquals("2h32m33s610ms", 
DurationFormatUtils.formatDuration(9153610L, "[d'd'H'h'm'm's's']S'ms'"));
-
         assertEquals("15m15s361ms", 
DurationFormatUtils.formatDuration(915361L, "[d'd'H'h'm'm's's']S'ms'"));
-
         assertEquals("9s153ms", DurationFormatUtils.formatDuration(9153L, 
"[d'd'H'h'm'm's's']S'ms'"));
-
         assertEquals("915ms", DurationFormatUtils.formatDuration(915L, 
"[d'd'H'h'm'm's's']S'ms'"));
-
-        //make sure we can handle omitting multiple literals after a token
-
+        // make sure we can handle omitting multiple literals after a token
         assertEquals(
                 DurationFormatUtils.formatPeriod(915361L, 9153610L, 
"H'h''h2'm'm's's'"),
                 DurationFormatUtils.formatPeriod(915361L, 9153610L, 
"[d'd''d2'H'h''h2'm'm']s's'"));
     }
 
+    @Test
+    void testRepeatedTokenSeparatedByLiteral() {
+        final long fiveHours = Duration.ofHours(5).toMillis();
+        // the same field letter on either side of a literal are two separate 
fields, not one padded field
+        assertEquals("5x5", DurationFormatUtils.formatDuration(fiveHours, 
"HxH", false));
+        assertEquals("5x5", DurationFormatUtils.formatDuration(fiveHours, 
"H'x'H", false));
+        // each H is a single-width field, so padding does not turn either 
into "05"
+        assertEquals("5x5", DurationFormatUtils.formatDuration(fiveHours, 
"HxH", true));
+        // separated by an optional block
+        assertEquals("5x5", DurationFormatUtils.formatDuration(fiveHours, 
"H[x]H", false));
+        assertEquals("2-2", 
DurationFormatUtils.formatDuration(Duration.ofDays(2).toMillis(), "d-d", 
false));
+        // adjacent repeats still collapse into a single padded field
+        assertEquals("05", DurationFormatUtils.formatDuration(fiveHours, "HH", 
true));
+    }
+
     @Test
     void testUnmatchedOptionalTokens() {
         assertIllegalArgumentException(() -> 
DurationFormatUtils.formatDuration(1, "[s"));

Reply via email to