You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@commons.apache.org by ah...@apache.org on 2023/08/09 15:33:37 UTC
[commons-lang] branch master updated: Syntax for optional tokens in DurationFormatUtils (#1062)
This is an automated email from the ASF dual-hosted git repository.
aherbert 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 85959a457 Syntax for optional tokens in DurationFormatUtils (#1062)
85959a457 is described below
commit 85959a457076b740380c4d6f3d83098edd17a9e7
Author: Dan Watson <29...@users.noreply.github.com>
AuthorDate: Wed Aug 9 11:33:31 2023 -0400
Syntax for optional tokens in DurationFormatUtils (#1062)
Allow optional tokens using the [ and ] characters.
Disallows nesting of optional tokens.
The leading literal within an optional block depends on first non-literal within the same block.
---
.../commons/lang3/time/DurationFormatUtils.java | 153 ++++++++++++---
.../lang3/time/DurationFormatUtilsTest.java | 214 +++++++++++++++++----
2 files changed, 302 insertions(+), 65 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 4daf47f13..00173ce98 100644
--- a/src/main/java/org/apache/commons/lang3/time/DurationFormatUtils.java
+++ b/src/main/java/org/apache/commons/lang3/time/DurationFormatUtils.java
@@ -49,6 +49,32 @@ import org.apache.commons.lang3.Validate;
* Token values are printed using decimal digits.
* A token character can be repeated to ensure that the field occupies a certain minimum
* size. Values will be left-padded with 0 unless padding is disabled in the method invocation.
+ * <br>
+ * Tokens can be marked as optional by surrounding them with brackets [ ]. These tokens will
+ * only be printed if the token value is non-zero. Literals within optional blocks will only be
+ * printed if the preceding non-literal token is non-zero. Leading optional literals will only
+ * be printed if the following non-literal is non-zero.
+ * Multiple optional blocks can be used to group literals with the desired token.
+ * <p>
+ * Notes on Optional Tokens:<br>
+ * <b>Multiple optional tokens without literals can result in impossible to understand output.</b><br>
+ * <b>Patterns where all tokens are optional can produce empty strings.</b><br>
+ * (See examples below)
+ * </p>
+ * <br>
+ * <table border="1">
+ * <caption>Example Output</caption>
+ * <tr><th>pattern</th><th>Duration.ofDays(1)</th><th>Duration.ofHours(1)</th><th>Duration.ofMinutes(1)</th><th>Duration.ZERO</th></tr>
+ * <tr><td>d'd'H'h'm'm's's'</td><td>1d0h0m0s</td><td>0d1h0m0s</td><td>0d0h1m0s</td><td>0d0h0m0s</td></tr>
+ * <tr><td>d'd'[H'h'm'm']s's'</td><td>1d0s</td><td>0d1h0s</td><td>0d1m0s</td><td>0d0s</td></tr>
+ * <tr><td>[d'd'H'h'm'm']s's'</td><td>1d0s</td><td>1h0s</td><td>1m0s</td><td>0s</td></tr>
+ * <tr><td>[d'd'H'h'm'm's's']</td><td>1d</td><td>1h</td><td>1m</td><td></td></tr>
+ * <tr><td>['{'d'}']HH':'mm</td><td>{1}00:00</td><td>01:00</td><td>00:01</td><td>00:00</td></tr>
+ * <tr><td>['{'dd'}']['<'HH'>']['('mm')']</td><td>{01}</td><td><01></td><td>(00)</td><td></td></tr>
+ * <tr><td>[dHms]</td><td>1</td><td>1</td><td>1</td><td></td></tr>
+ * </table>
+ * <b>Note: Optional blocks cannot be nested.</b>
+ *
* @since 2.1
*/
public class DurationFormatUtils {
@@ -419,43 +445,93 @@ public class DurationFormatUtils {
* @param padWithZeros whether to pad
* @return the formatted string
*/
- static String format(final Token[] tokens, final long years, final long months, final long days, final long hours, final long minutes, final long seconds,
+ static String format(final Token[] tokens, final long years, final long months, final long days, final long hours, final long minutes,
+ final long seconds,
final long milliseconds, final boolean padWithZeros) {
final StringBuilder buffer = new StringBuilder();
boolean lastOutputSeconds = false;
+ boolean lastOutputZero = false;
+ int optionalStart = -1;
+ boolean firstOptionalNonLiteral = false;
+ int optionalIndex = -1;
+ boolean inOptional = false;
for (final Token token : tokens) {
final Object value = token.getValue();
+ final boolean isLiteral = value instanceof StringBuilder;
final int count = token.getCount();
- if (value instanceof StringBuilder) {
- buffer.append(value.toString());
+ if (optionalIndex != token.optionalIndex) {
+ optionalIndex = token.optionalIndex;
+ if (optionalIndex > -1) {
+ //entering new optional block
+ optionalStart = buffer.length();
+ lastOutputZero = false;
+ inOptional = true;
+ firstOptionalNonLiteral = false;
+ } else {
+ //leaving optional block
+ inOptional = false;
+ }
+ }
+ if (isLiteral) {
+ if (!inOptional || !lastOutputZero) {
+ buffer.append(value.toString());
+ }
} else if (value.equals(y)) {
- buffer.append(paddedValue(years, padWithZeros, count));
lastOutputSeconds = false;
+ lastOutputZero = years == 0;
+ if (!inOptional || !lastOutputZero) {
+ buffer.append(paddedValue(years, padWithZeros, count));
+ }
} else if (value.equals(M)) {
- buffer.append(paddedValue(months, padWithZeros, count));
lastOutputSeconds = false;
+ lastOutputZero = months == 0;
+ if (!inOptional || !lastOutputZero) {
+ buffer.append(paddedValue(months, padWithZeros, count));
+ }
} else if (value.equals(d)) {
- buffer.append(paddedValue(days, padWithZeros, count));
lastOutputSeconds = false;
+ lastOutputZero = days == 0;
+ if (!inOptional || !lastOutputZero) {
+ buffer.append(paddedValue(days, padWithZeros, count));
+ }
} else if (value.equals(H)) {
- buffer.append(paddedValue(hours, padWithZeros, count));
lastOutputSeconds = false;
+ lastOutputZero = hours == 0;
+ if (!inOptional || !lastOutputZero) {
+ buffer.append(paddedValue(hours, padWithZeros, count));
+ }
} else if (value.equals(m)) {
- buffer.append(paddedValue(minutes, padWithZeros, count));
lastOutputSeconds = false;
+ lastOutputZero = minutes == 0;
+ if (!inOptional || !lastOutputZero) {
+ buffer.append(paddedValue(minutes, padWithZeros, count));
+ }
} else if (value.equals(s)) {
- buffer.append(paddedValue(seconds, padWithZeros, count));
lastOutputSeconds = true;
+ lastOutputZero = seconds == 0;
+ if (!inOptional || !lastOutputZero) {
+ buffer.append(paddedValue(seconds, padWithZeros, count));
+ }
} else if (value.equals(S)) {
- if (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));
- } else {
- buffer.append(paddedValue(milliseconds, padWithZeros, count));
+ lastOutputZero = milliseconds == 0;
+ if (!inOptional || !lastOutputZero) {
+ if (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));
+ } else {
+ buffer.append(paddedValue(milliseconds, padWithZeros, count));
+ }
}
lastOutputSeconds = false;
}
+ //as soon as we hit first nonliteral in optional, check for literal prefix
+ if (inOptional && !isLiteral && !firstOptionalNonLiteral){
+ firstOptionalNonLiteral = true;
+ if (lastOutputZero) {
+ buffer.delete(optionalStart, buffer.length());
+ }
+ }
}
return buffer.toString();
}
@@ -496,6 +572,8 @@ public class DurationFormatUtils {
// used internally, so cannot be accessed by other threads
StringBuilder buffer = null;
Token previous = null;
+ boolean inOptional = false;
+ int optionalIndex = -1;
for (int i = 0; i < format.length(); i++) {
final char ch = format.charAt(i);
if (inLiteral && ch != '\'') {
@@ -505,13 +583,26 @@ public class DurationFormatUtils {
String value = null;
switch (ch) {
// TODO: Need to handle escaping of '
+ case '[':
+ if (inOptional) {
+ throw new IllegalArgumentException("Nested optional block at index: "+i);
+ }
+ optionalIndex++;
+ inOptional = true;
+ break;
+ case ']':
+ if (!inOptional) {
+ throw new IllegalArgumentException("Attempting to close unopened optional block at index: "+i);
+ }
+ inOptional = false;
+ break;
case '\'':
if (inLiteral) {
buffer = null;
inLiteral = false;
} else {
buffer = new StringBuilder();
- list.add(new Token(buffer));
+ list.add(new Token(buffer, inOptional, optionalIndex));
inLiteral = true;
}
break;
@@ -539,7 +630,7 @@ public class DurationFormatUtils {
default:
if (buffer == null) {
buffer = new StringBuilder();
- list.add(new Token(buffer));
+ list.add(new Token(buffer, inOptional, optionalIndex));
}
buffer.append(ch);
}
@@ -548,7 +639,7 @@ public class DurationFormatUtils {
if (previous != null && previous.getValue().equals(value)) {
previous.increment();
} else {
- final Token token = new Token(value);
+ final Token token = new Token(value, inOptional, optionalIndex);
list.add(token);
previous = token;
}
@@ -558,6 +649,9 @@ public class DurationFormatUtils {
if (inLiteral) { // i.e. we have not found the end of the literal
throw new IllegalArgumentException("Unmatched quote in format: " + format);
}
+ if (inOptional) { // i.e. we have not found the end of the literal
+ throw new IllegalArgumentException("Unmatched optional in format: " + format);
+ }
return list.toArray(Token.EMPTY_ARRAY);
}
@@ -582,26 +676,21 @@ public class DurationFormatUtils {
private final Object value;
private int count;
+ private int optionalIndex = -1;
/**
* Wraps a token around a value. A value would be something like a 'Y'.
*
- * @param value to wrap, non-null.
+ * @param value value to wrap, non-null.
+ * @param optional whether the token is optional
+ * @param optionalIndex the index of the optional token within the pattern
*/
- Token(final Object value) {
- this(value, 1);
- }
-
- /**
- * Wraps a token around a repeated number of a value, for example it would
- * store 'yyyy' as a value for y and a count of 4.
- *
- * @param value to wrap, non-null.
- * @param count to wrap.
- */
- Token(final Object value, final int count) {
+ Token(final Object value, final boolean optional, final int optionalIndex) {
this.value = Objects.requireNonNull(value, "value");
- this.count = count;
+ this.count = 1;
+ if (optional) {
+ this.optionalIndex = 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 545b220fb..94e235927 100644
--- a/src/test/java/org/apache/commons/lang3/time/DurationFormatUtilsTest.java
+++ b/src/test/java/org/apache/commons/lang3/time/DurationFormatUtilsTest.java
@@ -27,6 +27,7 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
import java.lang.reflect.Constructor;
import java.lang.reflect.Modifier;
+import java.time.Duration;
import java.util.Calendar;
import java.util.TimeZone;
@@ -571,49 +572,49 @@ public class DurationFormatUtilsTest extends AbstractLangTest {
public void testLexx() {
// tests each constant
assertArrayEquals(new DurationFormatUtils.Token[]{
- new DurationFormatUtils.Token(DurationFormatUtils.y, 1),
- new DurationFormatUtils.Token(DurationFormatUtils.M, 1),
- new DurationFormatUtils.Token(DurationFormatUtils.d, 1),
- new DurationFormatUtils.Token(DurationFormatUtils.H, 1),
- new DurationFormatUtils.Token(DurationFormatUtils.m, 1),
- new DurationFormatUtils.Token(DurationFormatUtils.s, 1),
- new DurationFormatUtils.Token(DurationFormatUtils.S, 1)}, DurationFormatUtils.lexx("yMdHmsS"));
+ 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"));
// tests the ISO 8601-like
assertArrayEquals(new DurationFormatUtils.Token[]{
- new DurationFormatUtils.Token(DurationFormatUtils.H, 2),
- new DurationFormatUtils.Token(new StringBuilder(":"), 1),
- new DurationFormatUtils.Token(DurationFormatUtils.m, 2),
- new DurationFormatUtils.Token(new StringBuilder(":"), 1),
- new DurationFormatUtils.Token(DurationFormatUtils.s, 2),
- new DurationFormatUtils.Token(new StringBuilder("."), 1),
- new DurationFormatUtils.Token(DurationFormatUtils.S, 3)}, DurationFormatUtils.lexx("HH:mm:ss.SSS"));
+ 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"));
// test the iso extended format
assertArrayEquals(new DurationFormatUtils.Token[]{
- new DurationFormatUtils.Token(new StringBuilder("P"), 1),
- new DurationFormatUtils.Token(DurationFormatUtils.y, 4),
- new DurationFormatUtils.Token(new StringBuilder("Y"), 1),
- new DurationFormatUtils.Token(DurationFormatUtils.M, 1),
- new DurationFormatUtils.Token(new StringBuilder("M"), 1),
- new DurationFormatUtils.Token(DurationFormatUtils.d, 1),
- new DurationFormatUtils.Token(new StringBuilder("DT"), 1),
- new DurationFormatUtils.Token(DurationFormatUtils.H, 1),
- new DurationFormatUtils.Token(new StringBuilder("H"), 1),
- new DurationFormatUtils.Token(DurationFormatUtils.m, 1),
- new DurationFormatUtils.Token(new StringBuilder("M"), 1),
- new DurationFormatUtils.Token(DurationFormatUtils.s, 1),
- new DurationFormatUtils.Token(new StringBuilder("."), 1),
- new DurationFormatUtils.Token(DurationFormatUtils.S, 3),
- new DurationFormatUtils.Token(new StringBuilder("S"), 1)}, DurationFormatUtils
+ 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));
// test failures in equals
- final DurationFormatUtils.Token token = new DurationFormatUtils.Token(DurationFormatUtils.y, 4);
+ final DurationFormatUtils.Token token = createTokenWithCount(DurationFormatUtils.y, 4);
assertNotEquals(token, new Object(), "Token equal to non-Token class. ");
- assertNotEquals(token, new DurationFormatUtils.Token(new Object()), "Token equal to Token with wrong value class. ");
- assertNotEquals(token, new DurationFormatUtils.Token(DurationFormatUtils.y, 1), "Token equal to Token with different count. ");
- final DurationFormatUtils.Token numToken = new DurationFormatUtils.Token(Integer.valueOf(1), 4);
+ assertNotEquals(token, createTokenWithCount(new Object(), 1), "Token equal to Token with wrong value class. ");
+ assertNotEquals(token, createTokenWithCount(DurationFormatUtils.y, 1), "Token equal to Token with different count. ");
+ final DurationFormatUtils.Token numToken = createTokenWithCount(Integer.valueOf(1), 4);
assertEquals(numToken, numToken, "Token with Number value not equal to itself. ");
}
// Testing the under a day range in DurationFormatUtils.formatPeriod
@@ -632,4 +633,151 @@ public class DurationFormatUtilsTest extends AbstractLangTest {
}
}
+
+ @Test
+ public void testUnmatchedOptionalTokens() {
+ assertThrows(IllegalArgumentException.class, () -> DurationFormatUtils.formatDuration(1, "[s"));
+ assertThrows(IllegalArgumentException.class, () -> DurationFormatUtils.formatDuration(1, "[[s"));
+ assertThrows(IllegalArgumentException.class, () -> DurationFormatUtils.formatDuration(1, "[s]]"));
+ }
+
+ @Test
+ public void testOptionalLiteralSpecialCharacters() {
+ assertEquals(
+ DurationFormatUtils.formatDuration(10000L, "s's'"),
+ DurationFormatUtils.formatDuration(10000L, "['['m']']s's'"));
+ }
+
+ @Test
+ public void testAlternatingLiteralOptionals() {
+ String format = "['d'dH'h'][m'm']['s's]['ms'S]";
+
+ assertEquals("d1",
+ DurationFormatUtils.formatDuration(Duration.ofDays(1).toMillis(), format));
+
+ assertEquals("1h",
+ DurationFormatUtils.formatDuration(Duration.ofHours(1).toMillis(), format));
+
+ assertEquals("1m",
+ DurationFormatUtils.formatDuration(Duration.ofMinutes(1).toMillis(), format));
+
+ assertEquals("s1",
+ DurationFormatUtils.formatDuration(Duration.ofSeconds(1).toMillis(), format));
+
+ assertEquals("ms001",
+ DurationFormatUtils.formatDuration(Duration.ofMillis(1).toMillis(), format));
+
+ assertEquals("d1s1",
+ DurationFormatUtils.formatDuration(Duration.ofDays(1).plusSeconds(1).toMillis(), format));
+
+ assertEquals("d11h",
+ DurationFormatUtils.formatDuration(Duration.ofDays(1).plusHours(1).toMillis(), format));
+
+ assertEquals("d11h1m",
+ DurationFormatUtils.formatDuration(Duration.ofDays(1).plusHours(1).plusMinutes(1).toMillis(), format));
+
+ assertEquals("d11h1ms1",
+ DurationFormatUtils.formatDuration(Duration.ofDays(1).plusHours(1).plusMinutes(1).plusSeconds(1).toMillis(), format));
+
+ assertEquals("d11h1ms1ms001",
+ DurationFormatUtils.formatDuration(Duration.ofDays(1).plusHours(1).plusMinutes(1).plusSeconds(1).plusMillis(1).toMillis(), format));
+
+ }
+
+ @Test
+ public void testLiteralPrefixOptionalToken() {
+ assertEquals(
+ DurationFormatUtils.formatDuration(10000L, "s's'"),
+ DurationFormatUtils.formatDuration(10000L, "['['d']']['<'H'>']['{'m'}']s's'"));
+ assertEquals(
+ DurationFormatUtils.formatDuration(10000L, "s's'"),
+ DurationFormatUtils.formatDuration(10000L, "['{'m'}']s's'"));
+ }
+
+ @Test
+ public void testEmptyOptionals() {
+ assertEquals(
+ "",
+ DurationFormatUtils.formatDuration(0L, "[d'd'][H'h'][m'm'][s's']"));
+ assertEquals(
+ "",
+ DurationFormatUtils.formatDuration(0L, "['d''h''m''s's]"));
+ }
+
+ @Test
+ public void testMultipleOptionalBlocks() {
+
+ assertEquals(
+ DurationFormatUtils.formatDuration(Duration.ofHours(1).toMillis(), "'[['H']]'"),
+ DurationFormatUtils.formatDuration(Duration.ofHours(1).toMillis(), "['{'d'}']['[['H']]']"));
+
+ assertEquals(
+ DurationFormatUtils.formatDuration(Duration.ofDays(1).toMillis(), "['{'d'}']"),
+ DurationFormatUtils.formatDuration(Duration.ofDays(1).toMillis(), "['{'d'}']['['H']']"));
+ }
+
+ @Test
+ public void testOptionalToken() {
+
+ //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
+
+ 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
+
+ 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'"));
+ }
+
+ private DurationFormatUtils.Token createTokenWithCount(final Object value, final int count) {
+ DurationFormatUtils.Token token = new DurationFormatUtils.Token(value, false, -1);
+ for (int i = 1; i < count; i++) {
+ token.increment();
+ }
+ return token;
+ }
}