|
|
|
|
@ -28,6 +28,7 @@ import org.springframework.util.StringUtils;
@@ -28,6 +28,7 @@ import org.springframework.util.StringUtils;
|
|
|
|
|
/** |
|
|
|
|
* Support {@code Duration} parsing and printing in several styles, as listed in |
|
|
|
|
* {@link DurationFormat.Style}. |
|
|
|
|
* |
|
|
|
|
* <p>Some styles may not enforce any unit to be present, defaulting to {@code DurationFormat.Unit#MILLIS} |
|
|
|
|
* in that case. Methods in this class offer overloads that take a {@link DurationFormat.Unit} to |
|
|
|
|
* be used as a fall-back instead of the ultimate MILLIS default. |
|
|
|
|
@ -39,36 +40,13 @@ import org.springframework.util.StringUtils;
@@ -39,36 +40,13 @@ import org.springframework.util.StringUtils;
|
|
|
|
|
*/ |
|
|
|
|
public abstract class DurationFormatterUtils { |
|
|
|
|
|
|
|
|
|
private DurationFormatterUtils() { |
|
|
|
|
// singleton
|
|
|
|
|
} |
|
|
|
|
private static final Pattern ISO_8601_PATTERN = Pattern.compile("^[+-]?[pP].*$"); |
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
* Parse the given value to a duration. |
|
|
|
|
* @param value the value to parse |
|
|
|
|
* @param style the style in which to parse |
|
|
|
|
* @return a duration |
|
|
|
|
*/ |
|
|
|
|
public static Duration parse(String value, DurationFormat.Style style) { |
|
|
|
|
return parse(value, style, null); |
|
|
|
|
} |
|
|
|
|
private static final Pattern SIMPLE_PATTERN = Pattern.compile("^([+-]?\\d+)([a-zA-Z]{0,2})$"); |
|
|
|
|
|
|
|
|
|
private static final Pattern COMPOSITE_PATTERN = Pattern.compile("^([+-]?)\\(?\\s?(\\d+d)?\\s?(\\d+h)?\\s?(\\d+m)?" + |
|
|
|
|
"\\s?(\\d+s)?\\s?(\\d+ms)?\\s?(\\d+us)?\\s?(\\d+ns)?\\)?$"); |
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
* Parse the given value to a duration. |
|
|
|
|
* @param value the value to parse |
|
|
|
|
* @param style the style in which to parse |
|
|
|
|
* @param unit the duration unit to use if the value doesn't specify one ({@code null} |
|
|
|
|
* will default to ms) |
|
|
|
|
* @return a duration |
|
|
|
|
*/ |
|
|
|
|
public static Duration parse(String value, DurationFormat.Style style, @Nullable DurationFormat.Unit unit) { |
|
|
|
|
Assert.hasText(value, () -> "Value must not be empty"); |
|
|
|
|
return switch (style) { |
|
|
|
|
case ISO8601 -> parseIso8601(value); |
|
|
|
|
case SIMPLE -> parseSimple(value, unit); |
|
|
|
|
case COMPOSITE -> parseComposite(value); |
|
|
|
|
}; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
* Print the specified duration in the specified style. |
|
|
|
|
@ -97,27 +75,30 @@ public abstract class DurationFormatterUtils {
@@ -97,27 +75,30 @@ public abstract class DurationFormatterUtils {
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
* Detect the style then parse the value to return a duration. |
|
|
|
|
* Parse the given value to a duration. |
|
|
|
|
* @param value the value to parse |
|
|
|
|
* @return the parsed duration |
|
|
|
|
* @throws IllegalArgumentException if the value is not a known style or cannot be |
|
|
|
|
* parsed |
|
|
|
|
* @param style the style in which to parse |
|
|
|
|
* @return a duration |
|
|
|
|
*/ |
|
|
|
|
public static Duration detectAndParse(String value) { |
|
|
|
|
return detectAndParse(value, null); |
|
|
|
|
public static Duration parse(String value, DurationFormat.Style style) { |
|
|
|
|
return parse(value, style, null); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
* Detect the style then parse the value to return a duration. |
|
|
|
|
* Parse the given value to a duration. |
|
|
|
|
* @param value the value to parse |
|
|
|
|
* @param style the style in which to parse |
|
|
|
|
* @param unit the duration unit to use if the value doesn't specify one ({@code null} |
|
|
|
|
* will default to ms) |
|
|
|
|
* @return the parsed duration |
|
|
|
|
* @throws IllegalArgumentException if the value is not a known style or cannot be |
|
|
|
|
* parsed |
|
|
|
|
* @return a duration |
|
|
|
|
*/ |
|
|
|
|
public static Duration detectAndParse(String value, @Nullable DurationFormat.Unit unit) { |
|
|
|
|
return parse(value, detect(value), unit); |
|
|
|
|
public static Duration parse(String value, DurationFormat.Style style, @Nullable DurationFormat.Unit unit) { |
|
|
|
|
Assert.hasText(value, () -> "Value must not be empty"); |
|
|
|
|
return switch (style) { |
|
|
|
|
case ISO8601 -> parseIso8601(value); |
|
|
|
|
case SIMPLE -> parseSimple(value, unit); |
|
|
|
|
case COMPOSITE -> parseComposite(value); |
|
|
|
|
}; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
@ -141,10 +122,30 @@ public abstract class DurationFormatterUtils {
@@ -141,10 +122,30 @@ public abstract class DurationFormatterUtils {
|
|
|
|
|
throw new IllegalArgumentException("'" + value + "' is not a valid duration, cannot detect any known style"); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
private static final Pattern ISO_8601_PATTERN = Pattern.compile("^[+-]?[pP].*$"); |
|
|
|
|
private static final Pattern SIMPLE_PATTERN = Pattern.compile("^([+-]?\\d+)([a-zA-Z]{0,2})$"); |
|
|
|
|
private static final Pattern COMPOSITE_PATTERN = Pattern.compile("^([+-]?)\\(?\\s?(\\d+d)?\\s?(\\d+h)?\\s?(\\d+m)?" + |
|
|
|
|
"\\s?(\\d+s)?\\s?(\\d+ms)?\\s?(\\d+us)?\\s?(\\d+ns)?\\)?$"); |
|
|
|
|
/** |
|
|
|
|
* Detect the style then parse the value to return a duration. |
|
|
|
|
* @param value the value to parse |
|
|
|
|
* @return the parsed duration |
|
|
|
|
* @throws IllegalArgumentException if the value is not a known style or cannot be |
|
|
|
|
* parsed |
|
|
|
|
*/ |
|
|
|
|
public static Duration detectAndParse(String value) { |
|
|
|
|
return detectAndParse(value, null); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
* Detect the style then parse the value to return a duration. |
|
|
|
|
* @param value the value to parse |
|
|
|
|
* @param unit the duration unit to use if the value doesn't specify one ({@code null} |
|
|
|
|
* will default to ms) |
|
|
|
|
* @return the parsed duration |
|
|
|
|
* @throws IllegalArgumentException if the value is not a known style or cannot be |
|
|
|
|
* parsed |
|
|
|
|
*/ |
|
|
|
|
public static Duration detectAndParse(String value, @Nullable DurationFormat.Unit unit) { |
|
|
|
|
return parse(value, detect(value), unit); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private static Duration parseIso8601(String value) { |
|
|
|
|
try { |
|
|
|
|
@ -155,6 +156,11 @@ public abstract class DurationFormatterUtils {
@@ -155,6 +156,11 @@ public abstract class DurationFormatterUtils {
|
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
private static String printSimple(Duration duration, @Nullable DurationFormat.Unit unit) { |
|
|
|
|
unit = (unit == null ? DurationFormat.Unit.MILLIS : unit); |
|
|
|
|
return unit.print(duration); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
private static Duration parseSimple(String text, @Nullable DurationFormat.Unit fallbackUnit) { |
|
|
|
|
try { |
|
|
|
|
Matcher matcher = SIMPLE_PATTERN.matcher(text); |
|
|
|
|
@ -171,34 +177,6 @@ public abstract class DurationFormatterUtils {
@@ -171,34 +177,6 @@ public abstract class DurationFormatterUtils {
|
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
private static String printSimple(Duration duration, @Nullable DurationFormat.Unit unit) { |
|
|
|
|
unit = (unit == null ? DurationFormat.Unit.MILLIS : unit); |
|
|
|
|
return unit.print(duration); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
private static Duration parseComposite(String text) { |
|
|
|
|
try { |
|
|
|
|
Matcher matcher = COMPOSITE_PATTERN.matcher(text); |
|
|
|
|
Assert.state(matcher.matches() && matcher.groupCount() > 1, "Does not match composite duration pattern"); |
|
|
|
|
String sign = matcher.group(1); |
|
|
|
|
boolean negative = sign != null && sign.equals("-"); |
|
|
|
|
|
|
|
|
|
Duration result = Duration.ZERO; |
|
|
|
|
DurationFormat.Unit[] units = DurationFormat.Unit.values(); |
|
|
|
|
for (int i = 2; i < matcher.groupCount() + 1; i++) { |
|
|
|
|
String segment = matcher.group(i); |
|
|
|
|
if (StringUtils.hasText(segment)) { |
|
|
|
|
DurationFormat.Unit unit = units[units.length - i + 1]; |
|
|
|
|
result = result.plus(unit.parse(segment.replace(unit.asSuffix(), ""))); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
return negative ? result.negated() : result; |
|
|
|
|
} |
|
|
|
|
catch (Exception ex) { |
|
|
|
|
throw new IllegalArgumentException("'" + text + "' is not a valid composite duration", ex); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
private static String printComposite(Duration duration) { |
|
|
|
|
if (duration.isZero()) { |
|
|
|
|
return DurationFormat.Unit.SECONDS.print(duration); |
|
|
|
|
@ -243,4 +221,27 @@ public abstract class DurationFormatterUtils {
@@ -243,4 +221,27 @@ public abstract class DurationFormatterUtils {
|
|
|
|
|
return result.toString(); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
private static Duration parseComposite(String text) { |
|
|
|
|
try { |
|
|
|
|
Matcher matcher = COMPOSITE_PATTERN.matcher(text); |
|
|
|
|
Assert.state(matcher.matches() && matcher.groupCount() > 1, "Does not match composite duration pattern"); |
|
|
|
|
String sign = matcher.group(1); |
|
|
|
|
boolean negative = sign != null && sign.equals("-"); |
|
|
|
|
|
|
|
|
|
Duration result = Duration.ZERO; |
|
|
|
|
DurationFormat.Unit[] units = DurationFormat.Unit.values(); |
|
|
|
|
for (int i = 2; i < matcher.groupCount() + 1; i++) { |
|
|
|
|
String segment = matcher.group(i); |
|
|
|
|
if (StringUtils.hasText(segment)) { |
|
|
|
|
DurationFormat.Unit unit = units[units.length - i + 1]; |
|
|
|
|
result = result.plus(unit.parse(segment.replace(unit.asSuffix(), ""))); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
return negative ? result.negated() : result; |
|
|
|
|
} |
|
|
|
|
catch (Exception ex) { |
|
|
|
|
throw new IllegalArgumentException("'" + text + "' is not a valid composite duration", ex); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
} |
|
|
|
|
|