diff options
author | Jerome Guibert <jerome.guibert@fullsix.com> | 2014-09-19 15:41:27 +0200 |
---|---|---|
committer | Jerome Guibert <jerome.guibert@fullsix.com> | 2014-09-19 15:41:27 +0200 |
commit | 3c2d40901088f38eca41896f5b7410729e85ef70 (patch) | |
tree | 4c0e928bbf11a762c2a9c483e7ebe3c50c5abb76 /src | |
parent | f95a92786724c90bb33ec30eee6990831617eb23 (diff) | |
download | jackson-databind-3c2d40901088f38eca41896f5b7410729e85ef70.tar.gz |
extends iso8601 format to support parse according expression: [yyyy-MM-dd|yyyyMMdd][T(hh:mm[:ss[.sss]]|hhmm[ss[.sss]])]?[Z|[+-]hh:mm]]
Diffstat (limited to 'src')
-rw-r--r-- | src/main/java/com/fasterxml/jackson/databind/util/ISO8601Utils.java | 139 | ||||
-rw-r--r-- | src/test/java/com/fasterxml/jackson/databind/util/ISO8601UtilsTest.java | 104 |
2 files changed, 176 insertions, 67 deletions
diff --git a/src/main/java/com/fasterxml/jackson/databind/util/ISO8601Utils.java b/src/main/java/com/fasterxml/jackson/databind/util/ISO8601Utils.java index 5fce78e02..7a87d8948 100644 --- a/src/main/java/com/fasterxml/jackson/databind/util/ISO8601Utils.java +++ b/src/main/java/com/fasterxml/jackson/databind/util/ISO8601Utils.java @@ -5,8 +5,12 @@ import java.text.ParsePosition; import java.text.ParseException; /** - * Utilities methods for manipulating dates in iso8601 format. This is much much faster and GC friendly than - * using SimpleDateFormat so highly suitable if you (un)serialize lots of date objects. + * Utilities methods for manipulating dates in iso8601 format. This is much much faster and GC friendly than using SimpleDateFormat so + * highly suitable if you (un)serialize lots of date objects. + * + * Supported parse format: [yyyy-MM-dd|yyyyMMdd][T(hh:mm[:ss[.sss]]|hhmm[ss[.sss]])]?[Z|[+-]hh:mm]] + * + * @see http://www.w3.org/TR/NOTE-datetime */ public class ISO8601Utils { @@ -21,25 +25,25 @@ public class ISO8601Utils { private static final TimeZone TIMEZONE_GMT = TimeZone.getTimeZone(GMT_ID); /* - /********************************************************** - /* Static factories - /********************************************************** + * /********************************************************** /* Static factories + * /********************************************************** */ - + /** * Accessor for static GMT timezone instance. */ - public static TimeZone timeZoneGMT() { return TIMEZONE_GMT; } + public static TimeZone timeZoneGMT() { + return TIMEZONE_GMT; + } /* - /********************************************************** - /* Formatting - /********************************************************** + * /********************************************************** /* Formatting + * /********************************************************** */ - + /** * Format a date into 'yyyy-MM-ddThh:mm:ssZ' (GMT timezone, no milliseconds precision) - * + * * @param date the date to format * @return the date formatted as 'yyyy-MM-ddThh:mm:ssZ' */ @@ -49,8 +53,8 @@ public class ISO8601Utils { /** * Format a date into 'yyyy-MM-ddThh:mm:ss[.sss]Z' (GMT timezone) - * - * @param date the date to format + * + * @param date the date to format * @param millis true to include millis precision otherwise false * @return the date formatted as 'yyyy-MM-ddThh:mm:ss[.sss]Z' */ @@ -60,10 +64,10 @@ public class ISO8601Utils { /** * Format date into yyyy-MM-ddThh:mm:ss[.sss][Z|[+-]hh:mm] - * - * @param date the date to format + * + * @param date the date to format * @param millis true to include millis precision otherwise false - * @param tz timezone to use for the formatting (GMT will produce 'Z') + * @param tz timezone to use for the formatting (GMT will produce 'Z') * @return the date formatted as yyyy-MM-ddThh:mm:ss[.sss][Z|[+-]hh:mm] */ public static String format(Date date, boolean millis, TimeZone tz) { @@ -108,54 +112,72 @@ public class ISO8601Utils { } /* - /********************************************************** - /* Parsing - /********************************************************** + * /********************************************************** /* Parsing + * /********************************************************** */ /** * Parse a date from ISO-8601 formatted string. It expects a format yyyy-MM-ddThh:mm:ss[.sss][Z|[+-]hh:mm] - * + * * @param date ISO string to parse in the appropriate format. * @param pos The position to start parsing from, updated to where parsing stopped. * @return the parsed date * @throws ParseException if the date is not in the appropriate format */ - public static Date parse(String date, ParsePosition pos) throws ParseException - { + public static Date parse(String date, ParsePosition pos) throws ParseException { Exception fail = null; try { int offset = pos.getIndex(); // extract year int year = parseInt(date, offset, offset += 4); - checkOffset(date, offset, '-'); + if (checkOffset(date, offset, '-')) { + offset += 1; + } // extract month - int month = parseInt(date, offset += 1, offset += 2); - checkOffset(date, offset, '-'); + int month = parseInt(date, offset, offset += 2); + if (checkOffset(date, offset, '-')) { + offset += 1; + } // extract day - int day = parseInt(date, offset += 1, offset += 2); - checkOffset(date, offset, 'T'); - - // extract hours, minutes, seconds and milliseconds - int hour = parseInt(date, offset += 1, offset += 2); - checkOffset(date, offset, ':'); + int day = parseInt(date, offset, offset += 2); + // default time value + int hour = 0; + int minutes = 0; + int seconds = 0; + int milliseconds = 0; // always use 0 otherwise returned date will include millis of current time + if (checkOffset(date, offset, 'T')) { - int minutes = parseInt(date, offset += 1, offset += 2); - checkOffset(date, offset, ':'); + // extract hours, minutes, seconds and milliseconds + hour = parseInt(date, offset += 1, offset += 2); + if (checkOffset(date, offset, ':')) { + offset += 1; + } - int seconds = parseInt(date, offset += 1, offset += 2); - // milliseconds can be optional in the format - int milliseconds = 0; // always use 0 otherwise returned date will include millis of current time - if (date.charAt(offset) == '.') { - checkOffset(date, offset, '.'); - milliseconds = parseInt(date, offset += 1, offset += 3); + minutes = parseInt(date, offset, offset += 2); + if (checkOffset(date, offset, ':')) { + offset += 1; + } + // second and milliseconds can be optional + if (date.length() > offset) { + char c = date.charAt(offset); + if (c != 'Z' && c != '+' && c != '-') { + seconds = parseInt(date, offset, offset += 2); + // milliseconds can be optional in the format + if (checkOffset(date, offset, '.')) { + milliseconds = parseInt(date, offset += 1, offset += 3); + } + } + } } // extract timezone String timezoneId; + if (date.length() <= offset) { + throw new IndexOutOfBoundsException("No time zone indicator "); + } char timezoneIndicator = date.charAt(offset); if (timezoneIndicator == '+' || timezoneIndicator == '-') { String timezoneOffset = date.substring(offset); @@ -185,8 +207,8 @@ public class ISO8601Utils { pos.setIndex(offset); return calendar.getTime(); - //If we get a ParseException it'll already have the right message/offset. - //Other exception types can convert here. + // If we get a ParseException it'll already have the right message/offset. + // Other exception types can convert here. } catch (IndexOutOfBoundsException e) { fail = e; } catch (NumberFormatException e) { @@ -194,32 +216,28 @@ public class ISO8601Utils { } catch (IllegalArgumentException e) { fail = e; } - String input = (date == null) ? null : ('"'+date+"'"); - throw new ParseException("Failed to parse date ["+input - +"]: "+fail.getMessage(), pos.getIndex()); + String input = (date == null) ? null : ('"' + date + "'"); + throw new ParseException("Failed to parse date [" + input + "]: " + fail.getMessage(), pos.getIndex()); } /** - * Check if the expected character exist at the given offset of the - * - * @param value the string to check at the specified offset - * @param offset the offset to look for the expected character + * Check if the expected character exist at the given offset in the value. + * + * @param value the string to check at the specified offset + * @param offset the offset to look for the expected character * @param expected the expected character - * @throws IndexOutOfBoundsException if the expected character is not found + * @return true if the expected character exist at the given offset */ - private static void checkOffset(String value, int offset, char expected) throws ParseException { - char found = value.charAt(offset); - if (found != expected) { - throw new ParseException("Expected '" + expected + "' character but found '" + found + "'", offset); - } + private static boolean checkOffset(String value, int offset, char expected) { + return value.length() > offset ? (value.charAt(offset) == expected) : false; } /** * Parse an integer located between 2 given offsets in a string - * - * @param value the string to parse + * + * @param value the string to parse * @param beginIndex the start index for the integer in the string - * @param endIndex the end index for the integer in the string + * @param endIndex the end index for the integer in the string * @return the int * @throws NumberFormatException if the value is not a number */ @@ -251,9 +269,9 @@ public class ISO8601Utils { /** * Zero pad a number to a specified length - * + * * @param buffer buffer to use for padding - * @param value the integer value to pad if necessary. + * @param value the integer value to pad if necessary. * @param length the length of the string we should zero pad */ private static void padInt(StringBuilder buffer, int value, int length) { @@ -264,4 +282,3 @@ public class ISO8601Utils { buffer.append(strValue); } } - diff --git a/src/test/java/com/fasterxml/jackson/databind/util/ISO8601UtilsTest.java b/src/test/java/com/fasterxml/jackson/databind/util/ISO8601UtilsTest.java index 21b9046d3..9c97807fc 100644 --- a/src/test/java/com/fasterxml/jackson/databind/util/ISO8601UtilsTest.java +++ b/src/test/java/com/fasterxml/jackson/databind/util/ISO8601UtilsTest.java @@ -1,28 +1,39 @@ package com.fasterxml.jackson.databind.util; -import java.util.*; +import java.text.ParseException; import java.text.ParsePosition; +import java.util.Calendar; +import java.util.Date; +import java.util.GregorianCalendar; +import java.util.TimeZone; import com.fasterxml.jackson.databind.BaseMapTest; -import com.fasterxml.jackson.databind.util.ISO8601Utils; /** * @see ISO8601Utils */ -public class ISO8601UtilsTest extends BaseMapTest -{ +public class ISO8601UtilsTest extends BaseMapTest { private Date date; + private Date dateWithoutTime; private Date dateZeroMillis; + private Date dateZeroSecondAndMillis; @Override - public void setUp() - { + public void setUp() { Calendar cal = new GregorianCalendar(2007, 8 - 1, 13, 19, 51, 23); cal.setTimeZone(TimeZone.getTimeZone("GMT")); cal.set(Calendar.MILLISECOND, 789); date = cal.getTime(); cal.set(Calendar.MILLISECOND, 0); dateZeroMillis = cal.getTime(); + cal.set(Calendar.SECOND, 0); + dateZeroSecondAndMillis = cal.getTime(); + + cal = new GregorianCalendar(2007, 8 - 1, 13, 0, 0, 0); + cal.set(Calendar.MILLISECOND, 0); + cal.setTimeZone(TimeZone.getTimeZone("GMT")); + dateWithoutTime = cal.getTime(); + } public void testFormat() { @@ -58,4 +69,85 @@ public class ISO8601UtilsTest extends BaseMapTest assertEquals(date, d); } + public void testParseShortDate() throws java.text.ParseException { + Date d = ISO8601Utils.parse("20070813T19:51:23.789Z", new ParsePosition(0)); + assertEquals(date, d); + + d = ISO8601Utils.parse("20070813T19:51:23Z", new ParsePosition(0)); + assertEquals(dateZeroMillis, d); + + d = ISO8601Utils.parse("20070813T21:51:23.789+02:00", new ParsePosition(0)); + assertEquals(date, d); + } + + public void testParseShortTime() throws java.text.ParseException { + Date d = ISO8601Utils.parse("2007-08-13T195123.789Z", new ParsePosition(0)); + assertEquals(date, d); + + d = ISO8601Utils.parse("2007-08-13T195123Z", new ParsePosition(0)); + assertEquals(dateZeroMillis, d); + + d = ISO8601Utils.parse("2007-08-13T215123.789+02:00", new ParsePosition(0)); + assertEquals(date, d); + } + + public void testParseShortDateTime() throws java.text.ParseException { + Date d = ISO8601Utils.parse("20070813T195123.789Z", new ParsePosition(0)); + assertEquals(date, d); + + d = ISO8601Utils.parse("20070813T195123Z", new ParsePosition(0)); + assertEquals(dateZeroMillis, d); + + d = ISO8601Utils.parse("20070813T215123.789+02:00", new ParsePosition(0)); + assertEquals(date, d); + } + + public void testParseWithoutTime() throws ParseException { + Date d = ISO8601Utils.parse("2007-08-13Z", new ParsePosition(0)); + assertEquals(dateWithoutTime, d); + + d = ISO8601Utils.parse("20070813Z", new ParsePosition(0)); + assertEquals(dateWithoutTime, d); + + d = ISO8601Utils.parse("2007-08-13+00:00", new ParsePosition(0)); + assertEquals(dateWithoutTime, d); + + d = ISO8601Utils.parse("20070813+00:00", new ParsePosition(0)); + assertEquals(dateWithoutTime, d); + } + + public void testParseWithoutTimeAndTimeZoneMustFail() { + try { + ISO8601Utils.parse("2007-08-13", new ParsePosition(0)); + fail(); + } catch (ParseException p) { + } + try { + ISO8601Utils.parse("20070813", new ParsePosition(0)); + fail(); + } catch (ParseException p) { + } + try { + ISO8601Utils.parse("2007-08-13", new ParsePosition(0)); + fail(); + } catch (ParseException p) { + } + try { + ISO8601Utils.parse("20070813", new ParsePosition(0)); + fail(); + } catch (ParseException p) { + } + } + + + public void testParseOptional() throws java.text.ParseException { + Date d = ISO8601Utils.parse("2007-08-13T19:51Z", new ParsePosition(0)); + assertEquals(dateZeroSecondAndMillis, d); + + d = ISO8601Utils.parse("2007-08-13T1951Z", new ParsePosition(0)); + assertEquals(dateZeroSecondAndMillis, d); + + d = ISO8601Utils.parse("2007-08-13T21:51+02:00", new ParsePosition(0)); + assertEquals(dateZeroSecondAndMillis, d); + } } |