Skip to content

Commit

Permalink
Bit more work on #1678, serialization side
Browse files Browse the repository at this point in the history
  • Loading branch information
cowtowncoder committed Jun 25, 2017
1 parent 472e03b commit 37b780b
Show file tree
Hide file tree
Showing 8 changed files with 124 additions and 136 deletions.
2 changes: 1 addition & 1 deletion release-notes/VERSION
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ Project: jackson-databind
#1653: Convenience overload(s) for ObjectMapper#registerSubtypes
#1655: `@JsonAnyGetter` uses different `bean` parameter in `SimpleBeanPropertyFilter`
(reported by georgeflugq@github)
#1678: Rewrite `StdDateFormat` ISO-8601 deserialization functionality
#1678: Rewrite `StdDateFormat` ISO-8601 handling functionality

2.8.9.1 (not yet released)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
*
* @see ISO8601Utils
*/
@Deprecated // since 2.9
public class ISO8601DateFormat extends DateFormat
{
private static final long serialVersionUID = 1L;
Expand Down
120 changes: 31 additions & 89 deletions src/main/java/com/fasterxml/jackson/databind/util/ISO8601Utils.java
Original file line number Diff line number Diff line change
Expand Up @@ -12,54 +12,16 @@
*
* @see <a href="http://www.w3.org/TR/NOTE-datetime">this specification</a>
*/
@Deprecated // since 2.9
public class ISO8601Utils
{
@Deprecated // since 2.7
private static final String GMT_ID = "GMT";

/**
* ID to represent the 'UTC' string, default timezone since Jackson 2.7
*
* @since 2.7
*/
private static final String UTC_ID = "UTC";

/**
* The GMT timezone, prefetched to avoid more lookups.
*
* @deprecated Since 2.7 use {@link #TIMEZONE_UTC} instead
*/
@Deprecated
private static final TimeZone TIMEZONE_GMT = TimeZone.getTimeZone(GMT_ID);

/**
* The UTC timezone, prefetched to avoid more lookups.
*
* @since 2.7
*/
private static final TimeZone TIMEZONE_UTC = TimeZone.getTimeZone(UTC_ID);
protected final static int DEF_8601_LEN = "yyyy-MM-ddThh:mm:ss.SSS+00:00".length();

/**
* Timezone we use for 'Z' in ISO-8601 date/time values: since 2.7
* {@link #TIMEZONE_UTC}; with earlier versions up to 2.7 was {@link #TIMEZONE_GMT}.
*/
private static final TimeZone TIMEZONE_Z = TIMEZONE_UTC;

/*
/**********************************************************
/* Static factories
/**********************************************************
*/

/**
* Accessor for static GMT timezone instance.
*
* @deprecated since 2.6
*/
@Deprecated // since 2.6
public static TimeZone timeZoneGMT() {
return TIMEZONE_GMT;
}
private static final TimeZone TIMEZONE_Z = TimeZone.getTimeZone("UTC");

/*
/**********************************************************
Expand All @@ -74,7 +36,7 @@ public static TimeZone timeZoneGMT() {
* @return the date formatted as 'yyyy-MM-ddThh:mm:ssZ'
*/
public static String format(Date date) {
return format(date, false, TIMEZONE_UTC);
return format(date, false, TIMEZONE_Z);
}

/**
Expand All @@ -85,7 +47,12 @@ public static String format(Date date) {
* @return the date formatted as 'yyyy-MM-ddThh:mm:ss[.sss]Z'
*/
public static String format(Date date, boolean millis) {
return format(date, millis, TIMEZONE_UTC);
return format(date, millis, TIMEZONE_Z);
}

@Deprecated // since 2.9
public static String format(Date date, boolean millis, TimeZone tz) {
return format(date, millis, tz, Locale.US);
}

/**
Expand All @@ -95,45 +62,39 @@ public static String format(Date date, boolean millis) {
* @param millis true to include millis precision otherwise false
* @param tz timezone to use for the formatting (UTC will produce 'Z')
* @return the date formatted as yyyy-MM-ddThh:mm:ss[.sss][Z|[+-]hh:mm]
*
* @since 2.9
*/
public static String format(Date date, boolean millis, TimeZone tz) {
Calendar calendar = new GregorianCalendar(tz, Locale.US);
public static String format(Date date, boolean millis, TimeZone tz, Locale loc) {
Calendar calendar = new GregorianCalendar(tz, loc);
calendar.setTime(date);

// estimate capacity of buffer as close as we can (yeah, that's pedantic ;)
int capacity = "yyyy-MM-ddThh:mm:ss".length();
capacity += millis ? ".sss".length() : 0;
capacity += tz.getRawOffset() == 0 ? "Z".length() : "+hh:mm".length();
StringBuilder formatted = new StringBuilder(capacity);

padInt(formatted, calendar.get(Calendar.YEAR), "yyyy".length());
formatted.append('-');
padInt(formatted, calendar.get(Calendar.MONTH) + 1, "MM".length());
formatted.append('-');
padInt(formatted, calendar.get(Calendar.DAY_OF_MONTH), "dd".length());
formatted.append('T');
padInt(formatted, calendar.get(Calendar.HOUR_OF_DAY), "hh".length());
formatted.append(':');
padInt(formatted, calendar.get(Calendar.MINUTE), "mm".length());
formatted.append(':');
padInt(formatted, calendar.get(Calendar.SECOND), "ss".length());
StringBuilder sb = new StringBuilder(30);
sb.append(String.format(
"%04d-%02d-%02dT%02d:%02d:%02d",
calendar.get(Calendar.YEAR),
calendar.get(Calendar.MONTH) + 1,
calendar.get(Calendar.DAY_OF_MONTH),
calendar.get(Calendar.HOUR_OF_DAY),
calendar.get(Calendar.MINUTE),
calendar.get(Calendar.SECOND)
));
if (millis) {
formatted.append('.');
padInt(formatted, calendar.get(Calendar.MILLISECOND), "sss".length());
sb.append(String.format(".%03d", calendar.get(Calendar.MILLISECOND)));
}

int offset = tz.getOffset(calendar.getTimeInMillis());
if (offset != 0) {
int hours = Math.abs((offset / (60 * 1000)) / 60);
int minutes = Math.abs((offset / (60 * 1000)) % 60);
formatted.append(offset < 0 ? '-' : '+');
padInt(formatted, hours, "hh".length());
formatted.append(':');
padInt(formatted, minutes, "mm".length());
sb.append(String.format("%c%02d:%02d",
(offset < 0 ? '-' : '+'),
hours, minutes));
} else {
formatted.append('Z');
sb.append('Z');
}
return formatted.toString();
return sb.toString();
}

/*
Expand Down Expand Up @@ -286,11 +247,7 @@ public static Date parse(String date, ParsePosition pos) throws ParseException {
return calendar.getTime();
// 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) {
fail = e;
} catch (IllegalArgumentException e) {
} catch (Exception e) {
fail = e;
}
String input = (date == null) ? null : ('"' + date + '"');
Expand Down Expand Up @@ -350,21 +307,6 @@ private static int parseInt(String value, int beginIndex, int endIndex) throws N
return -result;
}

/**
* 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 length the length of the string we should zero pad
*/
private static void padInt(StringBuilder buffer, int value, int length) {
String strValue = Integer.toString(value);
for (int i = length - strValue.length(); i > 0; i--) {
buffer.append('0');
}
buffer.append(strValue);
}

/**
* Returns the index of the first character in the string that is not a digit, starting at offset.
*/
Expand Down
106 changes: 85 additions & 21 deletions src/main/java/com/fasterxml/jackson/databind/util/StdDateFormat.java
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@ public class StdDateFormat
* for easier enforcing of specific rules. Heavy lifting done by Calendar,
* anyway.
*/

protected final static String PATTERN_PLAIN_STR = "\\d\\d\\d\\d[-]\\d\\d[-]\\d\\d";

protected final static Pattern PATTERN_PLAIN = Pattern.compile(PATTERN_PLAIN_STR);
Expand Down Expand Up @@ -95,10 +94,8 @@ public class StdDateFormat
* actual instances more cheaply (avoids re-parsing).
*/
static {
/* Another important thing: let's force use of default timezone for
* baseline DataFormat objects
*/

// Another important thing: let's force use of default timezone for
// baseline DataFormat objects
DATE_FORMAT_RFC1123 = new SimpleDateFormat(DATE_FORMAT_STR_RFC1123, DEFAULT_LOCALE);
DATE_FORMAT_RFC1123.setTimeZone(DEFAULT_TIMEZONE);
DATE_FORMAT_ISO8601 = new SimpleDateFormat(DATE_FORMAT_STR_ISO8601, DEFAULT_LOCALE);
Expand Down Expand Up @@ -129,7 +126,6 @@ public class StdDateFormat
protected Boolean _lenient;

private transient DateFormat _formatRFC1123;
private transient DateFormat _formatISO8601;

/*
/**********************************************************
Expand Down Expand Up @@ -195,21 +191,16 @@ public StdDateFormat clone() {
return new StdDateFormat(_timezone, _locale, _lenient);
}

/**
* @deprecated Since 2.4; use variant that takes Locale
*/
@Deprecated
public static DateFormat getISO8601Format(TimeZone tz) {
return getISO8601Format(tz, DEFAULT_LOCALE);
}

/**
* Method for getting a non-shared DateFormat instance
* that uses specified timezone and can handle simple ISO-8601
* compliant date format.
*
* @since 2.4
*
* @deprecated Since 2.9
*/
@Deprecated // since 2.9
public static DateFormat getISO8601Format(TimeZone tz, Locale loc) {
return _cloneFormat(DATE_FORMAT_ISO8601, DATE_FORMAT_STR_ISO8601, tz, loc, null);
}
Expand All @@ -220,7 +211,10 @@ public static DateFormat getISO8601Format(TimeZone tz, Locale loc) {
* compliant date format.
*
* @since 2.4
*
* @deprecated Since 2.9
*/
@Deprecated // since 2.9
public static DateFormat getRFC1123Format(TimeZone tz, Locale loc) {
return _cloneFormat(DATE_FORMAT_RFC1123, DATE_FORMAT_STR_RFC1123,
tz, loc, null);
Expand Down Expand Up @@ -347,14 +341,85 @@ protected Date _parseDate(String dateStr, ParsePosition pos) throws ParseExcepti
public StringBuffer format(Date date, StringBuffer toAppendTo,
FieldPosition fieldPosition)
{
if (_formatISO8601 == null) {
_formatISO8601 = _cloneFormat(DATE_FORMAT_ISO8601, DATE_FORMAT_STR_ISO8601,
_timezone, _locale, _lenient);
TimeZone tz = _timezone;
if (tz == null) {
tz = DEFAULT_TIMEZONE;
}
// 24-Jun-2017, tatu: is this actually safe thing to do without clone() or sync?
return _formatISO8601.format(date, toAppendTo, fieldPosition);
_format(tz, _locale, date, toAppendTo);
return toAppendTo;
}

protected static void _format(TimeZone tz, Locale loc, Date date,
StringBuffer buffer)
{
Calendar calendar = new GregorianCalendar(tz, loc);
calendar.setTime(date);

pad4(buffer, calendar.get(Calendar.YEAR));
buffer.append('-');
pad2(buffer, calendar.get(Calendar.MONTH) + 1);
buffer.append('-');
pad2(buffer, calendar.get(Calendar.DAY_OF_MONTH));
buffer.append('T');
pad2(buffer, calendar.get(Calendar.HOUR_OF_DAY));
buffer.append(':');
pad2(buffer, calendar.get(Calendar.MINUTE));
buffer.append(':');
pad2(buffer, calendar.get(Calendar.SECOND));
buffer.append('.');
pad3(buffer, calendar.get(Calendar.MILLISECOND));

int offset = tz.getOffset(calendar.getTimeInMillis());
if (offset != 0) {
int hours = Math.abs((offset / (60 * 1000)) / 60);
int minutes = Math.abs((offset / (60 * 1000)) % 60);
buffer.append(offset < 0 ? '-' : '+');
pad2(buffer, hours);
// 24-Jun-2017, tatu: To add colon or not to add colon? Both are legal...
// tests appear to expect no colon so let's go with that.
// formatted.append(':');
pad2(buffer, minutes);
} else {
// 24-Jun-2017, tatu: While `Z` would be conveniently short, older specs
// mandate use of full `+0000`
// formatted.append('Z');
buffer.append("+0000");
}
}

private static void pad2(StringBuffer buffer, int value) {
int tens = value / 10;
if (tens == 0) {
buffer.append('0');
} else {
buffer.append((char) ('0' + tens));
value -= 10 * tens;
}
buffer.append((char) ('0' + value));
}

private static void pad3(StringBuffer buffer, int value) {
int h = value / 100;
if (h == 0) {
buffer.append('0');
} else {
buffer.append((char) ('0' + h));
value -= (h * 100);
}
pad2(buffer, value);
}

private static void pad4(StringBuffer buffer, int value) {
int h = value / 100;
if (h == 0) {
buffer.append('0').append('0');
} else {
pad2(buffer, h);
value -= (100 * h);
}
pad2(buffer, value);
}

/*
/**********************************************************
/* Std overrides
Expand Down Expand Up @@ -448,7 +513,7 @@ protected Date _parseAsISO8601(String dateStr, ParsePosition pos)
if ((_timezone != null) && ('Z' != dateStr.charAt(totalLen-1))) {
tz = _timezone;
}
Calendar cal = Calendar.getInstance(tz, _locale);
Calendar cal = new GregorianCalendar(tz, _locale);
if (_lenient != null) {
cal.setLenient(_lenient.booleanValue());
}
Expand Down Expand Up @@ -591,7 +656,6 @@ private final static DateFormat _cloneFormat(DateFormat df, String format,

protected void _clearFormats() {
_formatRFC1123 = null;
_formatISO8601 = null;
}

protected static <T> boolean _equals(T value1, T value2) {
Expand Down
Loading

0 comments on commit 37b780b

Please sign in to comment.