Browse Source
Prior to this commit, InstantFormatter was able to properly serialize an Instant that is far in the future (or in the past), but it could not properly deserialize it, because in such scenarios an ISO-formatted Instant starts with a +/- sign. This commit fixes this issue, while maintaining the previous contract, and also introduces tests for InstantFormatter. Closes gh-23895pull/23994/head
2 changed files with 124 additions and 5 deletions
@ -0,0 +1,119 @@
@@ -0,0 +1,119 @@
|
||||
/* |
||||
* Copyright 2002-2019 the original author or authors. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
|
||||
package org.springframework.format.datetime.standard; |
||||
|
||||
import org.junit.jupiter.api.extension.ExtensionContext; |
||||
import org.junit.jupiter.params.ParameterizedTest; |
||||
import org.junit.jupiter.params.provider.Arguments; |
||||
import org.junit.jupiter.params.provider.ArgumentsProvider; |
||||
import org.junit.jupiter.params.provider.ArgumentsSource; |
||||
|
||||
import java.text.ParseException; |
||||
import java.time.Instant; |
||||
import java.time.format.DateTimeFormatter; |
||||
import java.util.Random; |
||||
import java.util.stream.Stream; |
||||
|
||||
import static java.time.Instant.MAX; |
||||
import static java.time.Instant.MIN; |
||||
import static java.time.ZoneId.systemDefault; |
||||
import static org.assertj.core.api.Assertions.assertThat; |
||||
|
||||
/** |
||||
* @author Andrei Nevedomskii |
||||
*/ |
||||
@SuppressWarnings("ConstantConditions") |
||||
class InstantFormatterTests { |
||||
|
||||
private final InstantFormatter instantFormatter = new InstantFormatter(); |
||||
|
||||
@ParameterizedTest |
||||
@ArgumentsSource(ISOSerializedInstantProvider.class) |
||||
void should_parse_an_ISO_formatted_string_representation_of_an_instant(String input) throws ParseException { |
||||
Instant expected = DateTimeFormatter.ISO_INSTANT.parse(input, Instant::from); |
||||
|
||||
Instant actual = instantFormatter.parse(input, null); |
||||
|
||||
assertThat(actual).isEqualTo(expected); |
||||
} |
||||
|
||||
@ParameterizedTest |
||||
@ArgumentsSource(RFC1123SerializedInstantProvider.class) |
||||
void should_parse_an_RFC1123_formatted_string_representation_of_an_instant(String input) throws ParseException { |
||||
Instant expected = DateTimeFormatter.RFC_1123_DATE_TIME.parse(input, Instant::from); |
||||
|
||||
Instant actual = instantFormatter.parse(input, null); |
||||
|
||||
assertThat(actual).isEqualTo(expected); |
||||
} |
||||
|
||||
@ParameterizedTest |
||||
@ArgumentsSource(RandomInstantProvider.class) |
||||
void should_serialize_an_instant_using_ISO_format_and_ignoring_locale(Instant input) { |
||||
String expected = DateTimeFormatter.ISO_INSTANT.format(input); |
||||
|
||||
String actual = instantFormatter.print(input, null); |
||||
|
||||
assertThat(actual).isEqualTo(expected); |
||||
} |
||||
|
||||
private static class ISOSerializedInstantProvider extends RandomInstantProvider { |
||||
|
||||
@Override |
||||
Stream<?> provideArguments() { |
||||
return randomInstantStream(MIN, MAX).map(DateTimeFormatter.ISO_INSTANT::format); |
||||
} |
||||
} |
||||
|
||||
private static class RFC1123SerializedInstantProvider extends RandomInstantProvider { |
||||
|
||||
// RFC-1123 supports only 4-digit years
|
||||
private final Instant min = Instant.parse("0000-01-01T00:00:00.00Z"); |
||||
|
||||
private final Instant max = Instant.parse("9999-12-31T23:59:59.99Z"); |
||||
|
||||
@Override |
||||
Stream<?> provideArguments() { |
||||
return randomInstantStream(min, max) |
||||
.map(DateTimeFormatter.RFC_1123_DATE_TIME.withZone(systemDefault())::format); |
||||
} |
||||
} |
||||
|
||||
private static class RandomInstantProvider implements ArgumentsProvider { |
||||
|
||||
private static final long DATA_SET_SIZE = 10; |
||||
|
||||
static final Random RANDOM = new Random(); |
||||
|
||||
Stream<?> provideArguments() { |
||||
return randomInstantStream(MIN, MAX); |
||||
} |
||||
|
||||
@Override |
||||
public final Stream<? extends Arguments> provideArguments(ExtensionContext context) { |
||||
return provideArguments().map(Arguments::of).limit(DATA_SET_SIZE); |
||||
} |
||||
|
||||
Stream<Instant> randomInstantStream(Instant min, Instant max) { |
||||
return Stream.concat( |
||||
Stream.of(Instant.now()), // make sure that the data set includes current instant
|
||||
RANDOM.longs(min.getEpochSecond(), max.getEpochSecond()) |
||||
.mapToObj(Instant::ofEpochSecond) |
||||
); |
||||
} |
||||
} |
||||
} |
||||
Loading…
Reference in new issue