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