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 java.io.IOException;
20 import java.io.ObjectInputStream;
21 import java.io.Serializable;
22 import java.text.DateFormatSymbols;
23 import java.text.ParseException;
24 import java.text.ParsePosition;
25 import java.text.SimpleDateFormat;
26 import java.util.ArrayList;
27 import java.util.Calendar;
28 import java.util.Comparator;
29 import java.util.Date;
30 import java.util.HashMap;
31 import java.util.List;
32 import java.util.ListIterator;
33 import java.util.Locale;
34 import java.util.Map;
35 import java.util.Objects;
36 import java.util.Set;
37 import java.util.TimeZone;
38 import java.util.TreeSet;
39 import java.util.concurrent.ConcurrentHashMap;
40 import java.util.concurrent.ConcurrentMap;
41 import java.util.regex.Matcher;
42 import java.util.regex.Pattern;
43
44 import org.apache.commons.lang3.LocaleUtils;
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82 public class FastDateParser implements DateParser, Serializable {
83
84
85
86
87 private static final class CaseInsensitiveTextStrategy extends PatternStrategy {
88 private final int field;
89 final Locale locale;
90 private final Map<String, Integer> lKeyValues;
91
92
93
94
95
96
97
98
99 CaseInsensitiveTextStrategy(final int field, final Calendar definingCalendar, final Locale locale) {
100 this.field = field;
101 this.locale = LocaleUtils.toLocale(locale);
102
103 final StringBuilder regex = new StringBuilder();
104 regex.append("((?iu)");
105 lKeyValues = appendDisplayNames(definingCalendar, locale, field, regex);
106 regex.setLength(regex.length() - 1);
107 regex.append(")");
108 createPattern(regex);
109 }
110
111
112
113
114 @Override
115 void setCalendar(final FastDateParser parser, final Calendar calendar, final String value) {
116 final String lowerCase = value.toLowerCase(locale);
117 Integer iVal = lKeyValues.get(lowerCase);
118 if (iVal == null) {
119
120 iVal = lKeyValues.get(lowerCase + '.');
121 }
122
123 if (Calendar.AM_PM != this.field || iVal <= 1) {
124 calendar.set(field, iVal.intValue());
125 }
126 }
127
128
129
130
131
132
133 @Override
134 public String toString() {
135 return "CaseInsensitiveTextStrategy [field=" + field + ", locale=" + locale + ", lKeyValues=" + lKeyValues + ", pattern=" + pattern + "]";
136 }
137 }
138
139
140
141
142 private static final class CopyQuotedStrategy extends Strategy {
143
144 private final String formatField;
145
146
147
148
149
150
151 CopyQuotedStrategy(final String formatField) {
152 this.formatField = formatField;
153 }
154
155
156
157
158 @Override
159 boolean isNumber() {
160 return false;
161 }
162
163 @Override
164 boolean parse(final FastDateParser parser, final Calendar calendar, final String source, final ParsePosition pos, final int maxWidth) {
165 for (int idx = 0; idx < formatField.length(); ++idx) {
166 final int sIdx = idx + pos.getIndex();
167 if (sIdx == source.length()) {
168 pos.setErrorIndex(sIdx);
169 return false;
170 }
171 if (formatField.charAt(idx) != source.charAt(sIdx)) {
172 pos.setErrorIndex(sIdx);
173 return false;
174 }
175 }
176 pos.setIndex(formatField.length() + pos.getIndex());
177 return true;
178 }
179
180
181
182
183
184
185 @Override
186 public String toString() {
187 return "CopyQuotedStrategy [formatField=" + formatField + "]";
188 }
189 }
190
191 private static final class ISO8601TimeZoneStrategy extends PatternStrategy {
192
193
194 private static final Strategy ISO_8601_1_STRATEGY = new ISO8601TimeZoneStrategy("(Z|(?:[+-]\\d{2}))");
195
196 private static final Strategy ISO_8601_2_STRATEGY = new ISO8601TimeZoneStrategy("(Z|(?:[+-]\\d{2}\\d{2}))");
197
198 private static final Strategy ISO_8601_3_STRATEGY = new ISO8601TimeZoneStrategy("(Z|(?:[+-]\\d{2}(?::)\\d{2}))");
199
200
201
202
203
204
205
206 static Strategy getStrategy(final int tokenLen) {
207 switch (tokenLen) {
208 case 1:
209 return ISO_8601_1_STRATEGY;
210 case 2:
211 return ISO_8601_2_STRATEGY;
212 case 3:
213 return ISO_8601_3_STRATEGY;
214 default:
215 throw new IllegalArgumentException("invalid number of X");
216 }
217 }
218
219
220
221
222
223 ISO8601TimeZoneStrategy(final String pattern) {
224 createPattern(pattern);
225 }
226
227
228
229
230 @Override
231 void setCalendar(final FastDateParser parser, final Calendar calendar, final String value) {
232 calendar.setTimeZone(FastTimeZone.getGmtTimeZone(value));
233 }
234 }
235
236
237
238
239 private static class NumberStrategy extends Strategy {
240
241 private final int field;
242
243
244
245
246
247
248 NumberStrategy(final int field) {
249 this.field = field;
250 }
251
252
253
254
255 @Override
256 boolean isNumber() {
257 return true;
258 }
259
260
261
262
263
264
265
266
267 int modify(final FastDateParser parser, final int iValue) {
268 return iValue;
269 }
270
271 @Override
272 boolean parse(final FastDateParser parser, final Calendar calendar, final String source, final ParsePosition pos, final int maxWidth) {
273 int idx = pos.getIndex();
274 int last = source.length();
275
276 if (maxWidth == 0) {
277
278 for (; idx < last; ++idx) {
279 final char c = source.charAt(idx);
280 if (!Character.isWhitespace(c)) {
281 break;
282 }
283 }
284 pos.setIndex(idx);
285 } else {
286 final int end = idx + maxWidth;
287 if (last > end) {
288 last = end;
289 }
290 }
291
292 for (; idx < last; ++idx) {
293 final char c = source.charAt(idx);
294 if (!Character.isDigit(c)) {
295 break;
296 }
297 }
298
299 if (pos.getIndex() == idx) {
300 pos.setErrorIndex(idx);
301 return false;
302 }
303
304 final int value = Integer.parseInt(source.substring(pos.getIndex(), idx));
305 pos.setIndex(idx);
306
307 calendar.set(field, modify(parser, value));
308 return true;
309 }
310
311
312
313
314
315
316 @Override
317 public String toString() {
318 return "NumberStrategy [field=" + field + "]";
319 }
320 }
321
322
323
324
325 private abstract static class PatternStrategy extends Strategy {
326
327 Pattern pattern;
328
329 void createPattern(final String regex) {
330 this.pattern = Pattern.compile(regex);
331 }
332
333 void createPattern(final StringBuilder regex) {
334 createPattern(regex.toString());
335 }
336
337
338
339
340
341
342 @Override
343 boolean isNumber() {
344 return false;
345 }
346
347 @Override
348 boolean parse(final FastDateParser parser, final Calendar calendar, final String source, final ParsePosition pos, final int maxWidth) {
349 final Matcher matcher = pattern.matcher(source.substring(pos.getIndex()));
350 if (!matcher.lookingAt()) {
351 pos.setErrorIndex(pos.getIndex());
352 return false;
353 }
354 pos.setIndex(pos.getIndex() + matcher.end(1));
355 setCalendar(parser, calendar, matcher.group(1));
356 return true;
357 }
358
359 abstract void setCalendar(FastDateParser parser, Calendar calendar, String value);
360
361
362
363
364
365
366 @Override
367 public String toString() {
368 return getClass().getSimpleName() + " [pattern=" + pattern + "]";
369 }
370
371 }
372
373
374
375
376 private abstract static class Strategy {
377
378
379
380
381
382
383 boolean isNumber() {
384 return false;
385 }
386
387 abstract boolean parse(FastDateParser parser, Calendar calendar, String source, ParsePosition pos, int maxWidth);
388 }
389
390
391
392
393 private static final class StrategyAndWidth {
394
395 final Strategy strategy;
396 final int width;
397
398 StrategyAndWidth(final Strategy strategy, final int width) {
399 this.strategy = Objects.requireNonNull(strategy, "strategy");
400 this.width = width;
401 }
402
403 int getMaxWidth(final ListIterator<StrategyAndWidth> lt) {
404 if (!strategy.isNumber() || !lt.hasNext()) {
405 return 0;
406 }
407 final Strategy nextStrategy = lt.next().strategy;
408 lt.previous();
409 return nextStrategy.isNumber() ? width : 0;
410 }
411
412 @Override
413 public String toString() {
414 return "StrategyAndWidth [strategy=" + strategy + ", width=" + width + "]";
415 }
416 }
417
418
419
420
421 private final class StrategyParser {
422 private final Calendar definingCalendar;
423 private int currentIdx;
424
425 StrategyParser(final Calendar definingCalendar) {
426 this.definingCalendar = Objects.requireNonNull(definingCalendar, "definingCalendar");
427 }
428
429 StrategyAndWidth getNextStrategy() {
430 if (currentIdx >= pattern.length()) {
431 return null;
432 }
433
434 final char c = pattern.charAt(currentIdx);
435 if (isFormatLetter(c)) {
436 return letterPattern(c);
437 }
438 return literal();
439 }
440
441 private StrategyAndWidth letterPattern(final char c) {
442 final int begin = currentIdx;
443 while (++currentIdx < pattern.length()) {
444 if (pattern.charAt(currentIdx) != c) {
445 break;
446 }
447 }
448
449 final int width = currentIdx - begin;
450 return new StrategyAndWidth(getStrategy(c, width, definingCalendar), width);
451 }
452
453 private StrategyAndWidth literal() {
454 boolean activeQuote = false;
455
456 final StringBuilder sb = new StringBuilder();
457 while (currentIdx < pattern.length()) {
458 final char c = pattern.charAt(currentIdx);
459 if (!activeQuote && isFormatLetter(c)) {
460 break;
461 }
462 if (c == '\'' && (++currentIdx == pattern.length() || pattern.charAt(currentIdx) != '\'')) {
463 activeQuote = !activeQuote;
464 continue;
465 }
466 ++currentIdx;
467 sb.append(c);
468 }
469
470 if (activeQuote) {
471 throw new IllegalArgumentException("Unterminated quote");
472 }
473
474 final String formatField = sb.toString();
475 return new StrategyAndWidth(new CopyQuotedStrategy(formatField), formatField.length());
476 }
477 }
478
479
480
481
482 static class TimeZoneStrategy extends PatternStrategy {
483 private static final class TzInfo {
484 final TimeZone zone;
485 final int dstOffset;
486
487 TzInfo(final TimeZone tz, final boolean useDst) {
488 zone = tz;
489 dstOffset = useDst ? tz.getDSTSavings() : 0;
490 }
491
492 @Override
493 public String toString() {
494 return "TzInfo [zone=" + zone + ", dstOffset=" + dstOffset + "]";
495 }
496 }
497 private static final String RFC_822_TIME_ZONE = "[+-]\\d{4}";
498
499 private static final String GMT_OPTION = TimeZones.GMT_ID + "[+-]\\d{1,2}:\\d{2}";
500
501
502
503 private static final int ID = 0;
504
505 private final Locale locale;
506
507 private final Map<String, TzInfo> tzNames = new HashMap<>();
508
509
510
511
512
513
514 TimeZoneStrategy(final Locale locale) {
515 this.locale = LocaleUtils.toLocale(locale);
516
517 final StringBuilder sb = new StringBuilder();
518 sb.append("((?iu)" + RFC_822_TIME_ZONE + "|" + GMT_OPTION);
519
520 final Set<String> sorted = new TreeSet<>(LONGER_FIRST_LOWERCASE);
521
522
523
524 final String[][] zones = DateFormatSymbols.getInstance(locale).getZoneStrings();
525 for (final String[] zoneNames : zones) {
526
527 final String tzId = zoneNames[ID];
528 if (tzId.equalsIgnoreCase(TimeZones.GMT_ID)) {
529 continue;
530 }
531 final TimeZone tz = TimeZone.getTimeZone(tzId);
532
533
534 final TzInfo standard = new TzInfo(tz, false);
535 TzInfo tzInfo = standard;
536 for (int i = 1; i < zoneNames.length; ++i) {
537 switch (i) {
538 case 3:
539
540 tzInfo = new TzInfo(tz, true);
541 break;
542 case 5:
543 tzInfo = standard;
544 break;
545 default:
546 break;
547 }
548 final String zoneName = zoneNames[i];
549 if (zoneName != null) {
550 final String key = zoneName.toLowerCase(locale);
551
552
553 if (sorted.add(key)) {
554 tzNames.put(key, tzInfo);
555 }
556 }
557 }
558 }
559
560
561 for (final String tzId : TimeZone.getAvailableIDs()) {
562 if (tzId.equalsIgnoreCase(TimeZones.GMT_ID)) {
563 continue;
564 }
565 final TimeZone tz = TimeZone.getTimeZone(tzId);
566 final String zoneName = tz.getDisplayName(locale);
567 final String key = zoneName.toLowerCase(locale);
568 if (sorted.add(key)) {
569 tzNames.put(key, new TzInfo(tz, tz.observesDaylightTime()));
570 }
571 }
572
573
574
575 sorted.forEach(zoneName -> simpleQuote(sb.append('|'), zoneName));
576 sb.append(")");
577 createPattern(sb);
578 }
579
580
581
582
583 @Override
584 void setCalendar(final FastDateParser parser, final Calendar calendar, final String timeZone) {
585 final TimeZone tz = FastTimeZone.getGmtTimeZone(timeZone);
586 if (tz != null) {
587 calendar.setTimeZone(tz);
588 } else {
589 final String lowerCase = timeZone.toLowerCase(locale);
590 TzInfo tzInfo = tzNames.get(lowerCase);
591 if (tzInfo == null) {
592
593 tzInfo = tzNames.get(lowerCase + '.');
594 }
595 calendar.set(Calendar.DST_OFFSET, tzInfo.dstOffset);
596 calendar.set(Calendar.ZONE_OFFSET, tzInfo.zone.getRawOffset());
597 }
598 }
599
600
601
602
603
604
605 @Override
606 public String toString() {
607 return "TimeZoneStrategy [locale=" + locale + ", tzNames=" + tzNames + ", pattern=" + pattern + "]";
608 }
609
610 }
611
612
613
614
615
616
617 private static final long serialVersionUID = 3L;
618
619 static final Locale JAPANESE_IMPERIAL = new Locale("ja", "JP", "JP");
620
621
622
623
624
625 private static final Comparator<String> LONGER_FIRST_LOWERCASE = Comparator.reverseOrder();
626
627
628
629 @SuppressWarnings("unchecked")
630 private static final ConcurrentMap<Locale, Strategy>[] caches = new ConcurrentMap[Calendar.FIELD_COUNT];
631
632 private static final Strategy ABBREVIATED_YEAR_STRATEGY = new NumberStrategy(Calendar.YEAR) {
633
634
635
636 @Override
637 int modify(final FastDateParser parser, final int iValue) {
638 return iValue < 100 ? parser.adjustYear(iValue) : iValue;
639 }
640 };
641
642 private static final Strategy NUMBER_MONTH_STRATEGY = new NumberStrategy(Calendar.MONTH) {
643 @Override
644 int modify(final FastDateParser parser, final int iValue) {
645 return iValue - 1;
646 }
647 };
648
649 private static final Strategy LITERAL_YEAR_STRATEGY = new NumberStrategy(Calendar.YEAR);
650
651 private static final Strategy WEEK_OF_YEAR_STRATEGY = new NumberStrategy(Calendar.WEEK_OF_YEAR);
652
653 private static final Strategy WEEK_OF_MONTH_STRATEGY = new NumberStrategy(Calendar.WEEK_OF_MONTH);
654
655 private static final Strategy DAY_OF_YEAR_STRATEGY = new NumberStrategy(Calendar.DAY_OF_YEAR);
656
657 private static final Strategy DAY_OF_MONTH_STRATEGY = new NumberStrategy(Calendar.DAY_OF_MONTH);
658
659 private static final Strategy DAY_OF_WEEK_STRATEGY = new NumberStrategy(Calendar.DAY_OF_WEEK) {
660 @Override
661 int modify(final FastDateParser parser, final int iValue) {
662 return iValue == 7 ? Calendar.SUNDAY : iValue + 1;
663 }
664 };
665
666 private static final Strategy DAY_OF_WEEK_IN_MONTH_STRATEGY = new NumberStrategy(Calendar.DAY_OF_WEEK_IN_MONTH);
667
668 private static final Strategy HOUR_OF_DAY_STRATEGY = new NumberStrategy(Calendar.HOUR_OF_DAY);
669
670 private static final Strategy HOUR24_OF_DAY_STRATEGY = new NumberStrategy(Calendar.HOUR_OF_DAY) {
671 @Override
672 int modify(final FastDateParser parser, final int iValue) {
673 return iValue == 24 ? 0 : iValue;
674 }
675 };
676
677 private static final Strategy HOUR12_STRATEGY = new NumberStrategy(Calendar.HOUR) {
678 @Override
679 int modify(final FastDateParser parser, final int iValue) {
680 return iValue == 12 ? 0 : iValue;
681 }
682 };
683
684 private static final Strategy HOUR_STRATEGY = new NumberStrategy(Calendar.HOUR);
685
686 private static final Strategy MINUTE_STRATEGY = new NumberStrategy(Calendar.MINUTE);
687
688 private static final Strategy SECOND_STRATEGY = new NumberStrategy(Calendar.SECOND);
689
690
691
692 private static final Strategy MILLISECOND_STRATEGY = new NumberStrategy(Calendar.MILLISECOND);
693
694
695
696
697
698
699
700
701
702
703 private static Map<String, Integer> appendDisplayNames(final Calendar calendar, final Locale locale, final int field, final StringBuilder regex) {
704 Objects.requireNonNull(calendar, "calendar");
705 final Map<String, Integer> values = new HashMap<>();
706 final Locale actualLocale = LocaleUtils.toLocale(locale);
707 final Map<String, Integer> displayNames = calendar.getDisplayNames(field, Calendar.ALL_STYLES, actualLocale);
708 final TreeSet<String> sorted = new TreeSet<>(LONGER_FIRST_LOWERCASE);
709 displayNames.forEach((k, v) -> {
710 final String keyLc = k.toLowerCase(actualLocale);
711 if (sorted.add(keyLc)) {
712 values.put(keyLc, v);
713 }
714 });
715 sorted.forEach(symbol -> simpleQuote(regex, symbol).append('|'));
716 return values;
717 }
718
719
720
721
722
723
724
725 private static ConcurrentMap<Locale, Strategy> getCache(final int field) {
726 synchronized (caches) {
727 if (caches[field] == null) {
728 caches[field] = new ConcurrentHashMap<>(3);
729 }
730 return caches[field];
731 }
732 }
733
734 private static boolean isFormatLetter(final char c) {
735 return c >= 'A' && c <= 'Z' || c >= 'a' && c <= 'z';
736 }
737
738 private static StringBuilder simpleQuote(final StringBuilder sb, final String value) {
739 for (int i = 0; i < value.length(); ++i) {
740 final char c = value.charAt(i);
741 switch (c) {
742 case '\\':
743 case '^':
744 case '$':
745 case '.':
746 case '|':
747 case '?':
748 case '*':
749 case '+':
750 case '(':
751 case ')':
752 case '[':
753 case '{':
754 sb.append('\\');
755 default:
756 sb.append(c);
757 }
758 }
759 if (sb.charAt(sb.length() - 1) == '.') {
760
761 sb.append('?');
762 }
763 return sb;
764 }
765
766
767 private final String pattern;
768
769
770 private final TimeZone timeZone;
771
772
773 private final Locale locale;
774
775
776
777
778 private final int century;
779
780
781
782
783 private final int startYear;
784
785
786 private transient List<StrategyAndWidth> patterns;
787
788
789
790
791
792
793
794
795
796
797
798 protected FastDateParser(final String pattern, final TimeZone timeZone, final Locale locale) {
799 this(pattern, timeZone, locale, null);
800 }
801
802
803
804
805
806
807
808
809
810
811
812 protected FastDateParser(final String pattern, final TimeZone timeZone, final Locale locale, final Date centuryStart) {
813 this.pattern = Objects.requireNonNull(pattern, "pattern");
814 this.timeZone = Objects.requireNonNull(timeZone, "timeZone");
815 this.locale = LocaleUtils.toLocale(locale);
816
817 final Calendar definingCalendar = Calendar.getInstance(timeZone, this.locale);
818
819 final int centuryStartYear;
820 if (centuryStart != null) {
821 definingCalendar.setTime(centuryStart);
822 centuryStartYear = definingCalendar.get(Calendar.YEAR);
823 } else if (this.locale.equals(JAPANESE_IMPERIAL)) {
824 centuryStartYear = 0;
825 } else {
826
827 definingCalendar.setTime(new Date());
828 centuryStartYear = definingCalendar.get(Calendar.YEAR) - 80;
829 }
830 century = centuryStartYear / 100 * 100;
831 startYear = centuryStartYear - century;
832
833 init(definingCalendar);
834 }
835
836
837
838
839
840
841
842 private int adjustYear(final int twoDigitYear) {
843 final int trial = century + twoDigitYear;
844 return twoDigitYear >= startYear ? trial : trial + 100;
845 }
846
847
848
849
850
851
852
853
854 @Override
855 public boolean equals(final Object obj) {
856 if (!(obj instanceof FastDateParser)) {
857 return false;
858 }
859 final FastDateParser other = (FastDateParser) obj;
860 return pattern.equals(other.pattern) && timeZone.equals(other.timeZone) && locale.equals(other.locale);
861 }
862
863
864
865
866
867
868 @Override
869 public Locale getLocale() {
870 return locale;
871 }
872
873
874
875
876
877
878
879
880 private Strategy getLocaleSpecificStrategy(final int field, final Calendar definingCalendar) {
881 final ConcurrentMap<Locale, Strategy> cache = getCache(field);
882 return cache.computeIfAbsent(locale,
883 k -> field == Calendar.ZONE_OFFSET ? new TimeZoneStrategy(locale) : new CaseInsensitiveTextStrategy(field, definingCalendar, locale));
884 }
885
886
887
888
889
890
891 @Override
892 public String getPattern() {
893 return pattern;
894 }
895
896
897
898
899
900
901
902
903 private Strategy getStrategy(final char f, final int width, final Calendar definingCalendar) {
904 switch (f) {
905 default:
906 throw new IllegalArgumentException("Format '" + f + "' not supported");
907 case 'D':
908 return DAY_OF_YEAR_STRATEGY;
909 case 'E':
910 return getLocaleSpecificStrategy(Calendar.DAY_OF_WEEK, definingCalendar);
911 case 'F':
912 return DAY_OF_WEEK_IN_MONTH_STRATEGY;
913 case 'G':
914 return getLocaleSpecificStrategy(Calendar.ERA, definingCalendar);
915 case 'H':
916 return HOUR_OF_DAY_STRATEGY;
917 case 'K':
918 return HOUR_STRATEGY;
919 case 'M':
920 case 'L':
921 return width >= 3 ? getLocaleSpecificStrategy(Calendar.MONTH, definingCalendar) : NUMBER_MONTH_STRATEGY;
922 case 'S':
923 return MILLISECOND_STRATEGY;
924 case 'W':
925 return WEEK_OF_MONTH_STRATEGY;
926 case 'a':
927 return getLocaleSpecificStrategy(Calendar.AM_PM, definingCalendar);
928 case 'd':
929 return DAY_OF_MONTH_STRATEGY;
930 case 'h':
931 return HOUR12_STRATEGY;
932 case 'k':
933 return HOUR24_OF_DAY_STRATEGY;
934 case 'm':
935 return MINUTE_STRATEGY;
936 case 's':
937 return SECOND_STRATEGY;
938 case 'u':
939 return DAY_OF_WEEK_STRATEGY;
940 case 'w':
941 return WEEK_OF_YEAR_STRATEGY;
942 case 'y':
943 case 'Y':
944 return width > 2 ? LITERAL_YEAR_STRATEGY : ABBREVIATED_YEAR_STRATEGY;
945 case 'X':
946 return ISO8601TimeZoneStrategy.getStrategy(width);
947 case 'Z':
948 if (width == 2) {
949 return ISO8601TimeZoneStrategy.ISO_8601_3_STRATEGY;
950 }
951
952 case 'z':
953 return getLocaleSpecificStrategy(Calendar.ZONE_OFFSET, definingCalendar);
954 }
955 }
956
957
958
959
960
961 @Override
962 public TimeZone getTimeZone() {
963 return timeZone;
964 }
965
966
967
968
969
970 @Override
971 public int hashCode() {
972 return pattern.hashCode() + 13 * (timeZone.hashCode() + 13 * locale.hashCode());
973 }
974
975
976
977
978
979 private void init(final Calendar definingCalendar) {
980 patterns = new ArrayList<>();
981
982 final StrategyParser strategyParser = new StrategyParser(definingCalendar);
983 for (;;) {
984 final StrategyAndWidth field = strategyParser.getNextStrategy();
985 if (field == null) {
986 break;
987 }
988 patterns.add(field);
989 }
990 }
991
992
993
994
995
996
997 @Override
998 public Date parse(final String source) throws ParseException {
999 final ParsePosition pp = new ParsePosition(0);
1000 final Date date = parse(source, pp);
1001 if (date == null) {
1002
1003 if (locale.equals(JAPANESE_IMPERIAL)) {
1004 throw new ParseException("(The " + locale + " locale does not support dates before 1868 AD)\nUnparseable date: \"" + source,
1005 pp.getErrorIndex());
1006 }
1007 throw new ParseException("Unparseable date: " + source, pp.getErrorIndex());
1008 }
1009 return date;
1010 }
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020 @Override
1021 public Date parse(final String source, final ParsePosition pos) {
1022
1023 final Calendar cal = Calendar.getInstance(timeZone, locale);
1024 cal.clear();
1025
1026 return parse(source, pos, cal) ? cal.getTime() : null;
1027 }
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039 @Override
1040 public boolean parse(final String source, final ParsePosition pos, final Calendar calendar) {
1041 final ListIterator<StrategyAndWidth> lt = patterns.listIterator();
1042 while (lt.hasNext()) {
1043 final StrategyAndWidth strategyAndWidth = lt.next();
1044 final int maxWidth = strategyAndWidth.getMaxWidth(lt);
1045 if (!strategyAndWidth.strategy.parse(this, calendar, source, pos, maxWidth)) {
1046 return false;
1047 }
1048 }
1049 return true;
1050 }
1051
1052
1053
1054
1055
1056
1057 @Override
1058 public Object parseObject(final String source) throws ParseException {
1059 return parse(source);
1060 }
1061
1062
1063
1064
1065
1066
1067 @Override
1068 public Object parseObject(final String source, final ParsePosition pos) {
1069 return parse(source, pos);
1070 }
1071
1072
1073
1074
1075
1076
1077
1078
1079 private void readObject(final ObjectInputStream in) throws IOException, ClassNotFoundException {
1080 in.defaultReadObject();
1081
1082 final Calendar definingCalendar = Calendar.getInstance(timeZone, locale);
1083 init(definingCalendar);
1084 }
1085
1086
1087
1088
1089
1090 @Override
1091 public String toString() {
1092 return "FastDateParser[" + pattern + ", " + locale + ", " + timeZone.getID() + "]";
1093 }
1094
1095
1096
1097
1098
1099
1100 public String toStringAll() {
1101 return "FastDateParser [pattern=" + pattern + ", timeZone=" + timeZone + ", locale=" + locale + ", century=" + century + ", startYear=" + startYear
1102 + ", patterns=" + patterns + "]";
1103 }
1104 }