Browse Source

Polishing

pull/35405/head
Juergen Hoeller 7 months ago
parent
commit
15364cf59f
  1. 17
      spring-context/src/main/java/org/springframework/format/annotation/DurationFormat.java
  2. 145
      spring-context/src/main/java/org/springframework/format/datetime/standard/DurationFormatterUtils.java

17
spring-context/src/main/java/org/springframework/format/annotation/DurationFormat.java

@ -53,11 +53,19 @@ public @interface DurationFormat { @@ -53,11 +53,19 @@ public @interface DurationFormat {
*/
Unit defaultUnit() default Unit.MILLIS;
/**
* {@link Duration} format styles.
*/
enum Style {
/**
* ISO-8601 formatting.
* <p>This is what the JDK uses in {@link Duration#parse(CharSequence)}
* and {@link Duration#toString()}.
*/
ISO8601,
/**
* Simple formatting based on a short suffix, for example '1s'.
* <p>Supported unit suffixes include: {@code ns, us, ms, s, m, h, d}.
@ -72,13 +80,6 @@ public @interface DurationFormat { @@ -72,13 +80,6 @@ public @interface DurationFormat {
*/
SIMPLE,
/**
* ISO-8601 formatting.
* <p>This is what the JDK uses in {@link Duration#parse(CharSequence)}
* and {@link Duration#toString()}.
*/
ISO8601,
/**
* Like {@link #SIMPLE}, but allows multiple segments ordered from
* largest-to-smallest units of time, like {@code 1h12m27s}.
@ -90,6 +91,7 @@ public @interface DurationFormat { @@ -90,6 +91,7 @@ public @interface DurationFormat {
COMPOSITE
}
/**
* {@link Duration} format unit, which mirrors a subset of {@link ChronoUnit} and
* allows conversion to and from a supported {@code ChronoUnit} as well as
@ -227,7 +229,6 @@ public @interface DurationFormat { @@ -227,7 +229,6 @@ public @interface DurationFormat {
}
throw new IllegalArgumentException("'" + suffix + "' is not a valid simple duration Unit");
}
}
}

145
spring-context/src/main/java/org/springframework/format/datetime/standard/DurationFormatterUtils.java

@ -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);
}
}
}

Loading…
Cancel
Save