-
Notifications
You must be signed in to change notification settings - Fork 34
Support for OffsetDateTime and ZonedDateTime in jackson-jr-extension-javatime
#201
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 5 commits
360a960
adf67c8
cc26e74
b4166d3
d117f6d
1db59c8
f1fdc7c
24a6c1b
c72bbce
4d257e3
f8709b0
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,47 @@ | ||
| package com.fasterxml.jackson.jr.extension.javatime; | ||
|
|
||
| import java.io.IOException; | ||
| import java.time.OffsetDateTime; | ||
| import java.time.ZonedDateTime; | ||
| import java.time.temporal.TemporalAccessor; | ||
| import java.time.temporal.TemporalQuery; | ||
| import java.util.Objects; | ||
|
|
||
| import com.fasterxml.jackson.core.JsonParser; | ||
| import com.fasterxml.jackson.core.JsonToken; | ||
| import com.fasterxml.jackson.jr.ob.api.ValueReader; | ||
| import com.fasterxml.jackson.jr.ob.impl.JSONReader; | ||
|
|
||
| /** | ||
| * {@link ValueReader} designed to easily handle {@link TemporalAccessor} descendants such as | ||
| * {@link OffsetDateTime} and {@link ZonedDateTime}. Their string representation is expected to | ||
| * be in ISO 8601 format. | ||
| * @since 2.20 | ||
| * @see <a href="https://en.wikipedia.org/wiki/ISO_8601">ISO 8601 on Wikipedia</a> | ||
| */ | ||
| public class DefaultDateTimeValueReader<T extends TemporalAccessor> extends ValueReader { | ||
| private final TemporalQuery<T> _query; | ||
|
|
||
| /** | ||
| * Constructor that includes a temportal query that is to be used during formatting. | ||
| * @param targetType Target type | ||
| * @param query Temporal query for parsing | ||
| */ | ||
| public DefaultDateTimeValueReader(Class<T> targetType, TemporalQuery<T> query) { | ||
| super(targetType); | ||
|
|
||
| this._query = Objects.requireNonNull(query); | ||
| } | ||
|
|
||
| @Override | ||
| public Object read(JSONReader reader, JsonParser p) throws IOException { | ||
| // SimpleValueReader allows 'Date' objects to be null, so this should probably | ||
| // also be the case here. | ||
| if (p.hasToken(JsonToken.VALUE_NULL)) { | ||
| return null; | ||
| } | ||
|
|
||
| return JavaTimeReaderWriterProvider.FORMATTER.parse(p.getText(), _query); | ||
| } | ||
|
|
||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,48 +1,106 @@ | ||
| package com.fasterxml.jackson.jr.extension.javatime; | ||
|
|
||
| import java.time.LocalDateTime; | ||
| import java.time.OffsetDateTime; | ||
| import java.time.ZoneId; | ||
| import java.time.ZonedDateTime; | ||
| import java.time.format.DateTimeFormatter; | ||
| import java.time.format.DateTimeFormatterBuilder; | ||
| import java.time.temporal.ChronoField; | ||
|
|
||
| import com.fasterxml.jackson.jr.ob.api.ReaderWriterProvider; | ||
| import com.fasterxml.jackson.jr.ob.api.ValueReader; | ||
| import com.fasterxml.jackson.jr.ob.api.ValueWriter; | ||
| import com.fasterxml.jackson.jr.ob.impl.JSONReader; | ||
| import com.fasterxml.jackson.jr.ob.impl.JSONWriter; | ||
|
|
||
| import java.time.LocalDateTime; | ||
| import java.time.format.DateTimeFormatter; | ||
|
|
||
| /** | ||
| * Provider for {@link ValueReader}s and {@link ValueWriter}s for Date/Time | ||
| * types supported by Java Time Extension. | ||
| */ | ||
| public class JavaTimeReaderWriterProvider extends ReaderWriterProvider | ||
| { | ||
| private DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ISO_LOCAL_DATE_TIME; | ||
|
|
||
| public JavaTimeReaderWriterProvider() { } | ||
| private ZoneId _fallbackLocalZoneId; | ||
|
|
||
| protected static final DateTimeFormatter FORMATTER = createFormatter(true); | ||
| protected static final DateTimeFormatter LOCAL_FORMATTER = createFormatter(false); | ||
|
|
||
| public JavaTimeReaderWriterProvider() { | ||
| _fallbackLocalZoneId = ZoneId.systemDefault(); | ||
|
||
| } | ||
|
|
||
| @Override | ||
| public ValueReader findValueReader(JSONReader readContext, Class<?> type) { | ||
| return LocalDateTime.class.isAssignableFrom(type) ? new LocalDateTimeValueReader(dateTimeFormatter) : null; | ||
| if (LocalDateTime.class.isAssignableFrom(type)) { | ||
| return new LocalDateTimeValueReader(_fallbackLocalZoneId); | ||
| } | ||
| if (OffsetDateTime.class.isAssignableFrom(type)) { | ||
| return new DefaultDateTimeValueReader<OffsetDateTime>(OffsetDateTime.class, OffsetDateTime::from); | ||
| } | ||
| if (ZonedDateTime.class.isAssignableFrom(type)) { | ||
| return new DefaultDateTimeValueReader<ZonedDateTime>(ZonedDateTime.class, ZonedDateTime::from); | ||
| } | ||
| return null; | ||
| } | ||
|
|
||
| @Override | ||
| public ValueWriter findValueWriter(JSONWriter writeContext, Class<?> type) { | ||
| return LocalDateTime.class.isAssignableFrom(type) ? new LocalDateTimeValueWriter(dateTimeFormatter) : null; | ||
| if (LocalDateTime.class.isAssignableFrom(type)) { | ||
| return new LocalDateTimeValueWriter(); | ||
| } | ||
| if (OffsetDateTime.class.isAssignableFrom(type)) { | ||
| return new OffsetDateTimeValueWriter(); | ||
| } | ||
| if (ZonedDateTime.class.isAssignableFrom(type)) { | ||
| return new ZonedDateTimeValueWriter(); | ||
| } | ||
| return null; | ||
| } | ||
|
|
||
| /** | ||
| * Method for reconfiguring {@link DateTimeFormatter} used for reading/writing | ||
| * following Date/Time value types: | ||
| *<ul> | ||
| * <li>{@code java.time.LocalDateTime} | ||
| * </li> | ||
| *</ul> | ||
| * | ||
| * @param formatter | ||
| * | ||
| * @return This provider instance for call chaining | ||
| * Setter to configure a time zone that is to be applied when a zoned ISO 8601 date time needs | ||
| * to be converted to a <code>LocalDateTime</code>. Can be set to <code>null</code> to apply | ||
| * the system default. | ||
| * @see java.time.LocalDateTime | ||
| * @param fallbackLocalZoneId Time zone to apply, or <code>null</code> | ||
| * @since 2.20 | ||
| * @return Reference for chaining | ||
| */ | ||
| public JavaTimeReaderWriterProvider withDateTimeFormatter(DateTimeFormatter formatter) { | ||
| dateTimeFormatter = formatter; | ||
| public JavaTimeReaderWriterProvider setLocalFallbackTimeZone(ZoneId fallbackLocalZoneId) { | ||
| _fallbackLocalZoneId = fallbackLocalZoneId == null ? ZoneId.systemDefault() : fallbackLocalZoneId; | ||
| return this; | ||
| } | ||
|
|
||
| /** | ||
| * Create a forgiving date time formatter that allows different interpretations of ISO 8601 | ||
| * strings to be parsed. | ||
| * | ||
| * @param includeUtcDefault Set to <code>true</code> to set UTC to be the default offset | ||
| * for non-local date times. Set to <code>false</code> when handling | ||
| * local date times. | ||
| * @since 2.20 | ||
| * @return Formatter | ||
| */ | ||
| public static DateTimeFormatter createFormatter(boolean includeUtcDefault) { | ||
StanB-EKZ marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| final DateTimeFormatterBuilder builder = new DateTimeFormatterBuilder() | ||
| .parseLenient() | ||
| .appendPattern("yyyy-MM-dd'T'HH:mm:ss") | ||
| .optionalStart() | ||
| .appendFraction(ChronoField.MILLI_OF_SECOND, 1, 9, true) | ||
| .optionalEnd() | ||
| .optionalStart() | ||
| .appendOffsetId() | ||
| .optionalStart() | ||
| .appendLiteral('[') | ||
| .appendZoneRegionId() | ||
| .appendLiteral(']') | ||
| .optionalEnd() | ||
| .optionalEnd(); | ||
|
|
||
| if (includeUtcDefault) { | ||
| builder.parseDefaulting(ChronoField.OFFSET_SECONDS, 0); | ||
| } | ||
|
|
||
| return builder.toFormatter(); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,23 +1,60 @@ | ||
| package com.fasterxml.jackson.jr.extension.javatime; | ||
|
|
||
| import java.io.IOException; | ||
| import java.time.LocalDateTime; | ||
| import java.time.ZoneId; | ||
| import java.time.ZonedDateTime; | ||
| import java.time.temporal.TemporalAccessor; | ||
| import java.util.Objects; | ||
|
|
||
| import com.fasterxml.jackson.core.JsonParser; | ||
| import com.fasterxml.jackson.core.JsonToken; | ||
| import com.fasterxml.jackson.jr.ob.api.ValueReader; | ||
| import com.fasterxml.jackson.jr.ob.impl.JSONReader; | ||
|
|
||
| import java.io.IOException; | ||
| import java.time.LocalDateTime; | ||
| import java.time.format.DateTimeFormatter; | ||
|
|
||
| /** | ||
| * {@link ValueReader} designed specifically to handle {@link LocalDateTime} instances. This | ||
| * requires a slightly different approach than other date time types because we want to be as | ||
| * forgiving as possible and be able to also interpret ISO 8601 dates that include an offset | ||
| * or zone ID. | ||
| * @since 2.20 | ||
| * @see <a href="https://en.wikipedia.org/wiki/ISO_8601">ISO 8601 on Wikipedia</a> | ||
| */ | ||
| public class LocalDateTimeValueReader extends ValueReader { | ||
| private final DateTimeFormatter formatter; | ||
|
|
||
| public LocalDateTimeValueReader(DateTimeFormatter formatter) { | ||
| private final ZoneId _localZoneId; | ||
|
|
||
| /** | ||
| * Constructor that accepts a zone ID that should be used to when a ISO 8601 string that | ||
| * includes an offset needs to be converted to a local date time. | ||
| * @param localZoneId Destination zone ID | ||
| */ | ||
| public LocalDateTimeValueReader(ZoneId localZoneId) { | ||
| super(LocalDateTime.class); | ||
| this.formatter = formatter; | ||
| _localZoneId = Objects.requireNonNull(localZoneId); | ||
| } | ||
|
|
||
| @Override | ||
| public Object read(JSONReader reader, JsonParser p) throws IOException { | ||
| return LocalDateTime.parse(p.getText(), formatter); | ||
| // SimpleValueReader allows 'Date' objects to be null, so this should probably | ||
| // also be the case here. | ||
| if (p.hasToken(JsonToken.VALUE_NULL)) { | ||
| return null; | ||
| } | ||
|
|
||
| final TemporalAccessor ta = JavaTimeReaderWriterProvider.LOCAL_FORMATTER.parseBest(p.getText(), | ||
| ZonedDateTime::from, LocalDateTime::from); | ||
|
|
||
| if (ta instanceof ZonedDateTime) { | ||
| // Convert a date time that unexpectedly includes a time offset or zone ID, to a proper local date time | ||
| return ((ZonedDateTime)ta).withZoneSameInstant(_localZoneId).toLocalDateTime(); | ||
| } | ||
| if (ta instanceof LocalDateTime) { | ||
| return ta; | ||
| } | ||
|
|
||
| throw new IOException(String.format("Converting \"%s\" to an instance of %s was " | ||
| + "unexpected and should not occur", | ||
| p.getText(), | ||
| ta.getClass().getSimpleName())); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,28 @@ | ||
| package com.fasterxml.jackson.jr.extension.javatime; | ||
|
|
||
| import java.io.IOException; | ||
| import java.time.OffsetDateTime; | ||
| import java.time.format.DateTimeFormatter; | ||
|
|
||
| import com.fasterxml.jackson.core.JsonGenerator; | ||
| import com.fasterxml.jackson.jr.ob.api.ValueWriter; | ||
| import com.fasterxml.jackson.jr.ob.impl.JSONWriter; | ||
|
|
||
| /** | ||
| * {@link ValueWriter} that converts a {@link OffsetDateTime} to an ISO 8601 string including | ||
| * an offset. | ||
| * @see <a href="https://en.wikipedia.org/wiki/ISO_8601">ISO 8601 on Wikipedia</a> | ||
| * @since 2.20 | ||
| */ | ||
| public class OffsetDateTimeValueWriter implements ValueWriter { | ||
| @Override | ||
| public void writeValue(JSONWriter context, JsonGenerator g, Object value) throws IOException { | ||
| String offsetDateTimeString = ((OffsetDateTime) value).format(DateTimeFormatter.ISO_OFFSET_DATE_TIME); | ||
| context.writeValue(offsetDateTimeString); | ||
| } | ||
|
|
||
| @Override | ||
| public Class<?> valueType() { | ||
| return OffsetDateTime.class; | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,28 @@ | ||
| package com.fasterxml.jackson.jr.extension.javatime; | ||
|
|
||
| import java.io.IOException; | ||
| import java.time.ZonedDateTime; | ||
| import java.time.format.DateTimeFormatter; | ||
|
|
||
| import com.fasterxml.jackson.core.JsonGenerator; | ||
| import com.fasterxml.jackson.jr.ob.api.ValueWriter; | ||
| import com.fasterxml.jackson.jr.ob.impl.JSONWriter; | ||
|
|
||
| /** | ||
| * {@link ValueWriter} that converts a {@link ZonedDateTime} to an ISO 8601 string including | ||
| * an offset and a zone ID. | ||
| * @see <a href="https://en.wikipedia.org/wiki/ISO_8601">ISO 8601 on Wikipedia</a> | ||
| * @since 2.20 | ||
| */ | ||
| public class ZonedDateTimeValueWriter implements ValueWriter { | ||
| @Override | ||
| public void writeValue(JSONWriter context, JsonGenerator g, Object value) throws IOException { | ||
| String zonedDateTimeString = ((ZonedDateTime) value).format(DateTimeFormatter.ISO_ZONED_DATE_TIME); | ||
| context.writeValue(zonedDateTimeString); | ||
| } | ||
|
|
||
| @Override | ||
| public Class<?> valueType() { | ||
| return ZonedDateTime.class; | ||
| } | ||
| } |
Uh oh!
There was an error while loading. Please reload this page.