Skip to content

Commit 37b780b

Browse files
committed
Bit more work on #1678, serialization side
1 parent 472e03b commit 37b780b

File tree

8 files changed

+124
-136
lines changed

8 files changed

+124
-136
lines changed

release-notes/VERSION

+1-1
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ Project: jackson-databind
9292
#1653: Convenience overload(s) for ObjectMapper#registerSubtypes
9393
#1655: `@JsonAnyGetter` uses different `bean` parameter in `SimpleBeanPropertyFilter`
9494
(reported by georgeflugq@github)
95-
#1678: Rewrite `StdDateFormat` ISO-8601 deserialization functionality
95+
#1678: Rewrite `StdDateFormat` ISO-8601 handling functionality
9696

9797
2.8.9.1 (not yet released)
9898

src/main/java/com/fasterxml/jackson/databind/util/ISO8601DateFormat.java

+1
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
*
1616
* @see ISO8601Utils
1717
*/
18+
@Deprecated // since 2.9
1819
public class ISO8601DateFormat extends DateFormat
1920
{
2021
private static final long serialVersionUID = 1L;

src/main/java/com/fasterxml/jackson/databind/util/ISO8601Utils.java

+31-89
Original file line numberDiff line numberDiff line change
@@ -12,54 +12,16 @@
1212
*
1313
* @see <a href="http://www.w3.org/TR/NOTE-datetime">this specification</a>
1414
*/
15+
@Deprecated // since 2.9
1516
public class ISO8601Utils
1617
{
17-
@Deprecated // since 2.7
18-
private static final String GMT_ID = "GMT";
19-
20-
/**
21-
* ID to represent the 'UTC' string, default timezone since Jackson 2.7
22-
*
23-
* @since 2.7
24-
*/
25-
private static final String UTC_ID = "UTC";
26-
27-
/**
28-
* The GMT timezone, prefetched to avoid more lookups.
29-
*
30-
* @deprecated Since 2.7 use {@link #TIMEZONE_UTC} instead
31-
*/
32-
@Deprecated
33-
private static final TimeZone TIMEZONE_GMT = TimeZone.getTimeZone(GMT_ID);
34-
35-
/**
36-
* The UTC timezone, prefetched to avoid more lookups.
37-
*
38-
* @since 2.7
39-
*/
40-
private static final TimeZone TIMEZONE_UTC = TimeZone.getTimeZone(UTC_ID);
18+
protected final static int DEF_8601_LEN = "yyyy-MM-ddThh:mm:ss.SSS+00:00".length();
4119

4220
/**
4321
* Timezone we use for 'Z' in ISO-8601 date/time values: since 2.7
4422
* {@link #TIMEZONE_UTC}; with earlier versions up to 2.7 was {@link #TIMEZONE_GMT}.
4523
*/
46-
private static final TimeZone TIMEZONE_Z = TIMEZONE_UTC;
47-
48-
/*
49-
/**********************************************************
50-
/* Static factories
51-
/**********************************************************
52-
*/
53-
54-
/**
55-
* Accessor for static GMT timezone instance.
56-
*
57-
* @deprecated since 2.6
58-
*/
59-
@Deprecated // since 2.6
60-
public static TimeZone timeZoneGMT() {
61-
return TIMEZONE_GMT;
62-
}
24+
private static final TimeZone TIMEZONE_Z = TimeZone.getTimeZone("UTC");
6325

6426
/*
6527
/**********************************************************
@@ -74,7 +36,7 @@ public static TimeZone timeZoneGMT() {
7436
* @return the date formatted as 'yyyy-MM-ddThh:mm:ssZ'
7537
*/
7638
public static String format(Date date) {
77-
return format(date, false, TIMEZONE_UTC);
39+
return format(date, false, TIMEZONE_Z);
7840
}
7941

8042
/**
@@ -85,7 +47,12 @@ public static String format(Date date) {
8547
* @return the date formatted as 'yyyy-MM-ddThh:mm:ss[.sss]Z'
8648
*/
8749
public static String format(Date date, boolean millis) {
88-
return format(date, millis, TIMEZONE_UTC);
50+
return format(date, millis, TIMEZONE_Z);
51+
}
52+
53+
@Deprecated // since 2.9
54+
public static String format(Date date, boolean millis, TimeZone tz) {
55+
return format(date, millis, tz, Locale.US);
8956
}
9057

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

10372
// estimate capacity of buffer as close as we can (yeah, that's pedantic ;)
104-
int capacity = "yyyy-MM-ddThh:mm:ss".length();
105-
capacity += millis ? ".sss".length() : 0;
106-
capacity += tz.getRawOffset() == 0 ? "Z".length() : "+hh:mm".length();
107-
StringBuilder formatted = new StringBuilder(capacity);
108-
109-
padInt(formatted, calendar.get(Calendar.YEAR), "yyyy".length());
110-
formatted.append('-');
111-
padInt(formatted, calendar.get(Calendar.MONTH) + 1, "MM".length());
112-
formatted.append('-');
113-
padInt(formatted, calendar.get(Calendar.DAY_OF_MONTH), "dd".length());
114-
formatted.append('T');
115-
padInt(formatted, calendar.get(Calendar.HOUR_OF_DAY), "hh".length());
116-
formatted.append(':');
117-
padInt(formatted, calendar.get(Calendar.MINUTE), "mm".length());
118-
formatted.append(':');
119-
padInt(formatted, calendar.get(Calendar.SECOND), "ss".length());
73+
StringBuilder sb = new StringBuilder(30);
74+
sb.append(String.format(
75+
"%04d-%02d-%02dT%02d:%02d:%02d",
76+
calendar.get(Calendar.YEAR),
77+
calendar.get(Calendar.MONTH) + 1,
78+
calendar.get(Calendar.DAY_OF_MONTH),
79+
calendar.get(Calendar.HOUR_OF_DAY),
80+
calendar.get(Calendar.MINUTE),
81+
calendar.get(Calendar.SECOND)
82+
));
12083
if (millis) {
121-
formatted.append('.');
122-
padInt(formatted, calendar.get(Calendar.MILLISECOND), "sss".length());
84+
sb.append(String.format(".%03d", calendar.get(Calendar.MILLISECOND)));
12385
}
12486

12587
int offset = tz.getOffset(calendar.getTimeInMillis());
12688
if (offset != 0) {
12789
int hours = Math.abs((offset / (60 * 1000)) / 60);
12890
int minutes = Math.abs((offset / (60 * 1000)) % 60);
129-
formatted.append(offset < 0 ? '-' : '+');
130-
padInt(formatted, hours, "hh".length());
131-
formatted.append(':');
132-
padInt(formatted, minutes, "mm".length());
91+
sb.append(String.format("%c%02d:%02d",
92+
(offset < 0 ? '-' : '+'),
93+
hours, minutes));
13394
} else {
134-
formatted.append('Z');
95+
sb.append('Z');
13596
}
136-
return formatted.toString();
97+
return sb.toString();
13798
}
13899

139100
/*
@@ -286,11 +247,7 @@ public static Date parse(String date, ParsePosition pos) throws ParseException {
286247
return calendar.getTime();
287248
// If we get a ParseException it'll already have the right message/offset.
288249
// Other exception types can convert here.
289-
} catch (IndexOutOfBoundsException e) {
290-
fail = e;
291-
} catch (NumberFormatException e) {
292-
fail = e;
293-
} catch (IllegalArgumentException e) {
250+
} catch (Exception e) {
294251
fail = e;
295252
}
296253
String input = (date == null) ? null : ('"' + date + '"');
@@ -350,21 +307,6 @@ private static int parseInt(String value, int beginIndex, int endIndex) throws N
350307
return -result;
351308
}
352309

353-
/**
354-
* Zero pad a number to a specified length
355-
*
356-
* @param buffer buffer to use for padding
357-
* @param value the integer value to pad if necessary.
358-
* @param length the length of the string we should zero pad
359-
*/
360-
private static void padInt(StringBuilder buffer, int value, int length) {
361-
String strValue = Integer.toString(value);
362-
for (int i = length - strValue.length(); i > 0; i--) {
363-
buffer.append('0');
364-
}
365-
buffer.append(strValue);
366-
}
367-
368310
/**
369311
* Returns the index of the first character in the string that is not a digit, starting at offset.
370312
*/

src/main/java/com/fasterxml/jackson/databind/util/StdDateFormat.java

+85-21
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,6 @@ public class StdDateFormat
2626
* for easier enforcing of specific rules. Heavy lifting done by Calendar,
2727
* anyway.
2828
*/
29-
3029
protected final static String PATTERN_PLAIN_STR = "\\d\\d\\d\\d[-]\\d\\d[-]\\d\\d";
3130

3231
protected final static Pattern PATTERN_PLAIN = Pattern.compile(PATTERN_PLAIN_STR);
@@ -95,10 +94,8 @@ public class StdDateFormat
9594
* actual instances more cheaply (avoids re-parsing).
9695
*/
9796
static {
98-
/* Another important thing: let's force use of default timezone for
99-
* baseline DataFormat objects
100-
*/
101-
97+
// Another important thing: let's force use of default timezone for
98+
// baseline DataFormat objects
10299
DATE_FORMAT_RFC1123 = new SimpleDateFormat(DATE_FORMAT_STR_RFC1123, DEFAULT_LOCALE);
103100
DATE_FORMAT_RFC1123.setTimeZone(DEFAULT_TIMEZONE);
104101
DATE_FORMAT_ISO8601 = new SimpleDateFormat(DATE_FORMAT_STR_ISO8601, DEFAULT_LOCALE);
@@ -129,7 +126,6 @@ public class StdDateFormat
129126
protected Boolean _lenient;
130127

131128
private transient DateFormat _formatRFC1123;
132-
private transient DateFormat _formatISO8601;
133129

134130
/*
135131
/**********************************************************
@@ -195,21 +191,16 @@ public StdDateFormat clone() {
195191
return new StdDateFormat(_timezone, _locale, _lenient);
196192
}
197193

198-
/**
199-
* @deprecated Since 2.4; use variant that takes Locale
200-
*/
201-
@Deprecated
202-
public static DateFormat getISO8601Format(TimeZone tz) {
203-
return getISO8601Format(tz, DEFAULT_LOCALE);
204-
}
205-
206194
/**
207195
* Method for getting a non-shared DateFormat instance
208196
* that uses specified timezone and can handle simple ISO-8601
209197
* compliant date format.
210198
*
211199
* @since 2.4
200+
*
201+
* @deprecated Since 2.9
212202
*/
203+
@Deprecated // since 2.9
213204
public static DateFormat getISO8601Format(TimeZone tz, Locale loc) {
214205
return _cloneFormat(DATE_FORMAT_ISO8601, DATE_FORMAT_STR_ISO8601, tz, loc, null);
215206
}
@@ -220,7 +211,10 @@ public static DateFormat getISO8601Format(TimeZone tz, Locale loc) {
220211
* compliant date format.
221212
*
222213
* @since 2.4
214+
*
215+
* @deprecated Since 2.9
223216
*/
217+
@Deprecated // since 2.9
224218
public static DateFormat getRFC1123Format(TimeZone tz, Locale loc) {
225219
return _cloneFormat(DATE_FORMAT_RFC1123, DATE_FORMAT_STR_RFC1123,
226220
tz, loc, null);
@@ -347,14 +341,85 @@ protected Date _parseDate(String dateStr, ParsePosition pos) throws ParseExcepti
347341
public StringBuffer format(Date date, StringBuffer toAppendTo,
348342
FieldPosition fieldPosition)
349343
{
350-
if (_formatISO8601 == null) {
351-
_formatISO8601 = _cloneFormat(DATE_FORMAT_ISO8601, DATE_FORMAT_STR_ISO8601,
352-
_timezone, _locale, _lenient);
344+
TimeZone tz = _timezone;
345+
if (tz == null) {
346+
tz = DEFAULT_TIMEZONE;
353347
}
354-
// 24-Jun-2017, tatu: is this actually safe thing to do without clone() or sync?
355-
return _formatISO8601.format(date, toAppendTo, fieldPosition);
348+
_format(tz, _locale, date, toAppendTo);
349+
return toAppendTo;
356350
}
357351

352+
protected static void _format(TimeZone tz, Locale loc, Date date,
353+
StringBuffer buffer)
354+
{
355+
Calendar calendar = new GregorianCalendar(tz, loc);
356+
calendar.setTime(date);
357+
358+
pad4(buffer, calendar.get(Calendar.YEAR));
359+
buffer.append('-');
360+
pad2(buffer, calendar.get(Calendar.MONTH) + 1);
361+
buffer.append('-');
362+
pad2(buffer, calendar.get(Calendar.DAY_OF_MONTH));
363+
buffer.append('T');
364+
pad2(buffer, calendar.get(Calendar.HOUR_OF_DAY));
365+
buffer.append(':');
366+
pad2(buffer, calendar.get(Calendar.MINUTE));
367+
buffer.append(':');
368+
pad2(buffer, calendar.get(Calendar.SECOND));
369+
buffer.append('.');
370+
pad3(buffer, calendar.get(Calendar.MILLISECOND));
371+
372+
int offset = tz.getOffset(calendar.getTimeInMillis());
373+
if (offset != 0) {
374+
int hours = Math.abs((offset / (60 * 1000)) / 60);
375+
int minutes = Math.abs((offset / (60 * 1000)) % 60);
376+
buffer.append(offset < 0 ? '-' : '+');
377+
pad2(buffer, hours);
378+
// 24-Jun-2017, tatu: To add colon or not to add colon? Both are legal...
379+
// tests appear to expect no colon so let's go with that.
380+
// formatted.append(':');
381+
pad2(buffer, minutes);
382+
} else {
383+
// 24-Jun-2017, tatu: While `Z` would be conveniently short, older specs
384+
// mandate use of full `+0000`
385+
// formatted.append('Z');
386+
buffer.append("+0000");
387+
}
388+
}
389+
390+
private static void pad2(StringBuffer buffer, int value) {
391+
int tens = value / 10;
392+
if (tens == 0) {
393+
buffer.append('0');
394+
} else {
395+
buffer.append((char) ('0' + tens));
396+
value -= 10 * tens;
397+
}
398+
buffer.append((char) ('0' + value));
399+
}
400+
401+
private static void pad3(StringBuffer buffer, int value) {
402+
int h = value / 100;
403+
if (h == 0) {
404+
buffer.append('0');
405+
} else {
406+
buffer.append((char) ('0' + h));
407+
value -= (h * 100);
408+
}
409+
pad2(buffer, value);
410+
}
411+
412+
private static void pad4(StringBuffer buffer, int value) {
413+
int h = value / 100;
414+
if (h == 0) {
415+
buffer.append('0').append('0');
416+
} else {
417+
pad2(buffer, h);
418+
value -= (100 * h);
419+
}
420+
pad2(buffer, value);
421+
}
422+
358423
/*
359424
/**********************************************************
360425
/* Std overrides
@@ -448,7 +513,7 @@ protected Date _parseAsISO8601(String dateStr, ParsePosition pos)
448513
if ((_timezone != null) && ('Z' != dateStr.charAt(totalLen-1))) {
449514
tz = _timezone;
450515
}
451-
Calendar cal = Calendar.getInstance(tz, _locale);
516+
Calendar cal = new GregorianCalendar(tz, _locale);
452517
if (_lenient != null) {
453518
cal.setLenient(_lenient.booleanValue());
454519
}
@@ -591,7 +656,6 @@ private final static DateFormat _cloneFormat(DateFormat df, String format,
591656

592657
protected void _clearFormats() {
593658
_formatRFC1123 = null;
594-
_formatISO8601 = null;
595659
}
596660

597661
protected static <T> boolean _equals(T value1, T value2) {

0 commit comments

Comments
 (0)