@@ -109,6 +109,16 @@ public class StdDateFormat
109
109
protected transient TimeZone _timezone ;
110
110
111
111
protected final Locale _locale ;
112
+
113
+ /**
114
+ * Explicit override for leniency, if specified.
115
+ *<p>
116
+ * Can not be `final` because {@link #setLenient(boolean)} returns
117
+ * `void`.
118
+ *
119
+ * @since 2.7
120
+ */
121
+ protected Boolean _lenient ;
112
122
113
123
protected transient DateFormat _formatRFC1123 ;
114
124
protected transient DateFormat _formatISO8601 ;
@@ -125,11 +135,18 @@ public StdDateFormat() {
125
135
_locale = DEFAULT_LOCALE ;
126
136
}
127
137
138
+ @ Deprecated // since 2.7
128
139
public StdDateFormat (TimeZone tz , Locale loc ) {
129
140
_timezone = tz ;
130
141
_locale = loc ;
131
142
}
132
143
144
+ protected StdDateFormat (TimeZone tz , Locale loc , Boolean lenient ) {
145
+ _timezone = tz ;
146
+ _locale = loc ;
147
+ _lenient = lenient ;
148
+ }
149
+
133
150
public static TimeZone getDefaultTimeZone () {
134
151
return DEFAULT_TIMEZONE ;
135
152
}
@@ -145,22 +162,22 @@ public StdDateFormat withTimeZone(TimeZone tz) {
145
162
if ((tz == _timezone ) || tz .equals (_timezone )) {
146
163
return this ;
147
164
}
148
- return new StdDateFormat (tz , _locale );
165
+ return new StdDateFormat (tz , _locale , _lenient );
149
166
}
150
167
151
168
public StdDateFormat withLocale (Locale loc ) {
152
169
if (loc .equals (_locale )) {
153
170
return this ;
154
171
}
155
- return new StdDateFormat (_timezone , loc );
172
+ return new StdDateFormat (_timezone , loc , _lenient );
156
173
}
157
174
158
175
@ Override
159
176
public StdDateFormat clone () {
160
177
/* Although there is that much state to share, we do need to
161
178
* orchestrate a bit, mostly since timezones may be changed
162
179
*/
163
- return new StdDateFormat (_timezone , _locale );
180
+ return new StdDateFormat (_timezone , _locale , _lenient );
164
181
}
165
182
166
183
/**
@@ -179,7 +196,7 @@ public static DateFormat getISO8601Format(TimeZone tz) {
179
196
* @since 2.4
180
197
*/
181
198
public static DateFormat getISO8601Format (TimeZone tz , Locale loc ) {
182
- return _cloneFormat (DATE_FORMAT_ISO8601 , DATE_FORMAT_STR_ISO8601 , tz , loc );
199
+ return _cloneFormat (DATE_FORMAT_ISO8601 , DATE_FORMAT_STR_ISO8601 , tz , loc , null );
183
200
}
184
201
185
202
/**
@@ -190,7 +207,8 @@ public static DateFormat getISO8601Format(TimeZone tz, Locale loc) {
190
207
* @since 2.4
191
208
*/
192
209
public static DateFormat getRFC1123Format (TimeZone tz , Locale loc ) {
193
- return _cloneFormat (DATE_FORMAT_RFC1123 , DATE_FORMAT_STR_RFC1123 , tz , loc );
210
+ return _cloneFormat (DATE_FORMAT_RFC1123 , DATE_FORMAT_STR_RFC1123 ,
211
+ tz , loc , null );
194
212
}
195
213
196
214
/**
@@ -200,41 +218,93 @@ public static DateFormat getRFC1123Format(TimeZone tz, Locale loc) {
200
218
public static DateFormat getRFC1123Format (TimeZone tz ) {
201
219
return getRFC1123Format (tz , DEFAULT_LOCALE );
202
220
}
203
-
221
+
204
222
/*
205
223
/**********************************************************
206
- /* Public API
224
+ /* Public API, configuration
207
225
/**********************************************************
208
226
*/
209
227
210
228
@ Override // since 2.6
211
229
public TimeZone getTimeZone () {
212
230
return _timezone ;
213
231
}
214
-
232
+
215
233
@ Override
216
234
public void setTimeZone (TimeZone tz )
217
235
{
218
236
/* DateFormats are timezone-specific (via Calendar contained),
219
237
* so need to reset instances if timezone changes:
220
238
*/
221
239
if (!tz .equals (_timezone )) {
222
- _formatRFC1123 = null ;
223
- _formatISO8601 = null ;
224
- _formatISO8601_z = null ;
225
- _formatPlain = null ;
240
+ _clearFormats ();
226
241
_timezone = tz ;
227
242
}
228
243
}
229
-
244
+
245
+ /**
246
+ * Need to override since we need to keep track of leniency locally,
247
+ * and not via underlying {@link Calendar} instance like base class
248
+ * does.
249
+ */
250
+ @ Override // since 2.7
251
+ public void setLenient (boolean enabled ) {
252
+ Boolean newValue = enabled ;
253
+ if (_lenient != newValue ) {
254
+ _lenient = newValue ;
255
+ // and since leniency settings may have been used:
256
+ _clearFormats ();
257
+ }
258
+ }
259
+
260
+ @ Override // since 2.7
261
+ public boolean isLenient () {
262
+ if (_lenient == null ) {
263
+ // default is, I believe, true
264
+ return true ;
265
+ }
266
+ return _lenient .booleanValue ();
267
+ }
268
+
269
+ /*
270
+ /**********************************************************
271
+ /* Public API, parsing
272
+ /**********************************************************
273
+ */
274
+
230
275
@ Override
231
276
public Date parse (String dateStr ) throws ParseException
232
277
{
233
278
dateStr = dateStr .trim ();
234
279
ParsePosition pos = new ParsePosition (0 );
235
- Date result = parse (dateStr , pos );
236
- if (result != null ) {
237
- return result ;
280
+
281
+ Date dt ;
282
+
283
+ if (looksLikeISO8601 (dateStr )) { // also includes "plain"
284
+ dt = parseAsISO8601 (dateStr , pos , true );
285
+ } else {
286
+ // Also consider "stringified" simple time stamp
287
+ int i = dateStr .length ();
288
+ while (--i >= 0 ) {
289
+ char ch = dateStr .charAt (i );
290
+ if (ch < '0' || ch > '9' ) {
291
+ // 07-Aug-2013, tatu: And [databind#267] points out that negative numbers should also work
292
+ if (i > 0 || ch != '-' ) {
293
+ break ;
294
+ }
295
+ }
296
+ }
297
+ if ((i < 0 )
298
+ // let's just assume negative numbers are fine (can't be RFC-1123 anyway); check length for positive
299
+ && (dateStr .charAt (0 ) == '-' || NumberInput .inLongRange (dateStr , false ))) {
300
+ dt = new Date (Long .parseLong (dateStr ));
301
+ } else {
302
+ // Otherwise, fall back to using RFC 1123
303
+ dt = parseAsRFC1123 (dateStr , pos );
304
+ }
305
+ }
306
+ if (dt != null ) {
307
+ return dt ;
238
308
}
239
309
240
310
StringBuilder sb = new StringBuilder ();
@@ -256,7 +326,11 @@ public Date parse(String dateStr) throws ParseException
256
326
public Date parse (String dateStr , ParsePosition pos )
257
327
{
258
328
if (looksLikeISO8601 (dateStr )) { // also includes "plain"
259
- return parseAsISO8601 (dateStr , pos );
329
+ try {
330
+ return parseAsISO8601 (dateStr , pos , false );
331
+ } catch (ParseException e ) { // will NOT be thrown due to false but is declared...
332
+ return null ;
333
+ }
260
334
}
261
335
// Also consider "stringified" simple time stamp
262
336
int i = dateStr .length ();
@@ -279,12 +353,19 @@ public Date parse(String dateStr, ParsePosition pos)
279
353
return parseAsRFC1123 (dateStr , pos );
280
354
}
281
355
356
+ /*
357
+ /**********************************************************
358
+ /* Public API, writing
359
+ /**********************************************************
360
+ */
361
+
282
362
@ Override
283
363
public StringBuffer format (Date date , StringBuffer toAppendTo ,
284
364
FieldPosition fieldPosition )
285
365
{
286
366
if (_formatISO8601 == null ) {
287
- _formatISO8601 = _cloneFormat (DATE_FORMAT_ISO8601 , DATE_FORMAT_STR_ISO8601 , _timezone , _locale );
367
+ _formatISO8601 = _cloneFormat (DATE_FORMAT_ISO8601 , DATE_FORMAT_STR_ISO8601 ,
368
+ _timezone , _locale , _lenient );
288
369
}
289
370
return _formatISO8601 .format (date , toAppendTo , fieldPosition );
290
371
}
@@ -328,7 +409,8 @@ protected boolean looksLikeISO8601(String dateStr)
328
409
return false ;
329
410
}
330
411
331
- protected Date parseAsISO8601 (String dateStr , ParsePosition pos )
412
+ protected Date parseAsISO8601 (String dateStr , ParsePosition pos , boolean throwErrors )
413
+ throws ParseException
332
414
{
333
415
/* 21-May-2009, tatu: DateFormat has very strict handling of
334
416
* timezone modifiers for ISO-8601. So we need to do some scrubbing.
@@ -341,19 +423,24 @@ protected Date parseAsISO8601(String dateStr, ParsePosition pos)
341
423
int len = dateStr .length ();
342
424
char c = dateStr .charAt (len -1 );
343
425
DateFormat df ;
426
+ String formatStr ;
344
427
345
- // [JACKSON-200]: need to support "plain" date...
428
+ // Need to support "plain" date...
346
429
if (len <= 10 && Character .isDigit (c )) {
347
430
df = _formatPlain ;
431
+ formatStr = DATE_FORMAT_STR_PLAIN ;
348
432
if (df == null ) {
349
- df = _formatPlain = _cloneFormat (DATE_FORMAT_PLAIN , DATE_FORMAT_STR_PLAIN , _timezone , _locale );
433
+ df = _formatPlain = _cloneFormat (DATE_FORMAT_PLAIN , formatStr ,
434
+ _timezone , _locale , _lenient );
350
435
}
351
436
} else if (c == 'Z' ) {
352
437
df = _formatISO8601_z ;
438
+ formatStr = DATE_FORMAT_STR_ISO8601_Z ;
353
439
if (df == null ) {
354
- df = _formatISO8601_z = _cloneFormat (DATE_FORMAT_ISO8601_Z , DATE_FORMAT_STR_ISO8601_Z , _timezone , _locale );
440
+ df = _formatISO8601_z = _cloneFormat (DATE_FORMAT_ISO8601_Z , formatStr ,
441
+ _timezone , _locale , _lenient );
355
442
}
356
- // [JACKSON-334]: may be missing milliseconds... if so, add
443
+ // may be missing milliseconds... if so, add
357
444
if (dateStr .charAt (len -4 ) == ':' ) {
358
445
StringBuilder sb = new StringBuilder (dateStr );
359
446
sb .insert (len -1 , ".000" );
@@ -398,8 +485,10 @@ protected Date parseAsISO8601(String dateStr, ParsePosition pos)
398
485
dateStr = sb .toString ();
399
486
}
400
487
df = _formatISO8601 ;
488
+ formatStr = DATE_FORMAT_STR_ISO8601 ;
401
489
if (_formatISO8601 == null ) {
402
- df = _formatISO8601 = _cloneFormat (DATE_FORMAT_ISO8601 , DATE_FORMAT_STR_ISO8601 , _timezone , _locale );
490
+ df = _formatISO8601 = _cloneFormat (DATE_FORMAT_ISO8601 , formatStr ,
491
+ _timezone , _locale , _lenient );
403
492
}
404
493
} else {
405
494
// If not, plain date. Easiest to just patch 'Z' in the end?
@@ -419,19 +508,29 @@ protected Date parseAsISO8601(String dateStr, ParsePosition pos)
419
508
sb .append ('Z' );
420
509
dateStr = sb .toString ();
421
510
df = _formatISO8601_z ;
511
+ formatStr = DATE_FORMAT_STR_ISO8601_Z ;
422
512
if (df == null ) {
423
- df = _formatISO8601_z = _cloneFormat (DATE_FORMAT_ISO8601_Z , DATE_FORMAT_STR_ISO8601_Z ,
424
- _timezone , _locale );
513
+ df = _formatISO8601_z = _cloneFormat (DATE_FORMAT_ISO8601_Z , formatStr ,
514
+ _timezone , _locale , _lenient );
425
515
}
426
516
}
427
517
}
428
- return df .parse (dateStr , pos );
518
+ Date dt = df .parse (dateStr , pos );
519
+ // 22-Dec-2015, tatu: With non-lenient, may get null
520
+ if (dt == null ) {
521
+ throw new ParseException
522
+ (String .format ("Can not parse date \" %s\" : while it seems to fit format '%s', parsing fails (leniency? %s)" ,
523
+ dateStr , formatStr , _lenient ),
524
+ pos .getErrorIndex ());
525
+ }
526
+ return dt ;
429
527
}
430
528
431
529
protected Date parseAsRFC1123 (String dateStr , ParsePosition pos )
432
530
{
433
531
if (_formatRFC1123 == null ) {
434
- _formatRFC1123 = _cloneFormat (DATE_FORMAT_RFC1123 , DATE_FORMAT_STR_RFC1123 , _timezone , _locale );
532
+ _formatRFC1123 = _cloneFormat (DATE_FORMAT_RFC1123 , DATE_FORMAT_STR_RFC1123 ,
533
+ _timezone , _locale , _lenient );
435
534
}
436
535
return _formatRFC1123 .parse (dateStr , pos );
437
536
}
@@ -452,7 +551,7 @@ private final static boolean hasTimeZone(String str)
452
551
}
453
552
454
553
private final static DateFormat _cloneFormat (DateFormat df , String format ,
455
- TimeZone tz , Locale loc )
554
+ TimeZone tz , Locale loc , Boolean lenient )
456
555
{
457
556
if (!loc .equals (DEFAULT_LOCALE )) {
458
557
df = new SimpleDateFormat (format , loc );
@@ -463,7 +562,17 @@ private final static DateFormat _cloneFormat(DateFormat df, String format,
463
562
df .setTimeZone (tz );
464
563
}
465
564
}
565
+ if (lenient != null ) {
566
+ df .setLenient (lenient .booleanValue ());
567
+ }
466
568
return df ;
467
569
}
570
+
571
+ protected void _clearFormats () {
572
+ _formatRFC1123 = null ;
573
+ _formatISO8601 = null ;
574
+ _formatISO8601_z = null ;
575
+ _formatPlain = null ;
576
+ }
468
577
}
469
578
0 commit comments