Skip to content

Commit 7b54503

Browse files
committed
Reintroduce Unit to only have valid units in the enum
1 parent 9214cfb commit 7b54503

File tree

5 files changed

+219
-149
lines changed

5 files changed

+219
-149
lines changed

Diff for: spring-context/src/main/java/org/springframework/format/annotation/DurationFormat.java

+102-3
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,9 @@
2323
import java.lang.annotation.Target;
2424
import java.time.Duration;
2525
import java.time.temporal.ChronoUnit;
26+
import java.util.function.Function;
27+
28+
import org.springframework.lang.Nullable;
2629

2730
/**
2831
* Declares that a field or method parameter should be formatted as a {@link java.time.Duration},
@@ -43,11 +46,11 @@
4346
Style style() default Style.ISO8601;
4447

4548
/**
46-
* Define which {@code ChronoUnit} to fall back to in case the {@code style()}
49+
* Define which {@link Unit} to fall back to in case the {@code style()}
4750
* needs a unit for either parsing or printing, and none is explicitly provided in
48-
* the input.
51+
* the input ({@code Unit.MILLIS} if unspecified).
4952
*/
50-
ChronoUnit defaultUnit() default ChronoUnit.MILLIS;
53+
Unit defaultUnit() default Unit.MILLIS;
5154

5255
/**
5356
* Duration format styles.
@@ -74,4 +77,100 @@ enum Style {
7477
ISO8601;
7578
}
7679

80+
/**
81+
* Duration format unit, which mirrors a subset of {@link ChronoUnit} and allows conversion to and from
82+
* supported {@code ChronoUnit} as well as converting durations to longs.
83+
* The enum includes its corresponding suffix in the {@link Style#SIMPLE simple} Duration format style.
84+
*/
85+
enum Unit {
86+
/**
87+
* Nanoseconds ({@code "ns"}).
88+
*/
89+
NANOS(ChronoUnit.NANOS, "ns", Duration::toNanos),
90+
91+
/**
92+
* Microseconds ({@code "us"}).
93+
*/
94+
MICROS(ChronoUnit.MICROS, "us", duration -> duration.toNanos() / 1000L),
95+
96+
/**
97+
* Milliseconds ({@code "ms"}).
98+
*/
99+
MILLIS(ChronoUnit.MILLIS, "ms", Duration::toMillis),
100+
101+
/**
102+
* Seconds ({@code "s"}).
103+
*/
104+
SECONDS(ChronoUnit.SECONDS, "s", Duration::getSeconds),
105+
106+
/**
107+
* Minutes ({@code "m"}).
108+
*/
109+
MINUTES(ChronoUnit.MINUTES, "m", Duration::toMinutes),
110+
111+
/**
112+
* Hours ({@code "h"}).
113+
*/
114+
HOURS(ChronoUnit.HOURS, "h", Duration::toHours),
115+
116+
/**
117+
* Days ({@code "d"}).
118+
*/
119+
DAYS(ChronoUnit.DAYS, "d", Duration::toDays);
120+
121+
private final ChronoUnit chronoUnit;
122+
123+
private final String suffix;
124+
125+
private final Function<Duration, Long> longValue;
126+
127+
Unit(ChronoUnit chronoUnit, String suffix, Function<Duration, Long> toUnit) {
128+
this.chronoUnit = chronoUnit;
129+
this.suffix = suffix;
130+
this.longValue = toUnit;
131+
}
132+
133+
public ChronoUnit asChronoUnit() {
134+
return this.chronoUnit;
135+
}
136+
137+
public String asSuffix() {
138+
return this.suffix;
139+
}
140+
141+
public Duration parse(String value) {
142+
return Duration.of(Long.parseLong(value), asChronoUnit());
143+
}
144+
145+
public String print(Duration value) {
146+
return longValue(value) + asSuffix();
147+
}
148+
149+
public long longValue(Duration value) {
150+
return this.longValue.apply(value);
151+
}
152+
153+
public static Unit fromChronoUnit(@Nullable ChronoUnit chronoUnit) {
154+
if (chronoUnit == null) {
155+
return Unit.MILLIS;
156+
}
157+
for (Unit candidate : values()) {
158+
if (candidate.chronoUnit == chronoUnit) {
159+
return candidate;
160+
}
161+
}
162+
throw new IllegalArgumentException("No matching Unit for ChronoUnit." + chronoUnit.name());
163+
}
164+
165+
public static Unit fromSuffix(String suffix) {
166+
for (Unit candidate : values()) {
167+
if (candidate.suffix.equalsIgnoreCase(suffix)) {
168+
return candidate;
169+
}
170+
}
171+
throw new IllegalArgumentException("'" + suffix + "' is not a valid simple duration Unit");
172+
}
173+
174+
}
175+
77176
}

Diff for: spring-context/src/main/java/org/springframework/format/datetime/standard/DurationFormatter.java

+5-6
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@
1818

1919
import java.text.ParseException;
2020
import java.time.Duration;
21-
import java.time.temporal.ChronoUnit;
2221
import java.util.Locale;
2322

2423
import org.springframework.format.Formatter;
@@ -39,7 +38,7 @@ class DurationFormatter implements Formatter<Duration> { //TODO why is this one
3938

4039
private final DurationFormat.Style style;
4140
@Nullable
42-
private final ChronoUnit defaultUnit;
41+
private final DurationFormat.Unit defaultUnit;
4342

4443
/**
4544
* Create a {@code DurationFormatter} following JSR-310's parsing rules for a Duration
@@ -52,25 +51,25 @@ class DurationFormatter implements Formatter<Duration> { //TODO why is this one
5251
/**
5352
* Create a {@code DurationFormatter} in a specific {@link DurationFormat.Style}.
5453
* <p>When a unit is needed but cannot be determined (e.g. printing a Duration in the
55-
* {@code SIMPLE} style), {@code ChronoUnit#MILLIS} is used.
54+
* {@code SIMPLE} style), {@code DurationFormat.Unit#MILLIS} is used.
5655
*/
5756
public DurationFormatter(DurationFormat.Style style) {
5857
this(style, null);
5958
}
6059

6160
/**
6261
* Create a {@code DurationFormatter} in a specific {@link DurationFormat.Style} with an
63-
* optional {@code ChronoUnit}.
62+
* optional {@code DurationFormat.Unit}.
6463
* <p>If a {@code defaultUnit} is specified, it may be used in parsing cases when no
6564
* unit is present in the string (provided the style allows for such a case). It will
6665
* also be used as the representation's resolution when printing in the
6766
* {@link DurationFormat.Style#SIMPLE} style. Otherwise, the style defines its default
6867
* unit.
6968
*
7069
* @param style the {@code DurationStyle} to use
71-
* @param defaultUnit the {@code ChronoUnit} to fall back to when parsing and printing
70+
* @param defaultUnit the {@code DurationFormat.Unit} to fall back to when parsing and printing
7271
*/
73-
public DurationFormatter(DurationFormat.Style style, @Nullable ChronoUnit defaultUnit) {
72+
public DurationFormatter(DurationFormat.Style style, @Nullable DurationFormat.Unit defaultUnit) {
7473
this.style = style;
7574
this.defaultUnit = defaultUnit;
7675
}

Diff for: spring-context/src/main/java/org/springframework/format/datetime/standard/DurationFormatterUtils.java

+12-52
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@
1717
package org.springframework.format.datetime.standard;
1818

1919
import java.time.Duration;
20-
import java.time.temporal.ChronoUnit;
2120
import java.util.regex.Matcher;
2221
import java.util.regex.Pattern;
2322

@@ -29,8 +28,8 @@
2928
/**
3029
* Support {@code Duration} parsing and printing in several styles, as listed in
3130
* {@link DurationFormat.Style}.
32-
* <p>Some styles may not enforce any unit to be present, defaulting to {@code ChronoUnit#MILLIS}
33-
* in that case. Methods in this class offer overloads that take a {@code ChronoUnit} to
31+
* <p>Some styles may not enforce any unit to be present, defaulting to {@code DurationFormat.Unit#MILLIS}
32+
* in that case. Methods in this class offer overloads that take a {@link DurationFormat.Unit} to
3433
* be used as a fall-back instead of the ultimate MILLIS default.
3534
*
3635
* @author Phillip Webb
@@ -61,7 +60,7 @@ public static Duration parse(String value, DurationFormat.Style style) {
6160
* will default to ms)
6261
* @return a duration
6362
*/
64-
public static Duration parse(String value, DurationFormat.Style style, @Nullable ChronoUnit unit) {
63+
public static Duration parse(String value, DurationFormat.Style style, @Nullable DurationFormat.Unit unit) {
6564
return switch (style) {
6665
case ISO8601 -> parseIso8601(value);
6766
case SIMPLE -> parseSimple(value, unit);
@@ -86,7 +85,7 @@ public static String print(Duration value, DurationFormat.Style style) {
8685
* to ms)
8786
* @return the printed result
8887
*/
89-
public static String print(Duration value, DurationFormat.Style style, @Nullable ChronoUnit unit) {
88+
public static String print(Duration value, DurationFormat.Style style, @Nullable DurationFormat.Unit unit) {
9089
return switch (style) {
9190
case ISO8601 -> value.toString();
9291
case SIMPLE -> printSimple(value, unit);
@@ -113,7 +112,7 @@ public static Duration detectAndParse(String value) {
113112
* @throws IllegalArgumentException if the value is not a known style or cannot be
114113
* parsed
115114
*/
116-
public static Duration detectAndParse(String value, @Nullable ChronoUnit unit) {
115+
public static Duration detectAndParse(String value, @Nullable DurationFormat.Unit unit) {
117116
return parse(value, detect(value), unit);
118117
}
119118

@@ -134,19 +133,6 @@ public static DurationFormat.Style detect(String value) {
134133
throw new IllegalArgumentException("'" + value + "' is not a valid duration, cannot detect any known style");
135134
}
136135

137-
public static long longValueFromUnit(Duration duration, ChronoUnit unit) {
138-
return switch (unit) {
139-
case NANOS -> duration.toNanos();
140-
case MICROS -> duration.toNanos() / 1000L;
141-
case MILLIS -> duration.toMillis();
142-
case SECONDS -> duration.toSeconds();
143-
case MINUTES -> duration.toMinutes();
144-
case HOURS -> duration.toHours();
145-
case DAYS -> duration.toDays();
146-
default -> throw new IllegalArgumentException("'" + unit.name() + "' is not a supported ChronoUnit for simple duration representation");
147-
};
148-
}
149-
150136
private static final Pattern ISO_8601_PATTERN = Pattern.compile("^[+-]?[pP].*$");
151137
private static final Pattern SIMPLE_PATTERN = Pattern.compile("^([+-]?\\d+)([a-zA-Z]{0,2})$");
152138

@@ -159,51 +145,25 @@ private static Duration parseIso8601(String value) {
159145
}
160146
}
161147

162-
private static Duration parseSimple(String text, @Nullable ChronoUnit fallbackUnit) {
148+
private static Duration parseSimple(String text, @Nullable DurationFormat.Unit fallbackUnit) {
163149
try {
164150
Matcher matcher = SIMPLE_PATTERN.matcher(text);
165151
Assert.state(matcher.matches(), "Does not match simple duration pattern");
166152
String suffix = matcher.group(2);
167-
ChronoUnit parsingUnit = (fallbackUnit == null ? ChronoUnit.MILLIS : fallbackUnit);
153+
DurationFormat.Unit parsingUnit = (fallbackUnit == null ? DurationFormat.Unit.MILLIS : fallbackUnit);
168154
if (StringUtils.hasLength(suffix)) {
169-
parsingUnit = unitFromSuffix(suffix);
155+
parsingUnit = DurationFormat.Unit.fromSuffix(suffix);
170156
}
171-
return Duration.of(Long.parseLong(matcher.group(1)), parsingUnit);
157+
return parsingUnit.parse(matcher.group(1));
172158
}
173159
catch (Exception ex) {
174160
throw new IllegalArgumentException("'" + text + "' is not a valid simple duration", ex);
175161
}
176162
}
177163

178-
private static String printSimple(Duration duration, @Nullable ChronoUnit unit) {
179-
unit = (unit == null ? ChronoUnit.MILLIS : unit);
180-
return longValueFromUnit(duration, unit) + suffixFromUnit(unit);
181-
}
182-
183-
/* package-private */ static ChronoUnit unitFromSuffix(String suffix) {
184-
return switch (suffix.toLowerCase()) {
185-
case "ns" -> ChronoUnit.NANOS;
186-
case "us" -> ChronoUnit.MICROS;
187-
case "ms" -> ChronoUnit.MILLIS;
188-
case "s" -> ChronoUnit.SECONDS;
189-
case "m" -> ChronoUnit.MINUTES;
190-
case "h" -> ChronoUnit.HOURS;
191-
case "d" -> ChronoUnit.DAYS;
192-
default -> throw new IllegalArgumentException("'" + suffix + "' is not a valid simple duration unit");
193-
};
194-
}
195-
196-
/* package-private */ static String suffixFromUnit(ChronoUnit unit) {
197-
return switch (unit) {
198-
case NANOS -> "ns";
199-
case MICROS -> "us";
200-
case MILLIS -> "ms";
201-
case SECONDS -> "s";
202-
case MINUTES -> "m";
203-
case HOURS -> "h";
204-
case DAYS -> "d";
205-
default -> throw new IllegalArgumentException("'" + unit.name() + "' is not a supported ChronoUnit for simple duration representation");
206-
};
164+
private static String printSimple(Duration duration, @Nullable DurationFormat.Unit unit) {
165+
unit = (unit == null ? DurationFormat.Unit.MILLIS : unit);
166+
return unit.print(duration);
207167
}
208168

209169
}

Diff for: spring-context/src/test/java/org/springframework/format/datetime/standard/DateTimeFormattingTests.java

+1-2
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,6 @@
3131
import java.time.format.DateTimeFormatter;
3232
import java.time.format.DateTimeParseException;
3333
import java.time.format.FormatStyle;
34-
import java.time.temporal.ChronoUnit;
3534
import java.util.ArrayList;
3635
import java.util.Date;
3736
import java.util.GregorianCalendar;
@@ -688,7 +687,7 @@ public static class DateTimeBean {
688687

689688
private Duration duration;
690689

691-
@DurationFormat(style = Style.SIMPLE, defaultUnit = ChronoUnit.MICROS)
690+
@DurationFormat(style = Style.SIMPLE, defaultUnit = DurationFormat.Unit.MICROS)
692691
private Duration styleDuration;
693692

694693
private Year year;

0 commit comments

Comments
 (0)