Skip to content
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
@@ -1,8 +1,8 @@
package com.fasterxml.jackson.databind.util;

import java.util.*;
import java.text.ParsePosition;
import java.text.ParseException;
import java.text.ParsePosition;
import java.util.*;

/**
* Utilities methods for manipulating dates in iso8601 format. This is much much faster and GC friendly than using SimpleDateFormat so
Expand Down Expand Up @@ -200,9 +200,15 @@ public static Date parse(String date, ParsePosition pos) throws ParseException {
char c = date.charAt(offset);
if (c != 'Z' && c != '+' && c != '-') {
seconds = parseInt(date, offset, offset += 2);
if (seconds > 59 && seconds < 63) seconds = 59; // truncate up to 3 leap seconds
// milliseconds can be optional in the format
if (checkOffset(date, offset, '.')) {
milliseconds = parseInt(date, offset += 1, offset += 3);
offset += 1;
int endOffset = indexOfNonDigit(date, offset + 1); // assume at least one digit
int parseEndOffset = Math.min(endOffset, offset + 3); // parse up to 3 digits
int fraction = parseInt(date, offset, parseEndOffset);
milliseconds = (int) (Math.pow(10, 3 - (parseEndOffset - offset)) * fraction);
offset = endOffset;
}
}
}
Expand Down Expand Up @@ -340,4 +346,15 @@ private static void padInt(StringBuilder buffer, int value, int length) {
}
buffer.append(strValue);
}

/**
* Returns the index of the first character in the string that is not a digit, starting at offset.
*/
private static int indexOfNonDigit(String string, int offset) {
for (int i = offset; i < string.length(); i++) {
char c = string.charAt(i);
if (c < '0' || c > '9') return i;
}
return string.length();
}
}
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
package com.fasterxml.jackson.databind.util;

import com.fasterxml.jackson.databind.BaseMapTest;

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 java.util.concurrent.TimeUnit;

/**
* @see ISO8601Utils
Expand Down Expand Up @@ -126,4 +127,76 @@ public void testParseOptional() throws java.text.ParseException {
d = ISO8601Utils.parse("2007-08-13T21:51+02:00", new ParsePosition(0));
assertEquals(dateZeroSecondAndMillis, d);
}

public void testParseRfc3339Examples() throws java.text.ParseException {
// Two digit milliseconds.
Date d = ISO8601Utils.parse("1985-04-12T23:20:50.52Z", new ParsePosition(0));
assertEquals(newDate(1985, 4, 12, 23, 20, 50, 520, 0), d);

d = ISO8601Utils.parse("1996-12-19T16:39:57-08:00", new ParsePosition(0));
assertEquals(newDate(1996, 12, 19, 16, 39, 57, 0, -8 * 60), d);

// Truncated leap second.
d = ISO8601Utils.parse("1990-12-31T23:59:60Z", new ParsePosition(0));
assertEquals(newDate(1990, 12, 31, 23, 59, 59, 0, 0), d);

// Truncated leap second.
d = ISO8601Utils.parse("1990-12-31T15:59:60-08:00", new ParsePosition(0));
assertEquals(newDate(1990, 12, 31, 15, 59, 59, 0, -8 * 60), d);

// Two digit milliseconds.
d = ISO8601Utils.parse("1937-01-01T12:00:27.87+00:20", new ParsePosition(0));
assertEquals(newDate(1937, 1, 1, 12, 0, 27, 870, 20), d);
}

public void testFractionalSeconds() throws java.text.ParseException {
Date d = ISO8601Utils.parse("1970-01-01T00:00:00.9Z", new ParsePosition(0));
assertEquals(newDate(1970, 1, 1, 0, 0, 0, 900, 0), d);

d = ISO8601Utils.parse("1970-01-01T00:00:00.09Z", new ParsePosition(0));
assertEquals(newDate(1970, 1, 1, 0, 0, 0, 90, 0), d);

d = ISO8601Utils.parse("1970-01-01T00:00:00.009Z", new ParsePosition(0));
assertEquals(newDate(1970, 1, 1, 0, 0, 0, 9, 0), d);

d = ISO8601Utils.parse("1970-01-01T00:00:00.0009Z", new ParsePosition(0));
assertEquals(newDate(1970, 1, 1, 0, 0, 0, 0, 0), d);

d = ISO8601Utils.parse("1970-01-01T00:00:00.2147483647Z", new ParsePosition(0));
assertEquals(newDate(1970, 1, 1, 0, 0, 0, 214, 0), d);

d = ISO8601Utils.parse("1970-01-01T00:00:00.2147483648Z", new ParsePosition(0));
assertEquals(newDate(1970, 1, 1, 0, 0, 0, 214, 0), d);

d = ISO8601Utils.parse("1970-01-01T00:00:00.9+02:00", new ParsePosition(0));
assertEquals(newDate(1970, 1, 1, 0, 0, 0, 900, 2 * 60), d);

d = ISO8601Utils.parse("1970-01-01T00:00:00.09+02:00", new ParsePosition(0));
assertEquals(newDate(1970, 1, 1, 0, 0, 0, 90, 2 * 60), d);

d = ISO8601Utils.parse("1970-01-01T00:00:00.009+02:00", new ParsePosition(0));
assertEquals(newDate(1970, 1, 1, 0, 0, 0, 9, 2 * 60), d);

d = ISO8601Utils.parse("1970-01-01T00:00:00.0009+02:00", new ParsePosition(0));
assertEquals(newDate(1970, 1, 1, 0, 0, 0, 0, 2 * 60), d);

d = ISO8601Utils.parse("1970-01-01T00:00:00.2147483648+02:00", new ParsePosition(0));
assertEquals(newDate(1970, 1, 1, 0, 0, 0, 214, 2 * 60), d);
}

public void testDecimalWithoutDecimalPointButNoFractionalSeconds() throws java.text.ParseException {
try {
ISO8601Utils.parse("1970-01-01T00:00:00.Z", new ParsePosition(0));
fail();
} catch (ParseException expected) {
}
}

private Date newDate(int year, int month, int day, int hour,
int minute, int second, int millis, int timezoneOffsetMinutes) {
Calendar calendar = new GregorianCalendar(TimeZone.getTimeZone("GMT"));
calendar.set(year, month - 1, day, hour, minute, second);
calendar.set(Calendar.MILLISECOND, millis);
return new Date(calendar.getTimeInMillis() - TimeUnit.MINUTES.toMillis(timezoneOffsetMinutes));
}
}