AlwaysWriteSubSecondDigits76Test.java
package com.fasterxml.jackson.datatype.jsr310.ser;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.OffsetDateTime;
import java.time.ZoneOffset;
import java.time.ZonedDateTime;
import java.util.Locale;
import org.junit.jupiter.api.Test;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.databind.json.JsonMapper;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeFeature;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import com.fasterxml.jackson.datatype.jsr310.ModuleTestBase;
import static org.junit.jupiter.api.Assertions.assertEquals;
/**
* Tests for [modules-java8#76]: {@link JavaTimeFeature#ALWAYS_WRITE_SUBSECOND_DIGITS}.
*/
public class AlwaysWriteSubSecondDigits76Test extends ModuleTestBase
{
static class Wrapper {
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd'T'HH:mm:ss")
public OffsetDateTime value;
Wrapper(OffsetDateTime v) { value = v; }
}
static class ShapeOnlyWrapper {
@JsonFormat(shape = JsonFormat.Shape.STRING)
public Instant value;
ShapeOnlyWrapper(Instant v) { value = v; }
}
// NOTE: cannot use `ModuleTestBase.mapperBuilder()` here, since it already registers a
// plain `JavaTimeModule` and duplicate registrations of the same module are ignored
private static JsonMapper.Builder builderWithFeature() {
return JsonMapper.builder()
.defaultLocale(Locale.ENGLISH)
.addModule(new JavaTimeModule()
.enable(JavaTimeFeature.ALWAYS_WRITE_SUBSECOND_DIGITS));
}
private final ObjectMapper MAPPER = builderWithFeature()
.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS)
.build();
private final ObjectMapper DEFAULT_MAPPER = mapperBuilder()
.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS)
.build();
@Test
public void testInstantZeroSubSecond() throws Exception
{
Instant value = Instant.parse("2017-09-14T04:28:48Z");
// Default: sub-second field omitted entirely
assertEquals(q("2017-09-14T04:28:48Z"), DEFAULT_MAPPER.writeValueAsString(value));
// Enabled: zero-padded to millisecond precision
assertEquals(q("2017-09-14T04:28:48.000Z"), MAPPER.writeValueAsString(value));
}
@Test
public void testInstantHigherPrecisionNotTruncated() throws Exception
{
assertEquals(q("2017-09-14T04:28:48.100Z"),
MAPPER.writeValueAsString(Instant.parse("2017-09-14T04:28:48.100Z")));
assertEquals(q("2017-09-14T04:28:48.123456Z"),
MAPPER.writeValueAsString(Instant.parse("2017-09-14T04:28:48.123456Z")));
assertEquals(q("2017-09-14T04:28:48.123456789Z"),
MAPPER.writeValueAsString(Instant.parse("2017-09-14T04:28:48.123456789Z")));
}
@Test
public void testOffsetDateTime() throws Exception
{
OffsetDateTime value = OffsetDateTime.parse("2017-09-14T04:28:48+02:00");
assertEquals(q("2017-09-14T04:28:48+02:00"), DEFAULT_MAPPER.writeValueAsString(value));
assertEquals(q("2017-09-14T04:28:48.000+02:00"), MAPPER.writeValueAsString(value));
// Note: the JDK ISO formatter renders 100 msec as ".1"; with the feature on,
// width is stable at (at least) 3 digits
OffsetDateTime millis = OffsetDateTime.parse("2017-09-14T04:28:48.100+02:00");
assertEquals(q("2017-09-14T04:28:48.1+02:00"), DEFAULT_MAPPER.writeValueAsString(millis));
assertEquals(q("2017-09-14T04:28:48.100+02:00"), MAPPER.writeValueAsString(millis));
}
@Test
public void testZonedDateTime() throws Exception
{
ZonedDateTime value = ZonedDateTime.parse("2017-09-14T04:28:48+02:00[Europe/Budapest]");
assertEquals(q("2017-09-14T04:28:48.000+02:00"), MAPPER.writeValueAsString(value));
}
@Test
public void testZonedDateTimeWithZoneId() throws Exception
{
ObjectMapper mapper = builderWithFeature()
.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS)
.enable(SerializationFeature.WRITE_DATES_WITH_ZONE_ID)
.build();
ZonedDateTime value = ZonedDateTime.parse("2017-09-14T04:28:48+02:00[Europe/Budapest]");
assertEquals(q("2017-09-14T04:28:48.000+02:00[Europe/Budapest]"),
mapper.writeValueAsString(value));
}
@Test
public void testLocalDateTime() throws Exception
{
LocalDateTime value = LocalDateTime.parse("2017-09-14T04:28:48");
assertEquals(q("2017-09-14T04:28:48"), DEFAULT_MAPPER.writeValueAsString(value));
assertEquals(q("2017-09-14T04:28:48.000"), MAPPER.writeValueAsString(value));
// Seconds keep being written even when zero (as with the JDK ISO formatter)
LocalDateTime noSeconds = LocalDateTime.parse("2017-09-14T04:28");
assertEquals(q("2017-09-14T04:28:00"), DEFAULT_MAPPER.writeValueAsString(noSeconds));
assertEquals(q("2017-09-14T04:28:00.000"), MAPPER.writeValueAsString(noSeconds));
}
// Feature must not leak into numeric timestamp serialization
@Test
public void testTimestampsUnaffected() throws Exception
{
ObjectMapper mapper = builderWithFeature()
.enable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS)
.build();
assertEquals("1505363328.000000000",
mapper.writeValueAsString(Instant.parse("2017-09-14T04:28:48Z")));
}
// ... nor override an explicit `@JsonFormat` pattern
@Test
public void testExplicitPatternWins() throws Exception
{
assertEquals(a2q("{'value':'2017-09-14T04:28:48'}"),
MAPPER.writeValueAsString(new Wrapper(OffsetDateTime.parse("2017-09-14T04:28:48Z"))));
}
// Values written with the feature on must still be readable
@Test
public void testRoundTrip() throws Exception
{
for (String raw : new String[] {
"2017-09-14T04:28:48Z", "2017-09-14T04:28:48.123456789Z",
"1970-01-01T00:00:00Z", "+10000-09-14T04:28:48Z", "-0100-09-14T04:28:48Z" }) {
Instant value = Instant.parse(raw);
String json = MAPPER.writeValueAsString(value);
assertEquals(value, MAPPER.readValue(json, Instant.class),
"Round-trip failed for " + raw + " (serialized as " + json + ")");
}
}
// The full `Instant` range must keep working: it is wider than that of
// `LocalDateTime`, so the feature must not route `Instant` through a
// zone-bound formatter
@Test
public void testInstantMinMax() throws Exception
{
// no sub-second part -> padded
assertEquals(q("-1000000000-01-01T00:00:00Z"),
DEFAULT_MAPPER.writeValueAsString(Instant.MIN));
assertEquals(q("-1000000000-01-01T00:00:00.000Z"),
MAPPER.writeValueAsString(Instant.MIN));
// already at nanosecond precision -> unchanged
assertEquals(q("+1000000000-12-31T23:59:59.999999999Z"),
DEFAULT_MAPPER.writeValueAsString(Instant.MAX));
assertEquals(q("+1000000000-12-31T23:59:59.999999999Z"),
MAPPER.writeValueAsString(Instant.MAX));
// and just inside the `LocalDateTime` boundary, either side
assertEquals(q("-999999999-01-01T00:00:00.000Z"),
MAPPER.writeValueAsString(LocalDateTime.MIN.toInstant(ZoneOffset.UTC)));
assertEquals(q("-1000000000-12-31T23:59:59.000Z"),
MAPPER.writeValueAsString(LocalDateTime.MIN.toInstant(ZoneOffset.UTC).minusSeconds(1)));
}
// `Instant.toString()` writes 0, 3, 6 or 9 sub-second digits; only the
// zero case may be rewritten, the rest must be passed through untouched
@Test
public void testInstantSubSecondWidthsPreserved() throws Exception
{
assertEquals(q("1970-01-01T00:00:00.000000001Z"),
MAPPER.writeValueAsString(Instant.ofEpochSecond(0, 1)));
assertEquals(q("1970-01-01T00:00:00.000001Z"),
MAPPER.writeValueAsString(Instant.ofEpochSecond(0, 1000)));
assertEquals(q("1970-01-01T00:00:00.001Z"),
MAPPER.writeValueAsString(Instant.ofEpochSecond(0, 1000000)));
}
// `@JsonFormat(shape=STRING)` without a pattern must not lose the padding
@Test
public void testShapeStringWithoutPattern() throws Exception
{
assertEquals(a2q("{'value':'2017-09-14T04:28:48.000Z'}"),
MAPPER.writeValueAsString(new ShapeOnlyWrapper(Instant.parse("2017-09-14T04:28:48Z"))));
}
}