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 d8470d57f [LANG-980] Use enum constants for duration format units 
(#1856)
d8470d57f is described below

commit d8470d57fda340864acf5194bb56305a133fba0e
Author: Gary Gregory <[email protected]>
AuthorDate: Mon Nov 2 06:53:03 2026 -0500

    [LANG-980] Use enum constants for duration format units (#1856)
    
    Separate duration units from literal text and use an enum switch for
    formatting. Preserve optional-block handling and fractional-second
    padding, and add regression tests.
---
 src/changes/changes.xml                            |   1 +
 .../commons/lang3/time/DurationFormatUtils.java    | 204 +++++++++++----------
 .../lang3/time/DurationFormatUtilsTest.java        | 132 +++++++++----
 3 files changed, 200 insertions(+), 137 deletions(-)

diff --git a/src/changes/changes.xml b/src/changes/changes.xml
index b37f6430c..26dda99cc 100644
--- a/src/changes/changes.xml
+++ b/src/changes/changes.xml
@@ -85,6 +85,7 @@ The <action> type attribute can be add,update,fix,remove.
     <action issue="LANG-1839" type="fix" dev="ggregory" due-to="Gary Gregory, 
Raju Gupta">Fix recursive toString traversal cleanup (#1855).</action>
     <action issue="LANG-1341" type="fix" dev="ggregory" due-to="Gary Gregory, 
Reassoonn">Javadoc: Clarify superclass equality in EqualsBuilder.</action>
     <action issue="LANG-1423" type="fix" dev="ggregory" due-to="Gary Gregory, 
Reassoonn">Javadoc: Include wrapping operations in StringUtils overview 
(#1850).</action>
+    <action issue="LANG-980"  type="fix" dev="ggregory" due-to="Gary Gregory, 
Sebb">Use enum constants for duration format units in 
DurationFormatUtils.</action>
     <!-- ADD -->
     <action issue="LANG-1790" type="add" dev="ggregory" due-to="Mert Kirpici, 
Ivan Šarić, Gary Gregory">Add optional FIFO ordering for TimedSemaphore 
blocking acquisitions.</action>
     <!-- UPDATE -->
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 089b60f8d..16d2387cf 100644
--- a/src/main/java/org/apache/commons/lang3/time/DurationFormatUtils.java
+++ b/src/main/java/org/apache/commons/lang3/time/DurationFormatUtils.java
@@ -100,29 +100,45 @@ static final class Token {
         private static final Token[] EMPTY_ARRAY = {};
 
         /**
-         * Helper method to determine if a set of tokens contain a value
+         * Determines whether a set of tokens contains a duration unit.
          *
          * @param tokens set to search.
-         * @param value to look find.
+         * @param value duration unit to find.
          * @return boolean {@code true} if contained.
          */
-        static boolean containsTokenWithValue(final Token[] tokens, final 
CharSequence value) {
+        static boolean containsTokenWithValue(final Token[] tokens, final Unit 
value) {
             return Stream.of(tokens).anyMatch(token -> token.getValue() == 
value);
         }
 
-        private final CharSequence value;
+        private final Unit value;
+        private final StringBuilder literal;
         private int count;
         private final int optionalIndex;
 
         /**
-         * Wraps a token around a value. A value would be something like a 'Y'.
+         * Wraps a token around literal text.
          *
-         * @param value value to wrap, non-null.
+         * @param literal literal text to wrap, non-null.
          * @param optional whether the token is optional.
          * @param optionalIndex The index of the optional token within the 
pattern.
          */
-        Token(final CharSequence value, final boolean optional, final int 
optionalIndex) {
+        Token(final StringBuilder literal, final boolean optional, final int 
optionalIndex) {
+            this.value = null;
+            this.literal = Objects.requireNonNull(literal, "literal");
+            this.count = 1;
+            this.optionalIndex = optional ? optionalIndex : -1;
+        }
+
+        /**
+         * Wraps a token around a duration unit.
+         *
+         * @param value duration unit to wrap, non-null.
+         * @param optional whether the token is optional.
+         * @param optionalIndex The index of the optional token within the 
pattern.
+         */
+        Token(final Unit value, final boolean optional, final int 
optionalIndex) {
             this.value = Objects.requireNonNull(value, "value");
+            this.literal = null;
             this.count = 1;
             this.optionalIndex = optional ? optionalIndex : -1;
         }
@@ -137,16 +153,8 @@ static boolean containsTokenWithValue(final Token[] 
tokens, final CharSequence v
         public boolean equals(final Object obj2) {
             if (obj2 instanceof Token) {
                 final Token tok2 = (Token) obj2;
-                if (this.value.getClass() != tok2.value.getClass() || 
this.count != tok2.count) {
-                    return false;
-                }
-                if (this.value instanceof StringBuilder) {
-                    return this.value.toString().equals(tok2.value.toString());
-                }
-                if (this.value instanceof Number) {
-                    return this.value.equals(tok2.value);
-                }
-                return this.value == tok2.value;
+                return value == tok2.value && count == tok2.count
+                        && Objects.equals(literal == null ? null : 
literal.toString(), tok2.literal == null ? null : tok2.literal.toString());
             }
             return false;
         }
@@ -161,22 +169,22 @@ int getCount() {
         }
 
         /**
-         * Gets the particular value this token represents.
+         * Gets the duration unit this token represents.
          *
-         * @return Object value, non-null.
+         * @return the duration unit, or null for a literal token.
          */
-        CharSequence getValue() {
+        Unit getValue() {
             return value;
         }
 
         /**
-         * Returns a hash code for the token equal to the hash code for the 
token's value. Thus 'TT' and 'TTTT' will have the same hash code.
+         * Computes a hash code from the duration unit or literal text, 
excluding the repetition count.
          *
          * @return The hash code for the token.
          */
         @Override
         public int hashCode() {
-            return this.value.hashCode();
+            return value != null ? value.hashCode() : 
literal.toString().hashCode();
         }
 
         /**
@@ -193,7 +201,25 @@ void increment() {
          */
         @Override
         public String toString() {
-            return StringUtils.repeat(this.value.toString(), this.count);
+            return StringUtils.repeat(value != null ? value.toString() : 
literal.toString(), count);
+        }
+    }
+
+    /**
+     * Duration units recognized in format patterns.
+     */
+    enum Unit {
+        YEAR("y"), MONTH("M"), DAY("d"), HOUR("H"), MINUTE("m"), SECOND("s"), 
MILLISECOND("S");
+
+        private final String symbol;
+
+        Unit(final String symbol) {
+            this.symbol = symbol;
+        }
+
+        @Override
+        public String toString() {
+            return symbol;
         }
     }
 
@@ -211,20 +237,6 @@ public String toString() {
      */
     public static final String ISO_EXTENDED_FORMAT_PATTERN = 
"'P'yyyy'Y'M'M'd'DT'H'H'm'M's.SSS'S'";
 
-    static final String y = "y";
-
-    static final String M = "M";
-
-    static final String d = "d";
-
-    static final String H = "H";
-
-    static final String m = "m";
-
-    static final String s = "s";
-
-    static final String S = "S";
-
     /**
      * The internal method to do the formatting.
      *
@@ -250,8 +262,8 @@ static String format(final Token[] tokens, final long 
years, final long months,
         int optionalIndex = -1;
         boolean inOptional = false;
         for (final Token token : tokens) {
-            final CharSequence value = token.getValue();
-            final boolean isLiteral = value instanceof StringBuilder;
+            final Unit unit = token.getValue();
+            final boolean isLiteral = token.literal != null;
             final int count = token.getCount();
             if (optionalIndex != token.optionalIndex) {
               optionalIndex = token.optionalIndex;
@@ -268,56 +280,46 @@ static String format(final Token[] tokens, final long 
years, final long months,
             }
             if (isLiteral) {
                 if (!inOptional || !lastOutputZero) {
-                    buffer.append(value.toString());
-                }
-            } else if (value.equals(y)) {
-                lastOutputSeconds = false;
-                lastOutputZero = years == 0;
-                if (!inOptional || !lastOutputZero) {
-                    buffer.append(paddedValue(years, padWithZeros, count));
-                }
-            } else if (value.equals(M)) {
-                lastOutputSeconds = false;
-                lastOutputZero = months == 0;
-                if (!inOptional || !lastOutputZero) {
-                    buffer.append(paddedValue(months, padWithZeros, count));
-                }
-            } else if (value.equals(d)) {
-                lastOutputSeconds = false;
-                lastOutputZero = days == 0;
-                if (!inOptional || !lastOutputZero) {
-                    buffer.append(paddedValue(days, padWithZeros, count));
+                    buffer.append(token.literal);
                 }
-            } else if (value.equals(H)) {
-                lastOutputSeconds = false;
-                lastOutputZero = hours == 0;
-                if (!inOptional || !lastOutputZero) {
-                    buffer.append(paddedValue(hours, padWithZeros, count));
-                }
-            } else if (value.equals(m)) {
-                lastOutputSeconds = false;
-                lastOutputZero = minutes == 0;
-                if (!inOptional || !lastOutputZero) {
-                    buffer.append(paddedValue(minutes, padWithZeros, count));
-                }
-            } else if (value.equals(s)) {
-                lastOutputSeconds = true;
-                lastOutputZero = seconds == 0;
-                if (!inOptional || !lastOutputZero) {
-                    buffer.append(paddedValue(seconds, padWithZeros, count));
+            } else {
+                final long value;
+                switch (unit) {
+                case YEAR:
+                    value = years;
+                    break;
+                case MONTH:
+                    value = months;
+                    break;
+                case DAY:
+                    value = days;
+                    break;
+                case HOUR:
+                    value = hours;
+                    break;
+                case MINUTE:
+                    value = minutes;
+                    break;
+                case SECOND:
+                    value = seconds;
+                    break;
+                case MILLISECOND:
+                    value = milliseconds;
+                    break;
+                default:
+                    throw new IllegalStateException("Unexpected duration unit: 
" + unit);
                 }
-            } else if (value.equals(S)) {
-                lastOutputZero = milliseconds == 0;
+                lastOutputZero = value == 0;
                 if (!inOptional || !lastOutputZero) {
-                    if (lastOutputSeconds) {
+                    if (unit == Unit.MILLISECOND && lastOutputSeconds) {
                         // ensure at least 3 digits are displayed even if 
padding is not selected
                         final int width = padWithZeros ? Math.max(3, count) : 
3;
-                        buffer.append(paddedValue(milliseconds, true, width));
+                        buffer.append(paddedValue(value, true, width));
                     } else {
-                        buffer.append(paddedValue(milliseconds, padWithZeros, 
count));
+                        buffer.append(paddedValue(value, padWithZeros, count));
                     }
                 }
-                lastOutputSeconds = false;
+                lastOutputSeconds = unit == Unit.SECOND;
             }
             // as soon as we hit first nonliteral in optional, check for 
literal prefix
             if (inOptional && !isLiteral && !firstOptionalNonLiteral) {
@@ -365,19 +367,19 @@ public static String formatDuration(final long 
durationMillis, final String form
         long minutes = 0;
         long seconds = 0;
         long milliseconds = durationMillis;
-        if (Token.containsTokenWithValue(tokens, d)) {
+        if (Token.containsTokenWithValue(tokens, Unit.DAY)) {
             days = milliseconds / DateUtils.MILLIS_PER_DAY;
             milliseconds -= days * DateUtils.MILLIS_PER_DAY;
         }
-        if (Token.containsTokenWithValue(tokens, H)) {
+        if (Token.containsTokenWithValue(tokens, Unit.HOUR)) {
             hours = milliseconds / DateUtils.MILLIS_PER_HOUR;
             milliseconds -= hours * DateUtils.MILLIS_PER_HOUR;
         }
-        if (Token.containsTokenWithValue(tokens, m)) {
+        if (Token.containsTokenWithValue(tokens, Unit.MINUTE)) {
             minutes = milliseconds / DateUtils.MILLIS_PER_MINUTE;
             milliseconds -= minutes * DateUtils.MILLIS_PER_MINUTE;
         }
-        if (Token.containsTokenWithValue(tokens, s)) {
+        if (Token.containsTokenWithValue(tokens, Unit.SECOND)) {
             seconds = milliseconds / DateUtils.MILLIS_PER_SECOND;
             milliseconds -= seconds * DateUtils.MILLIS_PER_SECOND;
         }
@@ -539,7 +541,7 @@ public static String formatPeriod(final long startMillis, 
final long endMillis,
         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);
-        final boolean calendarUnits = Token.containsTokenWithValue(tokens, y) 
|| Token.containsTokenWithValue(tokens, M);
+        final boolean calendarUnits = Token.containsTokenWithValue(tokens, 
Unit.YEAR) || Token.containsTokenWithValue(tokens, Unit.MONTH);
         // Without years or months, count local dates directly instead of 
visiting every intervening month.
         long days = calendarUnits ? end.get(Calendar.DAY_OF_MONTH) - 
start.get(Calendar.DAY_OF_MONTH) : localEpochDay(end) - localEpochDay(start);
         long months = end.get(Calendar.MONTH) - start.get(Calendar.MONTH);
@@ -571,13 +573,13 @@ public static String formatPeriod(final long startMillis, 
final long endMillis,
                 months += 12;
                 years -= 1;
             }
-            if (!Token.containsTokenWithValue(tokens, y) && years != 0) {
+            if (!Token.containsTokenWithValue(tokens, Unit.YEAR) && years != 
0) {
                 while (years != 0) {
                     months += 12 * years;
                     years = 0;
                 }
             }
-            if (!Token.containsTokenWithValue(tokens, M)) {
+            if (!Token.containsTokenWithValue(tokens, Unit.MONTH)) {
                 while (months > 0) {
                     days += start.getActualMaximum(Calendar.DAY_OF_MONTH);
                     months -= 1;
@@ -591,19 +593,19 @@ public static String formatPeriod(final long startMillis, 
final long endMillis,
         // The rest of this code adds in values that
         // aren't requested. This allows the user to ask for the
         // number of months and get the real count and not just 0->11.
-        if (!Token.containsTokenWithValue(tokens, d)) {
+        if (!Token.containsTokenWithValue(tokens, Unit.DAY)) {
             hours += HOURS_PER_DAY * days;
             days = 0;
         }
-        if (!Token.containsTokenWithValue(tokens, H)) {
+        if (!Token.containsTokenWithValue(tokens, Unit.HOUR)) {
             minutes += MINUTES_PER_HOUR * hours;
             hours = 0;
         }
-        if (!Token.containsTokenWithValue(tokens, m)) {
+        if (!Token.containsTokenWithValue(tokens, Unit.MINUTE)) {
             seconds += SECONDS_PER_MINUTES * minutes;
             minutes = 0;
         }
-        if (!Token.containsTokenWithValue(tokens, s)) {
+        if (!Token.containsTokenWithValue(tokens, Unit.SECOND)) {
             milliseconds += DateUtils.MILLIS_PER_SECOND * seconds;
             seconds = 0;
         }
@@ -646,7 +648,7 @@ static Token[] lexx(final String format) {
                 buffer.append(ch); // buffer can't be null if inLiteral is true
                 continue;
             }
-            String value = null;
+            Unit value = null;
             switch (ch) {
             case '[':
                 if (inOptional) {
@@ -688,25 +690,25 @@ static Token[] lexx(final String format) {
                 previous = null;
                 break;
             case 'y':
-                value = y;
+                value = Unit.YEAR;
                 break;
             case 'M':
-                value = M;
+                value = Unit.MONTH;
                 break;
             case 'd':
-                value = d;
+                value = Unit.DAY;
                 break;
             case 'H':
-                value = H;
+                value = Unit.HOUR;
                 break;
             case 'm':
-                value = m;
+                value = Unit.MINUTE;
                 break;
             case 's':
-                value = s;
+                value = Unit.SECOND;
                 break;
             case 'S':
-                value = S;
+                value = Unit.MILLISECOND;
                 break;
             default:
                 if (buffer == null) {
@@ -717,7 +719,7 @@ static Token[] lexx(final String format) {
                 previous = null;
             }
             if (value != null) {
-                if (previous != null && previous.getValue().equals(value)) {
+                if (previous != null && previous.getValue() == value) {
                     previous.increment();
                 } else {
                     final Token token = new Token(value, inOptional, 
optionalIndex);
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 bb8025f1a..a7f8240c0 100644
--- a/src/test/java/org/apache/commons/lang3/time/DurationFormatUtilsTest.java
+++ b/src/test/java/org/apache/commons/lang3/time/DurationFormatUtilsTest.java
@@ -34,6 +34,7 @@
 
 import org.apache.commons.lang3.AbstractLangTest;
 import org.apache.commons.lang3.time.DurationFormatUtils.Token;
+import org.apache.commons.lang3.time.DurationFormatUtils.Unit;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.params.ParameterizedTest;
 import org.junit.jupiter.params.provider.Arguments;
@@ -50,6 +51,21 @@ class DurationFormatUtilsTest extends AbstractLangTest {
 
     private static final int FOUR_YEARS = 365 * 3 + 366;
 
+    private static Arguments[] testFractionalSecondsPadding() {
+        return new Arguments[] {
+                Arguments.of("s.S", false, "1.007"),
+                Arguments.of("s.S", true, "1.007"),
+                Arguments.of("s.SSSS", false, "1.007"),
+                Arguments.of("s.SSSS", true, "1.0007"),
+                Arguments.of("s' seconds 'S", false, "1 seconds 007"),
+                Arguments.of("s.S.S", false, "1.007.7"),
+                Arguments.of("s:m:S", false, "1:0:7"),
+                Arguments.of("s[m':'H]S", false, "17"),
+                Arguments.of("[s'.']SSS", false, "1.007"),
+                Arguments.of("S", false, "1007")
+        };
+    }
+
     private static Arguments[] testLANG1827() {
         final long twoHours = Duration.ofHours(2).toMillis();
         final long twoHoursThirtyMin = 
Duration.ofHours(2).plusMinutes(30).toMillis();
@@ -113,7 +129,7 @@ private void bruteForce(final int year, final int month, 
final int day, final St
         }
     }
 
-    private DurationFormatUtils.Token createTokenWithCount(final CharSequence 
value, final int count) {
+    private DurationFormatUtils.Token createTokenWithCount(final Unit value, 
final int count) {
         final DurationFormatUtils.Token token = new 
DurationFormatUtils.Token(value, false, -1);
         // To help debugging, toString() on a Token should never blow up.
         assertNotNull(token.toString());
@@ -709,6 +725,13 @@ void testFourYears() {
         }
     }
 
+    @ParameterizedTest
+    @MethodSource
+    void testFractionalSecondsPadding(final String format, final boolean 
padWithZeros, final String expected) {
+        assertEquals(expected, DurationFormatUtils.formatDuration(1007, 
format, padWithZeros));
+        assertEquals(expected, DurationFormatUtils.formatPeriod(0, 1007, 
format, padWithZeros, TimeZones.getTimeZone("GMT")));
+    }
+
     // https://issues.apache.org/jira/browse/LANG-281
     @Test
     void testJiraLang281() {
@@ -769,50 +792,47 @@ void testLANG984() { // Long durations
     void testLexx() {
         // tests each constant
         assertArrayEquals(new DurationFormatUtils.Token[] {
-            createTokenWithCount(DurationFormatUtils.y, 1),
-            createTokenWithCount(DurationFormatUtils.M, 1),
-            createTokenWithCount(DurationFormatUtils.d, 1),
-            createTokenWithCount(DurationFormatUtils.H, 1),
-            createTokenWithCount(DurationFormatUtils.m, 1),
-            createTokenWithCount(DurationFormatUtils.s, 1),
-            createTokenWithCount(DurationFormatUtils.S, 1) }, 
DurationFormatUtils.lexx("yMdHmsS"));
+            createTokenWithCount(Unit.YEAR, 1),
+            createTokenWithCount(Unit.MONTH, 1),
+            createTokenWithCount(Unit.DAY, 1),
+            createTokenWithCount(Unit.HOUR, 1),
+            createTokenWithCount(Unit.MINUTE, 1),
+            createTokenWithCount(Unit.SECOND, 1),
+            createTokenWithCount(Unit.MILLISECOND, 1) }, 
DurationFormatUtils.lexx("yMdHmsS"));
         // tests the ISO 8601-like
         assertArrayEquals(new DurationFormatUtils.Token[] {
-            createTokenWithCount(DurationFormatUtils.H, 2),
-            createTokenWithCount(new StringBuilder(":"), 1),
-            createTokenWithCount(DurationFormatUtils.m, 2),
-            createTokenWithCount(new StringBuilder(":"), 1),
-            createTokenWithCount(DurationFormatUtils.s, 2),
-            createTokenWithCount(new StringBuilder("."), 1),
-            createTokenWithCount(DurationFormatUtils.S, 3) }, 
DurationFormatUtils.lexx("HH:mm:ss.SSS"));
+            createTokenWithCount(Unit.HOUR, 2),
+            new Token(new StringBuilder(":"), false, -1),
+            createTokenWithCount(Unit.MINUTE, 2),
+            new Token(new StringBuilder(":"), false, -1),
+            createTokenWithCount(Unit.SECOND, 2),
+            new Token(new StringBuilder("."), false, -1),
+            createTokenWithCount(Unit.MILLISECOND, 3) }, 
DurationFormatUtils.lexx("HH:mm:ss.SSS"));
         // test the iso extended format
         assertArrayEquals(new DurationFormatUtils.Token[] {
-            createTokenWithCount(new StringBuilder("P"), 1),
-            createTokenWithCount(DurationFormatUtils.y, 4),
-            createTokenWithCount(new StringBuilder("Y"), 1),
-            createTokenWithCount(DurationFormatUtils.M, 1),
-            createTokenWithCount(new StringBuilder("M"), 1),
-            createTokenWithCount(DurationFormatUtils.d, 1),
-            createTokenWithCount(new StringBuilder("DT"), 1),
-            createTokenWithCount(DurationFormatUtils.H, 1),
-            createTokenWithCount(new StringBuilder("H"), 1),
-            createTokenWithCount(DurationFormatUtils.m, 1),
-            createTokenWithCount(new StringBuilder("M"), 1),
-            createTokenWithCount(DurationFormatUtils.s, 1),
-            createTokenWithCount(new StringBuilder("."), 1),
-            createTokenWithCount(DurationFormatUtils.S, 3),
-            createTokenWithCount(new StringBuilder("S"), 1) }, 
DurationFormatUtils.lexx(DurationFormatUtils.ISO_EXTENDED_FORMAT_PATTERN));
+            new Token(new StringBuilder("P"), false, -1),
+            createTokenWithCount(Unit.YEAR, 4),
+            new Token(new StringBuilder("Y"), false, -1),
+            createTokenWithCount(Unit.MONTH, 1),
+            new Token(new StringBuilder("M"), false, -1),
+            createTokenWithCount(Unit.DAY, 1),
+            new Token(new StringBuilder("DT"), false, -1),
+            createTokenWithCount(Unit.HOUR, 1),
+            new Token(new StringBuilder("H"), false, -1),
+            createTokenWithCount(Unit.MINUTE, 1),
+            new Token(new StringBuilder("M"), false, -1),
+            createTokenWithCount(Unit.SECOND, 1),
+            new Token(new StringBuilder("."), false, -1),
+            createTokenWithCount(Unit.MILLISECOND, 3),
+            new Token(new StringBuilder("S"), false, -1) }, 
DurationFormatUtils.lexx(DurationFormatUtils.ISO_EXTENDED_FORMAT_PATTERN));
         // test failures in equals
-        final DurationFormatUtils.Token token = 
createTokenWithCount(DurationFormatUtils.y, 4);
+        final DurationFormatUtils.Token token = 
createTokenWithCount(Unit.YEAR, 4);
         assertEquals(token, token);
         assertEquals(token.hashCode(), token.hashCode());
         assertNotEquals(token, new Object(), "Token equal to non-Token 
class.");
-        final Token token2 = createTokenWithCount("", 1);
+        final Token token2 = new Token(new StringBuilder("y"), false, -1);
         assertNotEquals(token, token2, "Token equal to Token with wrong value 
class.");
-        assertNotEquals(token.hashCode(), token2.hashCode());
-        assertNotEquals(token, createTokenWithCount(DurationFormatUtils.y, 1), 
"Token equal to Token with different count.");
-        final DurationFormatUtils.Token numToken = createTokenWithCount("1", 
4);
-        assertEquals(numToken, numToken, "Token with Number value not equal to 
itself.");
+        assertNotEquals(token, createTokenWithCount(Unit.YEAR, 1), "Token 
equal to Token with different count.");
     }
 
     @Test
@@ -889,6 +909,19 @@ void testOptionalToken() {
                 DurationFormatUtils.formatPeriod(915361L, 9153610L, 
"[d'd''d2'H'h''h2'm'm']s's'"));
     }
 
+    @Test
+    void testQuotedUnits() {
+        final Token[] literals = DurationFormatUtils.lexx("'yMdHmsS'");
+        assertEquals(1, literals.length);
+        for (final Unit unit : Unit.values()) {
+            assertFalse(Token.containsTokenWithValue(literals, unit));
+            
assertTrue(Token.containsTokenWithValue(DurationFormatUtils.lexx(unit.toString()),
 unit));
+        }
+        assertEquals("yMdHmsS", DurationFormatUtils.formatDuration(1007, 
"'yMdHmsS'"));
+        assertEquals("y1 M2 d3 H4 m5 s6 S007", DurationFormatUtils.format(
+                DurationFormatUtils.lexx("'y'y 'M'M 'd'd 'H'H 'm'm 's's 
'S'S"), 1, 2, 3, 4, 5, 6, 7, true));
+    }
+
     @Test
     void testRepeatedTokenSeparatedByLiteral() {
         final long fiveHours = Duration.ofHours(5).toMillis();
@@ -904,6 +937,33 @@ void testRepeatedTokenSeparatedByLiteral() {
         assertEquals("05", DurationFormatUtils.formatDuration(fiveHours, "HH", 
true));
     }
 
+    @Test
+    void testTokenEquality() {
+        final Token year = new Token(Unit.YEAR, false, -1);
+        final Token literal = new Token(new StringBuilder("y"), false, -1);
+        final Token equalLiteral = new Token(new StringBuilder("y"), false, 
-1);
+        assertEquals(year, new Token(Unit.YEAR, false, -1));
+        assertEquals(year.hashCode(), new Token(Unit.YEAR, false, 
-1).hashCode());
+        assertNotEquals(year, new Token(Unit.MONTH, false, -1));
+        assertNotEquals(year, literal);
+        assertNotEquals(literal, year);
+        assertEquals(literal, equalLiteral);
+        assertEquals(literal.hashCode(), equalLiteral.hashCode());
+        assertNotEquals(literal, new Token(new StringBuilder("M"), false, -1));
+        equalLiteral.increment();
+        assertNotEquals(literal, equalLiteral);
+    }
+
+    @Test
+    void testTokenToString() {
+        final Token[] tokens = DurationFormatUtils.lexx("yy MM dd HH mm ss SSS 
'literal'");
+        final String[] expected = { "yy", " ", "MM", " ", "dd", " ", "HH", " 
", "mm", " ", "ss", " ", "SSS", " ", "literal" };
+        assertEquals(expected.length, tokens.length);
+        for (int i = 0; i < expected.length; i++) {
+            assertEquals(expected[i], tokens[i].toString());
+        }
+    }
+
     @Test
     void testUnmatchedOptionalTokens() {
         assertIllegalArgumentException(() -> 
DurationFormatUtils.formatDuration(1, "[s"));

Reply via email to