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"));