1
16
17 package com.fasterxml.jackson.datatype.jsr310.deser;
18
19 import com.fasterxml.jackson.annotation.JsonFormat;
20 import com.fasterxml.jackson.core.JsonParser;
21 import com.fasterxml.jackson.core.JsonToken;
22 import com.fasterxml.jackson.core.JsonTokenId;
23 import com.fasterxml.jackson.databind.BeanProperty;
24 import com.fasterxml.jackson.databind.DeserializationContext;
25 import com.fasterxml.jackson.databind.DeserializationFeature;
26 import com.fasterxml.jackson.databind.JsonDeserializer;
27 import com.fasterxml.jackson.databind.JsonMappingException;
28 import com.fasterxml.jackson.datatype.jsr310.DecimalUtils;
29
30 import java.io.IOException;
31 import java.math.BigDecimal;
32 import java.time.DateTimeException;
33 import java.time.Instant;
34 import java.time.OffsetDateTime;
35 import java.time.ZoneId;
36 import java.time.ZonedDateTime;
37 import java.time.format.DateTimeFormatter;
38 import java.time.temporal.Temporal;
39 import java.time.temporal.TemporalAccessor;
40 import java.util.function.BiFunction;
41 import java.util.function.Function;
42 import java.util.regex.Pattern;
43
44
50 public class InstantDeserializer<T extends Temporal>
51 extends JSR310DateTimeDeserializerBase<T>
52 {
53 private static final long serialVersionUID = 1L;
54
55
60 private static final Pattern ISO8601_UTC_ZERO_OFFSET_SUFFIX_REGEX = Pattern.compile("\\+00:?(00)?$");
61
62 public static final InstantDeserializer<Instant> INSTANT = new InstantDeserializer<>(
63 Instant.class, DateTimeFormatter.ISO_INSTANT,
64 Instant::from,
65 a -> Instant.ofEpochMilli(a.value),
66 a -> Instant.ofEpochSecond(a.integer, a.fraction),
67 null,
68 true
69 );
70
71 public static final InstantDeserializer<OffsetDateTime> OFFSET_DATE_TIME = new InstantDeserializer<>(
72 OffsetDateTime.class, DateTimeFormatter.ISO_OFFSET_DATE_TIME,
73 OffsetDateTime::from,
74 a -> OffsetDateTime.ofInstant(Instant.ofEpochMilli(a.value), a.zoneId),
75 a -> OffsetDateTime.ofInstant(Instant.ofEpochSecond(a.integer, a.fraction), a.zoneId),
76 (d, z) -> d.withOffsetSameInstant(z.getRules().getOffset(d.toLocalDateTime())),
77 true
78 );
79
80 public static final InstantDeserializer<ZonedDateTime> ZONED_DATE_TIME = new InstantDeserializer<>(
81 ZonedDateTime.class, DateTimeFormatter.ISO_ZONED_DATE_TIME,
82 ZonedDateTime::from,
83 a -> ZonedDateTime.ofInstant(Instant.ofEpochMilli(a.value), a.zoneId),
84 a -> ZonedDateTime.ofInstant(Instant.ofEpochSecond(a.integer, a.fraction), a.zoneId),
85 ZonedDateTime::withZoneSameInstant,
86 false
87 );
88
89 protected final Function<FromIntegerArguments, T> fromMilliseconds;
90
91 protected final Function<FromDecimalArguments, T> fromNanoseconds;
92
93 protected final Function<TemporalAccessor, T> parsedToValue;
94
95 protected final BiFunction<T, ZoneId, T> adjust;
96
97
104 protected final boolean replaceZeroOffsetAsZ;
105
106
111 protected final Boolean _adjustToContextTZOverride;
112
113 protected InstantDeserializer(Class<T> supportedType,
114 DateTimeFormatter formatter,
115 Function<TemporalAccessor, T> parsedToValue,
116 Function<FromIntegerArguments, T> fromMilliseconds,
117 Function<FromDecimalArguments, T> fromNanoseconds,
118 BiFunction<T, ZoneId, T> adjust,
119 boolean replaceZeroOffsetAsZ)
120 {
121 super(supportedType, formatter);
122 this.parsedToValue = parsedToValue;
123 this.fromMilliseconds = fromMilliseconds;
124 this.fromNanoseconds = fromNanoseconds;
125 this.adjust = adjust == null ? ((d, z) -> d) : adjust;
126 this.replaceZeroOffsetAsZ = replaceZeroOffsetAsZ;
127 _adjustToContextTZOverride = null;
128 }
129
130 @SuppressWarnings("unchecked")
131 protected InstantDeserializer(InstantDeserializer<T> base, DateTimeFormatter f)
132 {
133 super((Class<T>) base.handledType(), f);
134 parsedToValue = base.parsedToValue;
135 fromMilliseconds = base.fromMilliseconds;
136 fromNanoseconds = base.fromNanoseconds;
137 adjust = base.adjust;
138 replaceZeroOffsetAsZ = (_formatter == DateTimeFormatter.ISO_INSTANT);
139 _adjustToContextTZOverride = base._adjustToContextTZOverride;
140 }
141
142 @SuppressWarnings("unchecked")
143 protected InstantDeserializer(InstantDeserializer<T> base, Boolean adjustToContextTimezoneOverride)
144 {
145 super((Class<T>) base.handledType(), base._formatter);
146 parsedToValue = base.parsedToValue;
147 fromMilliseconds = base.fromMilliseconds;
148 fromNanoseconds = base.fromNanoseconds;
149 adjust = base.adjust;
150 replaceZeroOffsetAsZ = base.replaceZeroOffsetAsZ;
151 _adjustToContextTZOverride = adjustToContextTimezoneOverride;
152 }
153
154 @SuppressWarnings("unchecked")
155 protected InstantDeserializer(InstantDeserializer<T> base, DateTimeFormatter f, Boolean leniency)
156 {
157 super((Class<T>) base.handledType(), f, leniency);
158 parsedToValue = base.parsedToValue;
159 fromMilliseconds = base.fromMilliseconds;
160 fromNanoseconds = base.fromNanoseconds;
161 adjust = base.adjust;
162 replaceZeroOffsetAsZ = (_formatter == DateTimeFormatter.ISO_INSTANT);
163 _adjustToContextTZOverride = base._adjustToContextTZOverride;
164 }
165
166 @Override
167 protected InstantDeserializer<T> withDateFormat(DateTimeFormatter dtf) {
168 if (dtf == _formatter) {
169 return this;
170 }
171 return new InstantDeserializer<T>(this, dtf);
172 }
173
174 @Override
175 protected InstantDeserializer<T> withLeniency(Boolean leniency) {
176 return new InstantDeserializer<T>(this, _formatter, leniency);
177 }
178
179 @Override
180 protected InstantDeserializer<T> withShape(JsonFormat.Shape shape) { return this; }
181
182 @SuppressWarnings("unchecked")
183 @Override
184 public T deserialize(JsonParser parser, DeserializationContext context) throws IOException
185 {
186
187
188 switch (parser.getCurrentTokenId())
189 {
190 case JsonTokenId.ID_NUMBER_FLOAT:
191 return _fromDecimal(context, parser.getDecimalValue());
192
193 case JsonTokenId.ID_NUMBER_INT:
194 return _fromLong(context, parser.getLongValue());
195
196 case JsonTokenId.ID_STRING:
197 {
198 String string = parser.getText().trim();
199 if (string.length() == 0) {
200 if (!isLenient()) {
201 return _failForNotLenient(parser, context, JsonToken.VALUE_STRING);
202 }
203 return null;
204 }
205
206 if (_formatter == DateTimeFormatter.ISO_INSTANT ||
207 _formatter == DateTimeFormatter.ISO_OFFSET_DATE_TIME ||
208 _formatter == DateTimeFormatter.ISO_ZONED_DATE_TIME) {
209
210 int dots = _countPeriods(string);
211 if (dots >= 0) {
212 try {
213 if (dots == 0) {
214 return _fromLong(context, Long.parseLong(string));
215 }
216 if (dots == 1) {
217 return _fromDecimal(context, new BigDecimal(string));
218 }
219 } catch (NumberFormatException e) {
220
221 }
222 }
223
224 string = replaceZeroOffsetAsZIfNecessary(string);
225 }
226
227 T value;
228 try {
229 TemporalAccessor acc = _formatter.parse(string);
230 value = parsedToValue.apply(acc);
231 if (shouldAdjustToContextTimezone(context)) {
232 return adjust.apply(value, this.getZone(context));
233 }
234 } catch (DateTimeException e) {
235 value = _handleDateTimeException(context, e, string);
236 }
237 return value;
238 }
239
240 case JsonTokenId.ID_EMBEDDED_OBJECT:
241
242
243 return (T) parser.getEmbeddedObject();
244
245 case JsonTokenId.ID_START_ARRAY:
246 return _deserializeFromArray(parser, context);
247 }
248 return _handleUnexpectedToken(context, parser, JsonToken.VALUE_STRING,
249 JsonToken.VALUE_NUMBER_INT, JsonToken.VALUE_NUMBER_FLOAT);
250 }
251
252 @SuppressWarnings("unchecked")
253 @Override
254 public JsonDeserializer<T> createContextual(DeserializationContext ctxt,
255 BeanProperty property) throws JsonMappingException
256 {
257 InstantDeserializer<T> deserializer =
258 (InstantDeserializer<T>)super.createContextual(ctxt, property);
259 if (deserializer != this) {
260 JsonFormat.Value val = findFormatOverrides(ctxt, property, handledType());
261 if (val != null) {
262 deserializer = new InstantDeserializer<>(deserializer, val.getFeature(JsonFormat.Feature.ADJUST_DATES_TO_CONTEXT_TIME_ZONE));
263 if (val.hasLenient()) {
264 Boolean leniency = val.getLenient();
265 if (leniency != null) {
266 deserializer = deserializer.withLeniency(leniency);
267 }
268 }
269 }
270 }
271 return deserializer;
272 }
273
274 protected boolean shouldAdjustToContextTimezone(DeserializationContext context) {
275 return (_adjustToContextTZOverride != null) ? _adjustToContextTZOverride :
276 context.isEnabled(DeserializationFeature.ADJUST_DATES_TO_CONTEXT_TIME_ZONE);
277 }
278
279
280 protected int _countPeriods(String str)
281 {
282 int commas = 0;
283 for (int i = 0, end = str.length(); i < end; ++i) {
284 int ch = str.charAt(i);
285 if (ch < '0' || ch > '9') {
286 if (ch == '.') {
287 ++commas;
288 } else {
289 return -1;
290 }
291 }
292 }
293 return commas;
294 }
295
296 protected T _fromLong(DeserializationContext context, long timestamp)
297 {
298 if(context.isEnabled(DeserializationFeature.READ_DATE_TIMESTAMPS_AS_NANOSECONDS)){
299 return fromNanoseconds.apply(new FromDecimalArguments(
300 timestamp, 0, this.getZone(context)
301 ));
302 }
303 return fromMilliseconds.apply(new FromIntegerArguments(
304 timestamp, this.getZone(context)));
305 }
306
307 protected T _fromDecimal(DeserializationContext context, BigDecimal value)
308 {
309 FromDecimalArguments args =
310 DecimalUtils.extractSecondsAndNanos(value, (s, ns) -> new FromDecimalArguments(s, ns, getZone(context)));
311 return fromNanoseconds.apply(args);
312 }
313
314 private ZoneId getZone(DeserializationContext context)
315 {
316
317 return (_valueClass == Instant.class) ? null : context.getTimeZone().toZoneId();
318 }
319
320 private String replaceZeroOffsetAsZIfNecessary(String text)
321 {
322 if (replaceZeroOffsetAsZ) {
323 return ISO8601_UTC_ZERO_OFFSET_SUFFIX_REGEX.matcher(text).replaceFirst("Z");
324 }
325
326 return text;
327 }
328
329 public static class FromIntegerArguments
330 {
331 public final long value;
332 public final ZoneId zoneId;
333
334 private FromIntegerArguments(long value, ZoneId zoneId)
335 {
336 this.value = value;
337 this.zoneId = zoneId;
338 }
339 }
340
341 public static class FromDecimalArguments
342 {
343 public final long integer;
344 public final int fraction;
345 public final ZoneId zoneId;
346
347 private FromDecimalArguments(long integer, int fraction, ZoneId zoneId)
348 {
349 this.integer = integer;
350 this.fraction = fraction;
351 this.zoneId = zoneId;
352 }
353 }
354 }
355