Skip to content

Commit 29aa2b8

Browse files
authored
Optimize InstantDeserializer addInColonToOffsetIfMissing (#336)
1 parent fa6a27a commit 29aa2b8

File tree

3 files changed

+104
-6
lines changed

3 files changed

+104
-6
lines changed

datetime/src/main/java/com/fasterxml/jackson/datatype/jsr310/deser/InstantDeserializer.java

+34-6
Original file line numberDiff line numberDiff line change
@@ -519,14 +519,42 @@ private static String replaceZeroOffsetAsZ(String text)
519519
}
520520

521521
// @since 2.13
522-
private String addInColonToOffsetIfMissing(String text)
522+
private static String addInColonToOffsetIfMissing(String text)
523523
{
524-
final Matcher matcher = ISO8601_COLONLESS_OFFSET_REGEX.matcher(text);
525-
if (matcher.find()){
526-
StringBuilder sb = new StringBuilder(matcher.group(0));
527-
sb.insert(3, ":");
524+
int timeIndex = text.indexOf('T');
525+
if (timeIndex < 0 || timeIndex > text.length() - 1) {
526+
return text;
527+
}
528+
529+
int offsetIndex = text.indexOf('+', timeIndex + 1);
530+
if (offsetIndex < 0) {
531+
offsetIndex = text.indexOf('-', timeIndex + 1);
532+
}
533+
534+
if (offsetIndex < 0 || offsetIndex > text.length() - 5) {
535+
return text;
536+
}
537+
538+
int colonIndex = text.indexOf(':', offsetIndex);
539+
if (colonIndex == offsetIndex + 3) {
540+
return text;
541+
}
528542

529-
return matcher.replaceFirst(sb.toString());
543+
if (Character.isDigit(text.charAt(offsetIndex + 1))
544+
&& Character.isDigit(text.charAt(offsetIndex + 2))
545+
&& Character.isDigit(text.charAt(offsetIndex + 3))
546+
&& Character.isDigit(text.charAt(offsetIndex + 4))) {
547+
String match = text.substring(offsetIndex, offsetIndex + 5);
548+
return text.substring(0, offsetIndex)
549+
+ match.substring(0, 3) + ':' + match.substring(3)
550+
+ text.substring(offsetIndex + match.length());
551+
}
552+
553+
// fallback to slow regex path, should be fully handled by the above
554+
final Matcher matcher = ISO8601_COLONLESS_OFFSET_REGEX.matcher(text);
555+
if (matcher.find()) {
556+
String match = matcher.group(0);
557+
return matcher.replaceFirst(match.substring(0, 3) + ':' + match.substring(3));
530558
}
531559
return text;
532560
}

datetime/src/test/java/com/fasterxml/jackson/datatype/jsr310/deser/OffsetDateTimeDeserTest.java

+35
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import java.time.temporal.ChronoField;
66
import java.time.temporal.ChronoUnit;
77
import java.time.temporal.Temporal;
8+
import java.util.Arrays;
89
import java.util.Map;
910
import java.util.TimeZone;
1011

@@ -811,6 +812,40 @@ public void testOffsetDateTimeMinOrMax() throws Exception
811812
_testOffsetDateTimeMinOrMax(OffsetDateTime.MAX);
812813
}
813814

815+
@Test
816+
public void OffsetDateTime_with_offset_can_be_deserialized() throws Exception {
817+
ObjectReader r = MAPPER.readerFor(OffsetDateTime.class)
818+
.without(DeserializationFeature.ADJUST_DATES_TO_CONTEXT_TIME_ZONE);
819+
820+
String base = "2015-07-24T12:23:34.184";
821+
for (String offset : Arrays.asList("+00", "-00")) {
822+
String time = base + offset;
823+
if (!System.getProperty("java.version").startsWith("1.8")) {
824+
// JDK 8 cannot parse hour offsets without minutes
825+
assertIsEqual(OffsetDateTime.parse("2015-07-24T12:23:34.184Z"), r.readValue('"' + time + '"'));
826+
}
827+
assertIsEqual(OffsetDateTime.parse("2015-07-24T12:23:34.184Z"), r.readValue('"' + time + "00" + '"'));
828+
assertIsEqual(OffsetDateTime.parse("2015-07-24T12:23:34.184Z"), r.readValue('"' + time + ":00" + '"'));
829+
assertIsEqual(OffsetDateTime.parse("2015-07-24T12:23:34.184" + offset + ":30"), r.readValue('"' + time + "30" + '"'));
830+
assertIsEqual(OffsetDateTime.parse("2015-07-24T12:23:34.184" + offset + ":30"), r.readValue('"' + time + ":30" + '"'));
831+
}
832+
833+
for (String prefix : Arrays.asList("-", "+")) {
834+
for (String hours : Arrays.asList("00", "01", "02", "03", "11", "12")) {
835+
String time = base + prefix + hours;
836+
OffsetDateTime expectedHour = OffsetDateTime.parse(time + ":00");
837+
if (!System.getProperty("java.version").startsWith("1.8")) {
838+
// JDK 8 cannot parse hour offsets without minutes
839+
assertIsEqual(expectedHour, r.readValue('"' + time + '"'));
840+
}
841+
assertIsEqual(expectedHour, r.readValue('"' + time + "00" + '"'));
842+
assertIsEqual(expectedHour, r.readValue('"' + time + ":00" + '"'));
843+
assertIsEqual(OffsetDateTime.parse(time + ":30"), r.readValue('"' + time + "30" + '"'));
844+
assertIsEqual(OffsetDateTime.parse(time + ":30"), r.readValue('"' + time + ":30" + '"'));
845+
}
846+
}
847+
}
848+
814849
private void _testOffsetDateTimeMinOrMax(OffsetDateTime offsetDateTime)
815850
throws Exception
816851
{

datetime/src/test/java/com/fasterxml/jackson/datatype/jsr310/deser/ZonedDateTimeDeserTest.java

+35
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,13 @@
1818

1919
import java.io.IOException;
2020
import java.time.LocalDateTime;
21+
import java.time.OffsetDateTime;
2122
import java.time.ZoneId;
2223
import java.time.ZoneOffset;
2324
import java.time.ZonedDateTime;
2425
import java.time.format.DateTimeFormatter;
2526
import java.time.format.DateTimeParseException;
27+
import java.util.Arrays;
2628
import java.util.Map;
2729
import java.util.TimeZone;
2830

@@ -282,6 +284,39 @@ public void testDeserializationWithoutColonInTimeZoneWithTZDB() throws Throwable
282284
wrapper.value);
283285
}
284286

287+
@Test
288+
public void ZonedDateTime_with_offset_can_be_deserialized() throws Exception {
289+
ObjectReader r = newMapper().readerFor(ZonedDateTime.class)
290+
.without(DeserializationFeature.ADJUST_DATES_TO_CONTEXT_TIME_ZONE);
291+
292+
String base = "2015-07-24T12:23:34.184";
293+
for (String offset : Arrays.asList("+00", "-00")) {
294+
String time = base + offset;
295+
if (!System.getProperty("java.version").startsWith("1.8")) {
296+
// JDK 8 cannot parse hour offsets without minutes
297+
assertEquals(ZonedDateTime.parse("2015-07-24T12:23:34.184Z"), r.readValue('"' + time + '"'));
298+
}
299+
assertEquals(ZonedDateTime.parse("2015-07-24T12:23:34.184Z"), r.readValue('"' + time + "00" + '"'));
300+
assertEquals(ZonedDateTime.parse("2015-07-24T12:23:34.184Z"), r.readValue('"' + time + ":00" + '"'));
301+
assertEquals(ZonedDateTime.parse("2015-07-24T12:23:34.184" + offset + ":30" ), r.readValue('"' + time + "30" + '"'));
302+
assertEquals(ZonedDateTime.parse("2015-07-24T12:23:34.184" + offset + ":30" ), r.readValue('"' + time + ":30" + '"'));
303+
}
304+
305+
for (String prefix : Arrays.asList("-", "+")) {
306+
for (String hours : Arrays.asList("00", "01", "02", "03", "11", "12")) {
307+
String time = base + prefix + hours;
308+
ZonedDateTime expectedHour = ZonedDateTime.parse(time + ":00");
309+
if (!System.getProperty("java.version").startsWith("1.8")) {
310+
// JDK 8 cannot parse hour offsets without minutes
311+
assertEquals(expectedHour, r.readValue('"' + time + '"'));
312+
}
313+
assertEquals(expectedHour, r.readValue('"' + time + "00" + '"'));
314+
assertEquals(expectedHour, r.readValue('"' + time + ":00" + '"'));
315+
assertEquals(ZonedDateTime.parse(time + ":30"), r.readValue('"' + time + "30" + '"'));
316+
assertEquals(ZonedDateTime.parse(time + ":30"), r.readValue('"' + time + ":30" + '"'));
317+
}
318+
}
319+
}
285320

286321
private void expectFailure(String json) throws Throwable {
287322
try {

0 commit comments

Comments
 (0)