39
39
import java .time .temporal .TemporalAccessor ;
40
40
import java .util .function .BiFunction ;
41
41
import java .util .function .Function ;
42
+ import java .util .regex .Pattern ;
42
43
43
44
/**
44
45
* Deserializer for Java 8 temporal {@link Instant}s, {@link OffsetDateTime}, and {@link ZonedDateTime}s.
@@ -51,13 +52,20 @@ public class InstantDeserializer<T extends Temporal>
51
52
{
52
53
private static final long serialVersionUID = 1L ;
53
54
55
+ /**
56
+ * Constants used to check if the time offset is zero. See [jackson-modules-java8#18]
57
+ *
58
+ * @since 2.9.0
59
+ */
60
+ private static final Pattern ISO8601_UTC_ZERO_OFFSET_SUFFIX_REGEX = Pattern .compile ("\\ +00:?(00)?$" );
61
+
54
62
public static final InstantDeserializer <Instant > INSTANT = new InstantDeserializer <>(
55
63
Instant .class , DateTimeFormatter .ISO_INSTANT ,
56
64
Instant ::from ,
57
65
a -> Instant .ofEpochMilli (a .value ),
58
66
a -> Instant .ofEpochSecond (a .integer , a .fraction ),
59
67
null ,
60
- true // yes, replace +0000 with Z
68
+ true // yes, replace zero offset with Z
61
69
);
62
70
63
71
public static final InstantDeserializer <OffsetDateTime > OFFSET_DATE_TIME = new InstantDeserializer <>(
@@ -66,7 +74,7 @@ public class InstantDeserializer<T extends Temporal>
66
74
a -> OffsetDateTime .ofInstant (Instant .ofEpochMilli (a .value ), a .zoneId ),
67
75
a -> OffsetDateTime .ofInstant (Instant .ofEpochSecond (a .integer , a .fraction ), a .zoneId ),
68
76
(d , z ) -> d .withOffsetSameInstant (z .getRules ().getOffset (d .toLocalDateTime ())),
69
- true // yes, replace +0000 with Z
77
+ true // yes, replace zero offset with Z
70
78
);
71
79
72
80
public static final InstantDeserializer <ZonedDateTime > ZONED_DATE_TIME = new InstantDeserializer <>(
@@ -75,7 +83,7 @@ public class InstantDeserializer<T extends Temporal>
75
83
a -> ZonedDateTime .ofInstant (Instant .ofEpochMilli (a .value ), a .zoneId ),
76
84
a -> ZonedDateTime .ofInstant (Instant .ofEpochSecond (a .integer , a .fraction ), a .zoneId ),
77
85
ZonedDateTime ::withZoneSameInstant ,
78
- false // keep +0000 and Z separate since zones explicitly supported
86
+ false // keep zero offset and Z separate since zones explicitly supported
79
87
);
80
88
81
89
protected final Function <FromIntegerArguments , T > fromMilliseconds ;
@@ -87,13 +95,13 @@ public class InstantDeserializer<T extends Temporal>
87
95
protected final BiFunction <T , ZoneId , T > adjust ;
88
96
89
97
/**
90
- * In case of vanilla `Instant` we seem to need to translate "+0000"
98
+ * In case of vanilla `Instant` we seem to need to translate "+0000 | +00:00 | +00 "
91
99
* timezone designator into plain "Z" for some reason; see
92
- * [datatype-jsr310#79 ] for more info
100
+ * [jackson-modules-java8#18 ] for more info
93
101
*
94
- * @since 2.7.5
102
+ * @since 2.9.0
95
103
*/
96
- protected final boolean replace0000AsZ ;
104
+ protected final boolean replaceZeroOffsetAsZ ;
97
105
98
106
/**
99
107
* Flag for <code>JsonFormat.Feature.ADJUST_DATES_TO_CONTEXT_TIME_ZONE</code>
@@ -108,14 +116,14 @@ protected InstantDeserializer(Class<T> supportedType,
108
116
Function <FromIntegerArguments , T > fromMilliseconds ,
109
117
Function <FromDecimalArguments , T > fromNanoseconds ,
110
118
BiFunction <T , ZoneId , T > adjust ,
111
- boolean replace0000AsZ )
119
+ boolean replaceZeroOffsetAsZ )
112
120
{
113
121
super (supportedType , formatter );
114
122
this .parsedToValue = parsedToValue ;
115
123
this .fromMilliseconds = fromMilliseconds ;
116
124
this .fromNanoseconds = fromNanoseconds ;
117
125
this .adjust = adjust == null ? ((d , z ) -> d ) : adjust ;
118
- this .replace0000AsZ = replace0000AsZ ;
126
+ this .replaceZeroOffsetAsZ = replaceZeroOffsetAsZ ;
119
127
_adjustToContextTZOverride = null ;
120
128
}
121
129
@@ -127,7 +135,7 @@ protected InstantDeserializer(InstantDeserializer<T> base, DateTimeFormatter f)
127
135
fromMilliseconds = base .fromMilliseconds ;
128
136
fromNanoseconds = base .fromNanoseconds ;
129
137
adjust = base .adjust ;
130
- replace0000AsZ = (_formatter == DateTimeFormatter .ISO_INSTANT );
138
+ replaceZeroOffsetAsZ = (_formatter == DateTimeFormatter .ISO_INSTANT );
131
139
_adjustToContextTZOverride = base ._adjustToContextTZOverride ;
132
140
}
133
141
@@ -139,7 +147,7 @@ protected InstantDeserializer(InstantDeserializer<T> base, Boolean adjustToConte
139
147
fromMilliseconds = base .fromMilliseconds ;
140
148
fromNanoseconds = base .fromNanoseconds ;
141
149
adjust = base .adjust ;
142
- replace0000AsZ = base .replace0000AsZ ;
150
+ replaceZeroOffsetAsZ = base .replaceZeroOffsetAsZ ;
143
151
_adjustToContextTZOverride = adjustToContextTimezoneOverride ;
144
152
}
145
153
@@ -189,13 +197,8 @@ public T deserialize(JsonParser parser, DeserializationContext context) throws I
189
197
// fall through to default handling, to get error there
190
198
}
191
199
}
192
- // 24-May-2016, tatu: as per [datatype-jsr310#79] seems like we need
193
- // some massaging in some cases...
194
- if (replace0000AsZ ) {
195
- if (string .endsWith ("+0000" )) {
196
- string = string .substring (0 , string .length () - 5 ) + "Z" ;
197
- }
198
- }
200
+
201
+ string = replaceZeroOffsetAsZIfNecessary (string );
199
202
}
200
203
201
204
T value ;
@@ -285,6 +288,15 @@ private ZoneId getZone(DeserializationContext context)
285
288
return (_valueClass == Instant .class ) ? null : context .getTimeZone ().toZoneId ();
286
289
}
287
290
291
+ private String replaceZeroOffsetAsZIfNecessary (String text )
292
+ {
293
+ if (replaceZeroOffsetAsZ ) {
294
+ return ISO8601_UTC_ZERO_OFFSET_SUFFIX_REGEX .matcher (text ).replaceFirst ("Z" );
295
+ }
296
+
297
+ return text ;
298
+ }
299
+
288
300
public static class FromIntegerArguments // since 2.8.3
289
301
{
290
302
public final long value ;
0 commit comments