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