From 5e799a2e2265cbb6a056b37b50f82cc1eebf1d45 Mon Sep 17 00:00:00 2001 From: Tatu Saloranta Date: Thu, 2 Oct 2014 23:16:26 -0700 Subject: [PATCH] Fix #570 --- release-notes/VERSION | 2 + .../jackson/databind/util/StdDateFormat.java | 44 ++++++++++---- .../deser/TestDateDeserialization.java | 57 ++++++++++++++++++- 3 files changed, 90 insertions(+), 13 deletions(-) diff --git a/release-notes/VERSION b/release-notes/VERSION index c9f1da2a73..d14426ee8e 100644 --- a/release-notes/VERSION +++ b/release-notes/VERSION @@ -12,6 +12,8 @@ Version: 2.4.3 (02-Oct-2014) #541: @JsonProperty in @JsonCreator is conflicting with POJOs getters/attributes (reported by fabienrenaud@github) #543: Problem resolving self-referential generic types +#570: Add Support for Parsing All Compliant ISO-8601 Date Formats + (requested by pfconrey@github) - Fixed a problem with `acceptJsonFormatVisitor` with Collection/array types that are marked with `@JsonValue`; could cause NPE in JSON Schema generator module. diff --git a/src/main/java/com/fasterxml/jackson/databind/util/StdDateFormat.java b/src/main/java/com/fasterxml/jackson/databind/util/StdDateFormat.java index 5cc78e28ff..6c9d6b3cb9 100644 --- a/src/main/java/com/fasterxml/jackson/databind/util/StdDateFormat.java +++ b/src/main/java/com/fasterxml/jackson/databind/util/StdDateFormat.java @@ -23,6 +23,8 @@ public class StdDateFormat * JDK date parsing is awfully brittle, and ISO-8601 is quite * permissive. The two don't mix, need to write a better one. */ + // 02-Oct-2014, tatu: Alas. While spit'n'polished a few times, still + // not really robust /** * Defines a commonly used date format that conforms @@ -398,31 +400,49 @@ protected Date parseAsISO8601(String dateStr, ParsePosition pos) // let's just append '00' dateStr += "00"; } - // [JACKSON-334]: may be missing milliseconds... if so, add + // Milliseconds partial or missing; and even seconds are optional len = dateStr.length(); - // '+0000' (5 chars); should come after '.000' (4 chars) of milliseconds, so: - c = dateStr.charAt(len-9); - if (Character.isDigit(c)) { + // remove 'T', '+'/'-' and 4-digit timezone-offset + int timeLen = len - dateStr.lastIndexOf('T') - 6; + if (timeLen < 12) { // 8 for hh:mm:ss, 4 for .sss + int offset = len - 5; // insertion offset, before tz-offset StringBuilder sb = new StringBuilder(dateStr); - sb.insert(len-5, ".000"); + switch (timeLen) { + case 11: + sb.insert(offset, '0'); break; + case 10: + sb.insert(offset, "00"); break; + case 9: // is this legal? (just second fraction marker) + sb.insert(offset, "000"); break; + case 8: + sb.insert(offset, ".000"); break; + case 7: // not legal to have single-digit second + break; + case 6: // probably not legal, but let's allow + sb.insert(offset, "00.000"); + case 5: // is legal to omit seconds + sb.insert(offset, ":00.000"); + } dateStr = sb.toString(); } - df = _formatISO8601; if (_formatISO8601 == null) { df = _formatISO8601 = _cloneFormat(DATE_FORMAT_ISO8601, DATE_FORMAT_STR_ISO8601, _timezone, _locale); } } else { - /* 24-Nov-2009, tatu: Ugh. This is getting pretty - * ugly. Need to rewrite! - */ - // If not, plain date. Easiest to just patch 'Z' in the end? StringBuilder sb = new StringBuilder(dateStr); // And possible also millisecond part if missing int timeLen = len - dateStr.lastIndexOf('T') - 1; - if (timeLen <= 8) { - sb.append(".000"); + if (timeLen < 12) { // missing, or partial + switch (timeLen) { + case 11: sb.append('0'); + case 10: sb.append('0'); + case 9: sb.append('0'); + break; + default: + sb.append(".000"); + } } sb.append('Z'); dateStr = sb.toString(); diff --git a/src/test/java/com/fasterxml/jackson/databind/deser/TestDateDeserialization.java b/src/test/java/com/fasterxml/jackson/databind/deser/TestDateDeserialization.java index 61ea444d96..caf6416c8d 100644 --- a/src/test/java/com/fasterxml/jackson/databind/deser/TestDateDeserialization.java +++ b/src/test/java/com/fasterxml/jackson/databind/deser/TestDateDeserialization.java @@ -145,6 +145,61 @@ public void testDateUtilISO8601() throws Exception assertEquals(30, c.get(Calendar.DAY_OF_MONTH)); } + // [Databind#570] + public void testISO8601PartialMilliseconds() throws Exception + { + String inputStr; + Date inputDate; + Calendar c = Calendar.getInstance(TimeZone.getTimeZone("GMT")); + + inputStr = "2014-10-03T18:00:00.6-05:00"; + inputDate = MAPPER.readValue(quote(inputStr), java.util.Date.class); + c.setTime(inputDate); + assertEquals(2014, c.get(Calendar.YEAR)); + assertEquals(Calendar.OCTOBER, c.get(Calendar.MONTH)); + assertEquals(3, c.get(Calendar.DAY_OF_MONTH)); + assertEquals(600, c.get(Calendar.MILLISECOND)); + + inputStr = "2014-10-03T18:00:00.61-05:00"; + inputDate = MAPPER.readValue(quote(inputStr), java.util.Date.class); + c.setTime(inputDate); + assertEquals(2014, c.get(Calendar.YEAR)); + assertEquals(Calendar.OCTOBER, c.get(Calendar.MONTH)); + assertEquals(3, c.get(Calendar.DAY_OF_MONTH)); + assertEquals(18 + 5, c.get(Calendar.HOUR_OF_DAY)); + assertEquals(0, c.get(Calendar.MINUTE)); + assertEquals(0, c.get(Calendar.SECOND)); + assertEquals(610, c.get(Calendar.MILLISECOND)); + + inputStr = "1997-07-16T19:20:30.45+01:00"; + inputDate = MAPPER.readValue(quote(inputStr), java.util.Date.class); + c.setTime(inputDate); + assertEquals(1997, c.get(Calendar.YEAR)); + assertEquals(Calendar.JULY, c.get(Calendar.MONTH)); + assertEquals(16, c.get(Calendar.DAY_OF_MONTH)); + assertEquals(19 - 1, c.get(Calendar.HOUR_OF_DAY)); + assertEquals(20, c.get(Calendar.MINUTE)); + assertEquals(30, c.get(Calendar.SECOND)); + assertEquals(450, c.get(Calendar.MILLISECOND)); + } + + public void testISO8601MissingSeconds() throws Exception + { + String inputStr; + Date inputDate; + Calendar c = Calendar.getInstance(TimeZone.getTimeZone("GMT")); + + inputStr = "1997-07-16T19:20+01:00"; + inputDate = MAPPER.readValue(quote(inputStr), java.util.Date.class); + c.setTime(inputDate); + assertEquals(1997, c.get(Calendar.YEAR)); + assertEquals(Calendar.JULY, c.get(Calendar.MONTH)); + assertEquals(16, c.get(Calendar.DAY_OF_MONTH)); + assertEquals(19 - 1, c.get(Calendar.HOUR_OF_DAY)); + assertEquals(0, c.get(Calendar.SECOND)); + assertEquals(0, c.get(Calendar.MILLISECOND)); +} + public void testDateUtilISO8601NoTimezone() throws Exception { // Timezone itself is optional as well... @@ -180,7 +235,7 @@ public void testDateUtilISO8601NoMilliseconds() throws Exception assertEquals(0, c.get(Calendar.MILLISECOND)); // 03-Nov-2013, tatu: This wouldn't work, and is the nominal reason - // for #338 I thinl + // for #338 I think /* inputDate = ISO8601Utils.parse(INPUT_STR); c = Calendar.getInstance(TimeZone.getTimeZone("GMT"));