1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package org.apache.commons.lang3.time;
18
19 import static org.apache.commons.lang3.LangAssertions.assertIllegalArgumentException;
20 import static org.junit.jupiter.api.Assertions.assertEquals;
21 import static org.junit.jupiter.api.Assertions.assertFalse;
22 import static org.junit.jupiter.api.Assertions.assertNotEquals;
23 import static org.junit.jupiter.api.Assertions.assertNotNull;
24 import static org.junit.jupiter.api.Assertions.assertThrows;
25 import static org.junit.jupiter.api.Assertions.assertTrue;
26 import static org.junit.jupiter.api.Assertions.fail;
27
28 import java.io.Serializable;
29 import java.text.ParseException;
30 import java.text.ParsePosition;
31 import java.text.SimpleDateFormat;
32 import java.time.Instant;
33 import java.util.Calendar;
34 import java.util.Date;
35 import java.util.GregorianCalendar;
36 import java.util.HashMap;
37 import java.util.Locale;
38 import java.util.Map;
39 import java.util.TimeZone;
40 import java.util.stream.Stream;
41
42 import org.apache.commons.lang3.AbstractLangTest;
43 import org.apache.commons.lang3.LocaleProblems;
44 import org.apache.commons.lang3.LocaleUtils;
45 import org.apache.commons.lang3.SerializationUtils;
46 import org.apache.commons.lang3.SystemUtils;
47 import org.apache.commons.lang3.function.TriFunction;
48 import org.junit.jupiter.api.AfterEach;
49 import org.junit.jupiter.api.BeforeEach;
50 import org.junit.jupiter.api.Test;
51 import org.junit.jupiter.params.ParameterizedTest;
52 import org.junit.jupiter.params.provider.Arguments;
53 import org.junit.jupiter.params.provider.MethodSource;
54 import org.junitpioneer.jupiter.DefaultLocale;
55 import org.junitpioneer.jupiter.DefaultTimeZone;
56 import org.junitpioneer.jupiter.ReadsDefaultLocale;
57 import org.junitpioneer.jupiter.ReadsDefaultTimeZone;
58 import org.junitpioneer.jupiter.cartesian.ArgumentSets;
59 import org.junitpioneer.jupiter.cartesian.CartesianTest;
60 import org.opentest4j.AssertionFailedError;
61
62
63
64
65 @DefaultLocale(language = "en")
66 @DefaultTimeZone(TimeZones.GMT_ID)
67 @ReadsDefaultLocale
68 @ReadsDefaultTimeZone
69 class FastDateParserTest extends AbstractLangTest {
70
71 private enum Expected1806 {
72
73
74 India(INDIA, "+05", "+0530", "+05:30", true),
75 Greenwich(TimeZones.GMT, "Z", "Z", "Z", false),
76 NewYork(NEW_YORK, "-05", "-0500", "-05:00", false);
77
78
79 final TimeZone zone;
80 final String one;
81 final String two;
82 final String three;
83 final long offset;
84
85 Expected1806(final TimeZone zone, final String one, final String two, final String three,
86 final boolean hasHalfHourOffset) {
87 this.zone = zone;
88 this.one = one;
89 this.two = two;
90 this.three = three;
91 this.offset = hasHalfHourOffset ? 30 * 60 * 1000 : 0;
92 }
93 }
94
95 static final String DATE_PARSER_PARAMETERS = "dateParserParameters";
96
97 static final String SHORT_FORMAT_NOERA = "y/M/d/h/a/m/s/E";
98
99 static final String LONG_FORMAT_NOERA = "yyyy/MMMM/dddd/hhhh/mmmm/ss/aaaa/EEEE";
100 static final String SHORT_FORMAT = "G/" + SHORT_FORMAT_NOERA;
101 static final String LONG_FORMAT = "GGGG/" + LONG_FORMAT_NOERA;
102
103 private static final String yMdHmsSZ = "yyyy-MM-dd'T'HH:mm:ss.SSS Z";
104 private static final String DMY_DOT = "dd.MM.yyyy";
105 private static final String YMD_SLASH = "yyyy/MM/dd";
106 private static final String MDY_DASH = "MM-DD-yyyy";
107 private static final String MDY_SLASH = "MM/DD/yyyy";
108
109 private static final TimeZone REYKJAVIK = TimeZones.getTimeZone("Atlantic/Reykjavik");
110 private static final TimeZone NEW_YORK = TimeZones.getTimeZone("America/New_York");
111 private static final TimeZone INDIA = TimeZones.getTimeZone("Asia/Calcutta");
112
113 private static final Locale SWEDEN = new Locale("sv", "SE");
114
115 static void checkParse(final Locale locale, final Calendar cal, final SimpleDateFormat simpleDateFormat,
116 final DateParser dateParser) {
117 final String formattedDate = simpleDateFormat.format(cal.getTime());
118 checkParse(locale, simpleDateFormat, dateParser, formattedDate, formattedDate);
119 checkParse(locale, simpleDateFormat, dateParser, formattedDate.toLowerCase(locale), formattedDate);
120 checkParse(locale, simpleDateFormat, dateParser, formattedDate.toUpperCase(locale), formattedDate);
121 }
122
123 static void checkParse(final Locale locale, final SimpleDateFormat simpleDateFormat, final DateParser dateParser,
124 final String formattedDate, final String originalFormattedDate) {
125 try {
126 final Date expectedTime = simpleDateFormat.parse(formattedDate);
127 final Date actualTime = dateParser.parse(formattedDate);
128 assertEquals(expectedTime, actualTime,
129 "locale: " + locale + ", formattedDate: '" + formattedDate + "', originalFormattedDate: '"
130 + originalFormattedDate + ", simpleDateFormat.pattern: '" + simpleDateFormat + "', Java: "
131 + SystemUtils.JAVA_RUNTIME_VERSION + "\n");
132 } catch (final Exception e) {
133 fail("locale: " + locale + ", formattedDate: '" + formattedDate + "', error : " + e + "\n", e);
134 }
135 }
136
137 static Stream<Arguments> dateParserParameters() {
138 return Stream.of(
139
140 Arguments.of((TriFunction<String, TimeZone, Locale, DateParser>) (format, timeZone, locale)
141 -> new FastDateParser(format, timeZone, locale, null)),
142 Arguments.of((TriFunction<String, TimeZone, Locale, DateParser>) FastDateFormat::getInstance)
143
144 );
145 }
146
147 private static Calendar initializeCalendar(final TimeZone timeZone) {
148 final Calendar cal = Calendar.getInstance(timeZone);
149 cal.set(Calendar.YEAR, 2001);
150 cal.set(Calendar.MONTH, 1);
151 cal.set(Calendar.DAY_OF_MONTH, 4);
152 cal.set(Calendar.HOUR_OF_DAY, 12);
153 cal.set(Calendar.MINUTE, 8);
154 cal.set(Calendar.SECOND, 56);
155 cal.set(Calendar.MILLISECOND, 235);
156 return cal;
157 }
158
159 static ArgumentSets testParsesFactory() {
160
161 return ArgumentSets
162 .argumentsForFirstParameter(LONG_FORMAT, SHORT_FORMAT)
163 .argumentsForNextParameter(LocaleUtils.availableLocaleList())
164 .argumentsForNextParameter(NEW_YORK, REYKJAVIK, TimeZones.GMT)
165 .argumentsForNextParameter(2003, 1940, 1868, 1867, 1, -1, -1940);
166
167 }
168
169 private final TriFunction<String, TimeZone, Locale, DateParser> dateParserProvider = (format, timeZone, locale) -> new FastDateParser(format, timeZone,
170 locale, null);
171
172 @BeforeEach
173 @AfterEach
174 void clear() {
175 AbstractFormatCache.clear();
176 FastDateFormat.clear();
177 FastDateParser.clear();
178 FastDatePrinter.clear();
179 }
180
181 private DateParser getDateInstance(final int dateStyle, final Locale locale) {
182 return getInstance(null, AbstractFormatCache.getPatternForStyle(Integer.valueOf(dateStyle), null, locale), TimeZone.getDefault(), Locale.getDefault());
183 }
184
185 private Calendar getEraStart(int year, final TimeZone zone, final Locale locale) {
186 final Calendar cal = Calendar.getInstance(zone, locale);
187 cal.clear();
188
189 if (locale.equals(FastDateParser.JAPANESE_IMPERIAL)) {
190 if (year < 1868) {
191 cal.set(Calendar.ERA, 0);
192 cal.set(Calendar.YEAR, 1868 - year);
193 }
194 } else {
195 if (year < 0) {
196 cal.set(Calendar.ERA, GregorianCalendar.BC);
197 year = -year;
198 }
199 cal.set(Calendar.YEAR, year / 100 * 100);
200 }
201 return cal;
202 }
203
204 DateParser getInstance(final String format) {
205 return getInstance(null, format, TimeZone.getDefault(), Locale.getDefault());
206 }
207
208 DateParser getInstance(final String format, final Locale locale) {
209 return getInstance(null, format, TimeZone.getDefault(), locale);
210 }
211
212 private DateParser getInstance(final String format, final TimeZone timeZone) {
213 return getInstance(null, format, timeZone, Locale.getDefault());
214 }
215
216
217
218
219
220
221
222
223
224
225 protected DateParser getInstance(final TriFunction<String, TimeZone, Locale, DateParser> dpProvider,
226 final String format, final TimeZone timeZone, final Locale locale) {
227 return (dpProvider == null ? this.dateParserProvider : dpProvider).apply(format, timeZone, locale);
228 }
229
230 @ParameterizedTest
231 @MethodSource(DATE_PARSER_PARAMETERS)
232 void test_Equality_Hash(final TriFunction<String, TimeZone, Locale, DateParser> dpProvider) {
233
234 final DateParser[] parsers = {
235 getInstance(dpProvider, yMdHmsSZ, NEW_YORK, Locale.US),
236 getInstance(dpProvider, DMY_DOT, NEW_YORK, Locale.US),
237 getInstance(dpProvider, YMD_SLASH, NEW_YORK, Locale.US),
238 getInstance(dpProvider, MDY_DASH, NEW_YORK, Locale.US),
239 getInstance(dpProvider, MDY_SLASH, NEW_YORK, Locale.US),
240 getInstance(dpProvider, MDY_SLASH, REYKJAVIK, Locale.US),
241 getInstance(dpProvider, MDY_SLASH, REYKJAVIK, SWEDEN)
242 };
243
244 final Map<DateParser, Integer> map = new HashMap<>();
245 int i = 0;
246 for (final DateParser parser : parsers) {
247 map.put(parser, Integer.valueOf(i++));
248 }
249 i = 0;
250 for (final DateParser parser : parsers) {
251 assertEquals(i++, map.get(parser).intValue());
252 }
253 }
254
255 @Test
256 void test1806() throws ParseException {
257 final String formatStub = "yyyy-MM-dd'T'HH:mm:ss.SSS";
258 final String dateStub = "2001-02-04T12:08:56.235";
259
260 for (final Expected1806 trial : Expected1806.values()) {
261 final Calendar cal = initializeCalendar(trial.zone);
262
263 final String message = trial.zone.getDisplayName() + ";";
264
265 DateParser parser = getInstance(formatStub + "X", trial.zone);
266 assertEquals(cal.getTime().getTime(), parser.parse(dateStub + trial.one).getTime() - trial.offset,
267 message + trial.one);
268
269 parser = getInstance(formatStub + "XX", trial.zone);
270 assertEquals(cal.getTime(), parser.parse(dateStub + trial.two), message + trial.two);
271
272 parser = getInstance(formatStub + "XXX", trial.zone);
273 assertEquals(cal.getTime(), parser.parse(dateStub + trial.three), message + trial.three);
274 }
275 }
276
277 @Test
278 void test1806Argument() {
279 assertIllegalArgumentException(() -> getInstance("XXXX"));
280 }
281
282 @ParameterizedTest
283 @MethodSource(DATE_PARSER_PARAMETERS)
284 void testAmPm(final TriFunction<String, TimeZone, Locale, DateParser> dpProvider) throws ParseException {
285 final Calendar cal = Calendar.getInstance(NEW_YORK, Locale.US);
286 cal.clear();
287
288 final DateParser h = getInstance(dpProvider, "yyyy-MM-dd hh a mm:ss", NEW_YORK, Locale.US);
289 final DateParser K = getInstance(dpProvider, "yyyy-MM-dd KK a mm:ss", NEW_YORK, Locale.US);
290 final DateParser k = getInstance(dpProvider, "yyyy-MM-dd kk:mm:ss", NEW_YORK, Locale.US);
291 final DateParser H = getInstance(dpProvider, "yyyy-MM-dd HH:mm:ss", NEW_YORK, Locale.US);
292
293 cal.set(2010, Calendar.AUGUST, 1, 0, 33, 20);
294 assertEquals(cal.getTime(), h.parse("2010-08-01 12 AM 33:20"));
295 assertEquals(cal.getTime(), K.parse("2010-08-01 0 AM 33:20"));
296 assertEquals(cal.getTime(), k.parse("2010-08-01 00:33:20"));
297 assertEquals(cal.getTime(), H.parse("2010-08-01 00:33:20"));
298
299 cal.set(2010, Calendar.AUGUST, 1, 3, 33, 20);
300 assertEquals(cal.getTime(), h.parse("2010-08-01 3 AM 33:20"));
301 assertEquals(cal.getTime(), K.parse("2010-08-01 3 AM 33:20"));
302 assertEquals(cal.getTime(), k.parse("2010-08-01 03:33:20"));
303 assertEquals(cal.getTime(), H.parse("2010-08-01 03:33:20"));
304
305 cal.set(2010, Calendar.AUGUST, 1, 15, 33, 20);
306 assertEquals(cal.getTime(), h.parse("2010-08-01 3 PM 33:20"));
307 assertEquals(cal.getTime(), K.parse("2010-08-01 3 PM 33:20"));
308 assertEquals(cal.getTime(), k.parse("2010-08-01 15:33:20"));
309 assertEquals(cal.getTime(), H.parse("2010-08-01 15:33:20"));
310
311 cal.set(2010, Calendar.AUGUST, 1, 12, 33, 20);
312 assertEquals(cal.getTime(), h.parse("2010-08-01 12 PM 33:20"));
313 assertEquals(cal.getTime(), K.parse("2010-08-01 0 PM 33:20"));
314 assertEquals(cal.getTime(), k.parse("2010-08-01 12:33:20"));
315 assertEquals(cal.getTime(), H.parse("2010-08-01 12:33:20"));
316 }
317
318 @Test
319 void testDayNumberOfWeek() throws ParseException {
320 final DateParser parser = getInstance("u");
321 final Calendar calendar = Calendar.getInstance();
322
323 calendar.setTime(parser.parse("1"));
324 assertEquals(Calendar.MONDAY, calendar.get(Calendar.DAY_OF_WEEK));
325
326 calendar.setTime(parser.parse("6"));
327 assertEquals(Calendar.SATURDAY, calendar.get(Calendar.DAY_OF_WEEK));
328
329 calendar.setTime(parser.parse("7"));
330 assertEquals(Calendar.SUNDAY, calendar.get(Calendar.DAY_OF_WEEK));
331 }
332
333 @ParameterizedTest
334 @MethodSource(DATE_PARSER_PARAMETERS)
335 void testDayOf(final TriFunction<String, TimeZone, Locale, DateParser> dpProvider) throws ParseException {
336 final Calendar cal = Calendar.getInstance(NEW_YORK, Locale.US);
337 cal.clear();
338 cal.set(2003, Calendar.FEBRUARY, 10);
339
340 final DateParser fdf = getInstance(dpProvider, "W w F D y", NEW_YORK, Locale.US);
341 assertEquals(cal.getTime(), fdf.parse("3 7 2 41 03"));
342 }
343
344 @Test
345 void testEquals() {
346 final DateParser parser1 = getInstance(YMD_SLASH);
347 final DateParser parser2 = getInstance(YMD_SLASH);
348
349 assertEquals(parser1, parser2);
350 assertEquals(parser1.hashCode(), parser2.hashCode());
351
352 assertNotEquals(parser1, new Object());
353 }
354
355 @Test
356 void testISO8601TimeZoneVariants() throws Exception {
357 final Date date = Date.from(Instant.parse("2026-01-17T04:30:00Z"));
358 final TimeZone timeZone = TimeZone.getTimeZone("UTC");
359 assertEquals(date, new FastDateParser("yyyy-MM-dd'T'HH:mm:ssXXX", timeZone, Locale.US).parse("2026-01-17T10:00:00+05:30"));
360 assertEquals(date, new FastDateParser("yyyy-MM-dd'T'HH:mm:ssXX", timeZone, Locale.US).parse("2026-01-17T10:00:00+0530"));
361 }
362
363 @Test
364 void testJpLocales() {
365
366 final Calendar cal = Calendar.getInstance(TimeZones.GMT);
367 cal.clear();
368 cal.set(2003, Calendar.FEBRUARY, 10);
369 cal.set(Calendar.ERA, GregorianCalendar.BC);
370
371 final Locale locale = LocaleUtils.toLocale("zh");
372
373
374 final SimpleDateFormat sdf = new SimpleDateFormat(LONG_FORMAT, locale);
375 final DateParser fdf = getInstance(LONG_FORMAT, locale);
376
377
378 checkParse(locale, cal, sdf, fdf);
379 }
380
381 @ParameterizedTest
382 @MethodSource(DATE_PARSER_PARAMETERS)
383 void testLANG_831(final TriFunction<String, TimeZone, Locale, DateParser> dpProvider) throws Exception {
384 testSdfAndFdp(dpProvider, "M E", "3 Tue", true);
385 }
386
387 @ParameterizedTest
388 @MethodSource(DATE_PARSER_PARAMETERS)
389 void testLANG_832(final TriFunction<String, TimeZone, Locale, DateParser> dpProvider) throws Exception {
390 testSdfAndFdp(dpProvider, "'d'd", "d3", false);
391 testSdfAndFdp(dpProvider, "'d'd'", "d3", true);
392 }
393
394 @ParameterizedTest
395 @MethodSource(DATE_PARSER_PARAMETERS)
396 void testLang1121(final TriFunction<String, TimeZone, Locale, DateParser> dpProvider) throws ParseException {
397 final TimeZone kst = TimeZones.getTimeZone("KST");
398 final DateParser fdp = getInstance(dpProvider, "yyyyMMdd", kst, Locale.KOREA);
399
400 assertThrows(ParseException.class, () -> fdp.parse("2015"));
401
402
403 Date actual = fdp.parse("20150429");
404 final Calendar cal = Calendar.getInstance(kst, Locale.KOREA);
405 cal.clear();
406 cal.set(2015, Calendar.APRIL, 29);
407 Date expected = cal.getTime();
408 assertEquals(expected, actual);
409
410 final SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd", Locale.KOREA);
411 sdf.setTimeZone(kst);
412 expected = sdf.parse("20150429113100");
413
414
415 actual = fdp.parse("20150429113100");
416 assertEquals(expected, actual);
417 }
418
419 @ParameterizedTest
420 @MethodSource(DATE_PARSER_PARAMETERS)
421 void testLang1380(final TriFunction<String, TimeZone, Locale, DateParser> dpProvider) throws ParseException {
422 final Calendar expected = Calendar.getInstance(TimeZones.GMT, Locale.FRANCE);
423 expected.clear();
424 expected.set(2014, Calendar.APRIL, 14);
425
426 final DateParser fdp = getInstance(dpProvider, "dd MMM yyyy", TimeZones.GMT, Locale.FRANCE);
427 assertEquals(expected.getTime(), fdp.parse("14 avril 2014"));
428 assertEquals(expected.getTime(), fdp.parse("14 avr. 2014"));
429 assertEquals(expected.getTime(), fdp.parse("14 avr 2014"));
430 }
431
432 @Test
433 void testLang303() throws ParseException {
434 DateParser parser = getInstance(YMD_SLASH);
435 final Calendar cal = Calendar.getInstance();
436 cal.set(2004, Calendar.DECEMBER, 31);
437
438 final Date date = parser.parse("2004/11/31");
439
440 parser = SerializationUtils.deserialize(SerializationUtils.serialize((Serializable) parser));
441 assertEquals(date, parser.parse("2004/11/31"));
442 }
443
444 @Test
445 void testLang538() throws ParseException {
446 final DateParser parser = getInstance("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", TimeZones.GMT);
447
448 final Calendar cal = Calendar.getInstance(TimeZones.getTimeZone("GMT-8"));
449 cal.clear();
450 cal.set(2009, Calendar.OCTOBER, 16, 8, 42, 16);
451
452 assertEquals(cal.getTime(), parser.parse("2009-10-16T16:42:16.000Z"));
453 }
454
455 @ParameterizedTest
456 @MethodSource(DATE_PARSER_PARAMETERS)
457 void testLang996(final TriFunction<String, TimeZone, Locale, DateParser> dpProvider) throws ParseException {
458 final Calendar expected = Calendar.getInstance(NEW_YORK, Locale.US);
459 expected.clear();
460 expected.set(2014, Calendar.MAY, 14);
461
462 final DateParser fdp = getInstance(dpProvider, "ddMMMyyyy", NEW_YORK, Locale.US);
463 assertEquals(expected.getTime(), fdp.parse("14may2014"));
464 assertEquals(expected.getTime(), fdp.parse("14MAY2014"));
465 assertEquals(expected.getTime(), fdp.parse("14May2014"));
466 }
467
468 @Test
469 void testLocaleMatches() {
470 final DateParser parser = getInstance(yMdHmsSZ, SWEDEN);
471 assertEquals(SWEDEN, parser.getLocale());
472 }
473
474
475
476
477
478
479 @Test
480 void testLowYearPadding() throws ParseException {
481 final DateParser parser = getInstance(YMD_SLASH);
482 final Calendar cal = Calendar.getInstance();
483 cal.clear();
484
485 cal.set(1, Calendar.JANUARY, 1);
486 assertEquals(cal.getTime(), parser.parse("0001/01/01"));
487 cal.set(10, Calendar.JANUARY, 1);
488 assertEquals(cal.getTime(), parser.parse("0010/01/01"));
489 cal.set(100, Calendar.JANUARY, 1);
490 assertEquals(cal.getTime(), parser.parse("0100/01/01"));
491 cal.set(999, Calendar.JANUARY, 1);
492 assertEquals(cal.getTime(), parser.parse("0999/01/01"));
493 }
494
495 @Test
496 void testMilleniumBug() throws ParseException {
497 final DateParser parser = getInstance(DMY_DOT);
498 final Calendar cal = Calendar.getInstance();
499 cal.clear();
500
501 cal.set(1000, Calendar.JANUARY, 1);
502 assertEquals(cal.getTime(), parser.parse("01.01.1000"));
503 }
504
505 @ParameterizedTest
506 @MethodSource(DATE_PARSER_PARAMETERS)
507 void testParseLongShort(final TriFunction<String, TimeZone, Locale, DateParser> dpProvider)
508 throws ParseException {
509 final Calendar cal = Calendar.getInstance(NEW_YORK, Locale.US);
510 cal.clear();
511 cal.set(2003, Calendar.FEBRUARY, 10, 15, 33, 20);
512 cal.set(Calendar.MILLISECOND, 989);
513 cal.setTimeZone(NEW_YORK);
514
515 DateParser fdf = getInstance(dpProvider, "yyyy GGGG MMMM dddd aaaa EEEE HHHH mmmm ssss SSSS ZZZZ", NEW_YORK,
516 Locale.US);
517
518 assertEquals(cal.getTime(), fdf.parse("2003 AD February 0010 PM Monday 0015 0033 0020 0989 GMT-05:00"));
519 cal.set(Calendar.ERA, GregorianCalendar.BC);
520
521 final Date parse = fdf.parse("2003 BC February 0010 PM Saturday 0015 0033 0020 0989 GMT-05:00");
522 assertEquals(cal.getTime(), parse);
523
524 fdf = getInstance(null, "y G M d a E H m s S Z", NEW_YORK, Locale.US);
525 assertEquals(cal.getTime(), fdf.parse("03 BC 2 10 PM Sat 15 33 20 989 -0500"));
526
527 cal.set(Calendar.ERA, GregorianCalendar.AD);
528 assertEquals(cal.getTime(), fdf.parse("03 AD 2 10 PM Saturday 15 33 20 989 -0500"));
529 }
530
531 @ParameterizedTest
532 @MethodSource(DATE_PARSER_PARAMETERS)
533 void testParseNumerics(final TriFunction<String, TimeZone, Locale, DateParser> dpProvider)
534 throws ParseException {
535 final Calendar cal = Calendar.getInstance(NEW_YORK, Locale.US);
536 cal.clear();
537 cal.set(2003, Calendar.FEBRUARY, 10, 15, 33, 20);
538 cal.set(Calendar.MILLISECOND, 989);
539
540 final DateParser fdf = getInstance(dpProvider, "yyyyMMddHHmmssSSS", NEW_YORK, Locale.US);
541 assertEquals(cal.getTime(), fdf.parse("20030210153320989"));
542 }
543
544 @Test
545 void testParseOffset() {
546 final DateParser parser = getInstance(YMD_SLASH);
547 final Date date = parser.parse("Today is 2015/07/04", new ParsePosition(9));
548
549 final Calendar cal = Calendar.getInstance();
550 cal.clear();
551 cal.set(2015, Calendar.JULY, 4);
552 assertEquals(cal.getTime(), date);
553 }
554
555 @CartesianTest
556 @CartesianTest.MethodFactory("testParsesFactory")
557
558 void testParses(final String format, final Locale locale, final TimeZone timeZone, final int year) throws Exception {
559 final Calendar cal = getEraStart(year, timeZone, locale);
560 final Date centuryStart = cal.getTime();
561 cal.set(Calendar.MONTH, 1);
562 cal.set(Calendar.DAY_OF_MONTH, 10);
563 final Date in = cal.getTime();
564 final FastDateParser fastDateParser = new FastDateParser(format, timeZone, locale, centuryStart);
565 validateSdfFormatFdpParseEquality(format, locale, timeZone, fastDateParser, in, year, centuryStart);
566 }
567
568
569
570
571 @Test
572 void testParsesKnownJava16Ea25Failure() throws Exception {
573 final String format = LONG_FORMAT;
574 final int year = 2003;
575 final Locale locale = new Locale.Builder().setLanguage("sq").setRegion("MK").build();
576 assertEquals("sq_MK", locale.toString());
577 assertNotNull(locale);
578 final TimeZone timeZone = NEW_YORK;
579 final Calendar cal = getEraStart(year, timeZone, locale);
580 final Date centuryStart = cal.getTime();
581
582 cal.set(Calendar.MONTH, 1);
583 cal.set(Calendar.DAY_OF_MONTH, 10);
584 final Date in = cal.getTime();
585
586 final FastDateParser fastDateParser = new FastDateParser(format, timeZone, locale, centuryStart);
587 validateSdfFormatFdpParseEquality(format, locale, timeZone, fastDateParser, in, year, centuryStart);
588 }
589
590 @ParameterizedTest
591 @MethodSource(DATE_PARSER_PARAMETERS)
592 void testParseZone(final TriFunction<String, TimeZone, Locale, DateParser> dpProvider)
593 throws ParseException {
594 final Calendar cal = Calendar.getInstance(NEW_YORK, Locale.US);
595 cal.clear();
596 cal.set(2003, Calendar.JULY, 10, 16, 33, 20);
597
598 final DateParser fdf = getInstance(dpProvider, yMdHmsSZ, NEW_YORK, Locale.US);
599
600 assertEquals(cal.getTime(), fdf.parse("2003-07-10T15:33:20.000 -0500"));
601 assertEquals(cal.getTime(), fdf.parse("2003-07-10T15:33:20.000 GMT-05:00"));
602 assertEquals(cal.getTime(), fdf.parse("2003-07-10T16:33:20.000 Eastern Daylight Time"));
603 assertEquals(cal.getTime(), fdf.parse("2003-07-10T16:33:20.000 EDT"));
604
605 cal.setTimeZone(TimeZones.getTimeZone("GMT-3"));
606 cal.set(2003, Calendar.FEBRUARY, 10, 9, 0, 0);
607
608 assertEquals(cal.getTime(), fdf.parse("2003-02-10T09:00:00.000 -0300"));
609
610 cal.setTimeZone(TimeZones.getTimeZone("GMT+5"));
611 cal.set(2003, Calendar.FEBRUARY, 10, 15, 5, 6);
612
613 assertEquals(cal.getTime(), fdf.parse("2003-02-10T15:05:06.000 +0500"));
614 }
615
616 @Test
617 void testPatternMatches() {
618 final DateParser parser = getInstance(yMdHmsSZ);
619 assertEquals(yMdHmsSZ, parser.getPattern());
620 }
621
622 @ParameterizedTest
623 @MethodSource(DATE_PARSER_PARAMETERS)
624 void testQuotes(final TriFunction<String, TimeZone, Locale, DateParser> dpProvider) throws ParseException {
625 final Calendar cal = Calendar.getInstance(NEW_YORK, Locale.US);
626 cal.clear();
627 cal.set(2003, Calendar.FEBRUARY, 10, 15, 33, 20);
628 cal.set(Calendar.MILLISECOND, 989);
629
630 final DateParser fdf = getInstance(dpProvider, "''yyyyMMdd'A''B'HHmmssSSS''", NEW_YORK, Locale.US);
631 assertEquals(cal.getTime(), fdf.parse("'20030210A'B153320989'"));
632 }
633
634 private void testSdfAndFdp(final TriFunction<String, TimeZone, Locale, DateParser> dbProvider, final String format,
635 final String date, final boolean shouldFail) throws Exception {
636 Date dfdp = null;
637 Date dsdf = null;
638 Throwable f = null;
639 Throwable s = null;
640
641 try {
642 final SimpleDateFormat sdf = new SimpleDateFormat(format, Locale.US);
643 sdf.setTimeZone(NEW_YORK);
644 dsdf = sdf.parse(date);
645 assertFalse(shouldFail, "Expected SDF failure, but got " + dsdf + " for [" + format + ", " + date + "]");
646 } catch (final Exception e) {
647 s = e;
648 if (!shouldFail) {
649 throw e;
650 }
651 }
652
653 try {
654 final DateParser fdp = getInstance(dbProvider, format, NEW_YORK, Locale.US);
655 dfdp = fdp.parse(date);
656 assertFalse(shouldFail, "Expected FDF failure, but got " + dfdp + " for [" + format + ", " + date + "]");
657 } catch (final Exception e) {
658 f = e;
659 if (!shouldFail) {
660 throw e;
661 }
662 }
663
664 assertEquals(f == null, s == null, "Should both or neither throw Exceptions");
665 assertEquals(dsdf, dfdp, "Parsed dates should be equal");
666 }
667
668
669
670
671
672
673 @Test
674 void testShortDateStyleWithLocales() throws ParseException {
675 DateParser fdf = getDateInstance(FastDateFormat.SHORT, Locale.US);
676 final Calendar cal = Calendar.getInstance();
677 cal.clear();
678
679 cal.set(2004, Calendar.FEBRUARY, 3);
680 assertEquals(cal.getTime(), fdf.parse("2/3/04"));
681
682 fdf = getDateInstance(FastDateFormat.SHORT, SWEDEN);
683 assertEquals(cal.getTime(), fdf.parse("2004-02-03"));
684 }
685
686 @ParameterizedTest
687 @MethodSource(DATE_PARSER_PARAMETERS)
688 void testSpecialCharacters(final TriFunction<String, TimeZone, Locale, DateParser> dpProvider)
689 throws Exception {
690 testSdfAndFdp(dpProvider, "q", "", true);
691 testSdfAndFdp(dpProvider, "Q", "", true);
692 testSdfAndFdp(dpProvider, "$", "$", false);
693 testSdfAndFdp(dpProvider, "?.d", "?.12", false);
694 testSdfAndFdp(dpProvider, "''yyyyMMdd'A''B'HHmmssSSS''", "'20030210A'B153320989'", false);
695 testSdfAndFdp(dpProvider, "''''yyyyMMdd'A''B'HHmmssSSS''", "''20030210A'B153320989'", false);
696 testSdfAndFdp(dpProvider, "'$\\Ed'", "$\\Ed", false);
697
698
699 testSdfAndFdp(dpProvider, "'QED'", "QED", false);
700 testSdfAndFdp(dpProvider, "'QED'", "qed", true);
701
702 testSdfAndFdp(dpProvider, "yyyy-MM-dd 'QED'", "2003-02-10 QED", false);
703 testSdfAndFdp(dpProvider, "yyyy-MM-dd 'QED'", "2003-02-10 qed", true);
704 }
705
706 @Test
707 void testTimeZoneMatches() {
708 final DateParser parser = getInstance(yMdHmsSZ, REYKJAVIK);
709 assertEquals(REYKJAVIK, parser.getTimeZone());
710 }
711
712 @Test
713 void testToStringContainsName() {
714 final DateParser parser = getInstance(YMD_SLASH);
715 assertTrue(parser.toString().startsWith("FastDate"));
716 }
717
718
719
720 @ParameterizedTest
721 @MethodSource("org.apache.commons.lang3.LocaleUtils#availableLocaleList()")
722 void testTzParses(final Locale locale) throws Exception {
723
724 final FastDateParser fdp = new FastDateParser("yyyy/MM/dd z", TimeZone.getDefault(), locale);
725 for (final TimeZone timeZone : new TimeZone[] { NEW_YORK, REYKJAVIK, TimeZones.GMT }) {
726 final Calendar cal = Calendar.getInstance(timeZone, locale);
727 cal.clear();
728 cal.set(Calendar.YEAR, 2000);
729 cal.set(Calendar.MONTH, 1);
730 cal.set(Calendar.DAY_OF_MONTH, 10);
731 final Date expected = cal.getTime();
732 final Date actual = fdp.parse("2000/02/10 " + timeZone.getDisplayName(locale));
733 assertEquals(expected, actual, "timeZone:" + timeZone.getID() + " locale:" + locale.getDisplayName());
734 }
735 }
736
737 private void validateSdfFormatFdpParseEquality(final String formatStr, final Locale locale, final TimeZone timeZone,
738 final FastDateParser fastDateParser, final Date inDate, final int year, final Date csDate) throws ParseException {
739 final SimpleDateFormat sdf = new SimpleDateFormat(formatStr, locale);
740 sdf.setTimeZone(timeZone);
741 if (formatStr.equals(SHORT_FORMAT)) {
742 sdf.set2DigitYearStart(csDate);
743 }
744 final String fmt = sdf.format(inDate);
745
746
747 try {
748 final Date out = fastDateParser.parse(fmt);
749 assertEquals(inDate, out, "format: '" + formatStr + "', locale: '" + locale + "', time zone: '"
750 + timeZone.getID() + "', year: " + year + ", parse: '" + fmt);
751 } catch (final ParseException e) {
752 LocaleProblems.assumeLocaleSupportedB(locale, e);
753 if (year >= 1868 || !locale.getCountry().equals("JP")) {
754
755 throw new AssertionFailedError("locale " + locale, e);
756 }
757 }
758 }
759 }
760