Skip to content

Commit

Permalink
Support parsing long millisecond timestamps in InstantFormatter
Browse files Browse the repository at this point in the history
This commit adds support of parsing a simple long from a String and
turning it to an `Instant` by considering it represents a timestamp in
milliseconds (see `Instant.ofEpochMilli`). Failing to parse a long from
the String, the previous algorithm is used: first check for an RFC-1123
representation then an ISO_INSTANT representation.

See spring-projectsgh-30312
Closes spring-projectsgh-30546
  • Loading branch information
RemusRD authored and mdeinum committed Jun 29, 2023
1 parent 50054f1 commit 1950182
Show file tree
Hide file tree
Showing 3 changed files with 49 additions and 6 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -41,13 +41,18 @@ public class InstantFormatter implements Formatter<Instant> {

@Override
public Instant parse(String text, Locale locale) throws ParseException {
if (text.length() > 0 && Character.isAlphabetic(text.charAt(0))) {
// assuming RFC-1123 value a la "Tue, 3 Jun 2008 11:05:30 GMT"
return Instant.from(DateTimeFormatter.RFC_1123_DATE_TIME.parse(text));
try {
return Instant.ofEpochMilli(Long.parseLong(text));
}
else {
// assuming UTC instant a la "2007-12-03T10:15:30.00Z"
return Instant.parse(text);
catch (NumberFormatException ex) {
if (text.length() > 0 && Character.isAlphabetic(text.charAt(0))) {
// assuming RFC-1123 value a la "Tue, 3 Jun 2008 11:05:30 GMT"
return Instant.from(DateTimeFormatter.RFC_1123_DATE_TIME.parse(text));
}
else {
// assuming UTC instant a la "2007-12-03T10:15:30.00Z"
return Instant.parse(text);
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -621,6 +621,19 @@ void patternLocalDateWithUnsupportedPattern() {
.hasMessageStartingWith("Text '210302'")
.hasNoCause();
}

@Test
void testBindInstantAsLongEpochMillis() {
MutablePropertyValues propertyValues = new MutablePropertyValues();
propertyValues.add("instant", 1234L);
binder.bind(propertyValues);
assertThat(binder.getBindingResult().getErrorCount()).isZero();
assertThat(binder.getBindingResult().getRawFieldValue("instant"))
.isInstanceOf(Instant.class)
.isEqualTo(Instant.ofEpochMilli(1234L));
assertThat(binder.getBindingResult().getFieldValue("instant"))
.hasToString("1970-01-01T00:00:01.234Z");
}
}


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import java.text.ParseException;
import java.time.Instant;
import java.time.format.DateTimeFormatter;
import java.time.temporal.ChronoUnit;
import java.util.Random;
import java.util.stream.Stream;

Expand Down Expand Up @@ -79,6 +80,16 @@ void should_serialize_an_Instant_using_ISO_format_and_ignoring_Locale(Instant in
assertThat(actual).isEqualTo(expected);
}

@ParameterizedTest
@ArgumentsSource(RandomEpochMillisProvider.class)
void should_parse_into_an_Instant_from_epoch_mili(Instant input) throws ParseException {
Instant expected = input;

Instant actual = instantFormatter.parse(Long.toString(input.toEpochMilli()), null);

assertThat(actual).isEqualTo(expected);
}

private static class RandomInstantProvider implements ArgumentsProvider {

private static final long DATA_SET_SIZE = 10;
Expand Down Expand Up @@ -121,5 +132,19 @@ Stream<?> provideArguments() {
.map(DateTimeFormatter.RFC_1123_DATE_TIME.withZone(systemDefault())::format);
}
}
private static final class RandomEpochMillisProvider implements ArgumentsProvider {

private static final long DATA_SET_SIZE = 10;

private static final Random random = new Random();

@Override
public Stream<Arguments> provideArguments(ExtensionContext context) {
return random.longs(DATA_SET_SIZE, Long.MIN_VALUE, Long.MAX_VALUE)
.mapToObj(Instant::ofEpochMilli)
.map(instant -> instant.truncatedTo(ChronoUnit.MILLIS))
.map(Arguments::of);
}
}

}

0 comments on commit 1950182

Please sign in to comment.