Skip to content

Optimize InstantDeserializer addInColonToOffsetIfMissing() #336

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

Merged
merged 2 commits into from
Dec 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -519,14 +519,42 @@ private static String replaceZeroOffsetAsZ(String text)
}

// @since 2.13
private String addInColonToOffsetIfMissing(String text)
private static String addInColonToOffsetIfMissing(String text)
{
final Matcher matcher = ISO8601_COLONLESS_OFFSET_REGEX.matcher(text);
if (matcher.find()){
StringBuilder sb = new StringBuilder(matcher.group(0));
sb.insert(3, ":");
int timeIndex = text.indexOf('T');
if (timeIndex < 0 || timeIndex > text.length() - 1) {
return text;
}

int offsetIndex = text.indexOf('+', timeIndex + 1);
if (offsetIndex < 0) {
offsetIndex = text.indexOf('-', timeIndex + 1);
}

if (offsetIndex < 0 || offsetIndex > text.length() - 5) {
return text;
}

int colonIndex = text.indexOf(':', offsetIndex);
if (colonIndex == offsetIndex + 3) {
return text;
}

return matcher.replaceFirst(sb.toString());
if (Character.isDigit(text.charAt(offsetIndex + 1))
&& Character.isDigit(text.charAt(offsetIndex + 2))
&& Character.isDigit(text.charAt(offsetIndex + 3))
&& Character.isDigit(text.charAt(offsetIndex + 4))) {
String match = text.substring(offsetIndex, offsetIndex + 5);
return text.substring(0, offsetIndex)
+ match.substring(0, 3) + ':' + match.substring(3)
+ text.substring(offsetIndex + match.length());
}

// fallback to slow regex path, should be fully handled by the above
final Matcher matcher = ISO8601_COLONLESS_OFFSET_REGEX.matcher(text);
if (matcher.find()) {
String match = matcher.group(0);
return matcher.replaceFirst(match.substring(0, 3) + ':' + match.substring(3));
}
return text;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import java.time.temporal.ChronoField;
import java.time.temporal.ChronoUnit;
import java.time.temporal.Temporal;
import java.util.Arrays;
import java.util.Map;
import java.util.TimeZone;

Expand Down Expand Up @@ -811,6 +812,40 @@ public void testOffsetDateTimeMinOrMax() throws Exception
_testOffsetDateTimeMinOrMax(OffsetDateTime.MAX);
}

@Test
public void OffsetDateTime_with_offset_can_be_deserialized() throws Exception {
Comment on lines +815 to +816
Copy link
Member

@JooHyukKim JooHyukKim Dec 16, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems like we can merge this and one for zonedDateTime below into a separate test class like Xxx336Test.java for their purposes and similar style, but idk might be overkill for now.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, I can consolidate these into a separate test class

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In this case, I think keeping them along with existing test makes sense: this is not new functionality but optimizing (and adding test coverage). So let's not create per-issue test classes.

ObjectReader r = MAPPER.readerFor(OffsetDateTime.class)
.without(DeserializationFeature.ADJUST_DATES_TO_CONTEXT_TIME_ZONE);

String base = "2015-07-24T12:23:34.184";
for (String offset : Arrays.asList("+00", "-00")) {
String time = base + offset;
if (!System.getProperty("java.version").startsWith("1.8")) {
// JDK 8 cannot parse hour offsets without minutes
assertIsEqual(OffsetDateTime.parse("2015-07-24T12:23:34.184Z"), r.readValue('"' + time + '"'));
}
assertIsEqual(OffsetDateTime.parse("2015-07-24T12:23:34.184Z"), r.readValue('"' + time + "00" + '"'));
assertIsEqual(OffsetDateTime.parse("2015-07-24T12:23:34.184Z"), r.readValue('"' + time + ":00" + '"'));
assertIsEqual(OffsetDateTime.parse("2015-07-24T12:23:34.184" + offset + ":30"), r.readValue('"' + time + "30" + '"'));
assertIsEqual(OffsetDateTime.parse("2015-07-24T12:23:34.184" + offset + ":30"), r.readValue('"' + time + ":30" + '"'));
}

for (String prefix : Arrays.asList("-", "+")) {
for (String hours : Arrays.asList("00", "01", "02", "03", "11", "12")) {
String time = base + prefix + hours;
OffsetDateTime expectedHour = OffsetDateTime.parse(time + ":00");
if (!System.getProperty("java.version").startsWith("1.8")) {
// JDK 8 cannot parse hour offsets without minutes
assertIsEqual(expectedHour, r.readValue('"' + time + '"'));
}
assertIsEqual(expectedHour, r.readValue('"' + time + "00" + '"'));
assertIsEqual(expectedHour, r.readValue('"' + time + ":00" + '"'));
assertIsEqual(OffsetDateTime.parse(time + ":30"), r.readValue('"' + time + "30" + '"'));
assertIsEqual(OffsetDateTime.parse(time + ":30"), r.readValue('"' + time + ":30" + '"'));
}
}
}

private void _testOffsetDateTimeMinOrMax(OffsetDateTime offsetDateTime)
throws Exception
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,13 @@

import java.io.IOException;
import java.time.LocalDateTime;
import java.time.OffsetDateTime;
import java.time.ZoneId;
import java.time.ZoneOffset;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeParseException;
import java.util.Arrays;
import java.util.Map;
import java.util.TimeZone;

Expand Down Expand Up @@ -282,6 +284,39 @@ public void testDeserializationWithoutColonInTimeZoneWithTZDB() throws Throwable
wrapper.value);
}

@Test
public void ZonedDateTime_with_offset_can_be_deserialized() throws Exception {
ObjectReader r = newMapper().readerFor(ZonedDateTime.class)
.without(DeserializationFeature.ADJUST_DATES_TO_CONTEXT_TIME_ZONE);

String base = "2015-07-24T12:23:34.184";
for (String offset : Arrays.asList("+00", "-00")) {
String time = base + offset;
if (!System.getProperty("java.version").startsWith("1.8")) {
// JDK 8 cannot parse hour offsets without minutes
assertEquals(ZonedDateTime.parse("2015-07-24T12:23:34.184Z"), r.readValue('"' + time + '"'));
}
assertEquals(ZonedDateTime.parse("2015-07-24T12:23:34.184Z"), r.readValue('"' + time + "00" + '"'));
assertEquals(ZonedDateTime.parse("2015-07-24T12:23:34.184Z"), r.readValue('"' + time + ":00" + '"'));
assertEquals(ZonedDateTime.parse("2015-07-24T12:23:34.184" + offset + ":30" ), r.readValue('"' + time + "30" + '"'));
assertEquals(ZonedDateTime.parse("2015-07-24T12:23:34.184" + offset + ":30" ), r.readValue('"' + time + ":30" + '"'));
}

for (String prefix : Arrays.asList("-", "+")) {
for (String hours : Arrays.asList("00", "01", "02", "03", "11", "12")) {
String time = base + prefix + hours;
ZonedDateTime expectedHour = ZonedDateTime.parse(time + ":00");
if (!System.getProperty("java.version").startsWith("1.8")) {
// JDK 8 cannot parse hour offsets without minutes
assertEquals(expectedHour, r.readValue('"' + time + '"'));
}
assertEquals(expectedHour, r.readValue('"' + time + "00" + '"'));
assertEquals(expectedHour, r.readValue('"' + time + ":00" + '"'));
assertEquals(ZonedDateTime.parse(time + ":30"), r.readValue('"' + time + "30" + '"'));
assertEquals(ZonedDateTime.parse(time + ":30"), r.readValue('"' + time + ":30" + '"'));
}
}
}

private void expectFailure(String json) throws Throwable {
try {
Expand Down