aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorJerome Guibert <jerome.guibert@fullsix.com>2014-09-19 15:41:27 +0200
committerJerome Guibert <jerome.guibert@fullsix.com>2014-09-19 15:41:27 +0200
commit3c2d40901088f38eca41896f5b7410729e85ef70 (patch)
tree4c0e928bbf11a762c2a9c483e7ebe3c50c5abb76 /src
parentf95a92786724c90bb33ec30eee6990831617eb23 (diff)
downloadjackson-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.java139
-rw-r--r--src/test/java/com/fasterxml/jackson/databind/util/ISO8601UtilsTest.java104
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);
+ }
}