diff --git a/spring-context/src/main/java/org/springframework/format/datetime/standard/InstantFormatter.java b/spring-context/src/main/java/org/springframework/format/datetime/standard/InstantFormatter.java index 456c0ad090..84ddfe4f28 100644 --- a/spring-context/src/main/java/org/springframework/format/datetime/standard/InstantFormatter.java +++ b/spring-context/src/main/java/org/springframework/format/datetime/standard/InstantFormatter.java @@ -41,13 +41,18 @@ public class InstantFormatter implements Formatter { @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); + } } } diff --git a/spring-context/src/test/java/org/springframework/format/datetime/standard/DateTimeFormattingTests.java b/spring-context/src/test/java/org/springframework/format/datetime/standard/DateTimeFormattingTests.java index 392fdd61c6..8d072dec85 100644 --- a/spring-context/src/test/java/org/springframework/format/datetime/standard/DateTimeFormattingTests.java +++ b/spring-context/src/test/java/org/springframework/format/datetime/standard/DateTimeFormattingTests.java @@ -621,6 +621,19 @@ class DateTimeFormattingTests { .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"); + } } diff --git a/spring-context/src/test/java/org/springframework/format/datetime/standard/InstantFormatterTests.java b/spring-context/src/test/java/org/springframework/format/datetime/standard/InstantFormatterTests.java index 16ba2cbd5e..1113697bbe 100644 --- a/spring-context/src/test/java/org/springframework/format/datetime/standard/InstantFormatterTests.java +++ b/spring-context/src/test/java/org/springframework/format/datetime/standard/InstantFormatterTests.java @@ -19,6 +19,7 @@ package org.springframework.format.datetime.standard; 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; @@ -79,6 +80,16 @@ class InstantFormatterTests { 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; @@ -121,5 +132,19 @@ class InstantFormatterTests { .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 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); + } + } }