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
486 private static final class TzInfo {
487 final TimeZone zone;
488 final int dstOffset;
489
490 TzInfo(final TimeZone tz, final boolean useDst) {
491 zone = tz;
492 dstOffset = useDst ? tz.getDSTSavings() : 0;
493 }
494
495 @Override
496 public String toString() {
497 return "TzInfo [zone=" + zone + ", dstOffset=" + dstOffset + "]";
498 }
499 }
500
501 private static final String RFC_822_TIME_ZONE = "[+-]\\d{4}";
502
503 private static final String GMT_OPTION = TimeZones.GMT_ID + "[+-]\\d{1,2}:\\d{2}";
504
505
506
507
508 private static final int ID = 0;
509
510
511
512
513
514
515
516
517
518
519
520
521
522 static boolean skipTimeZone(final String tzId) {
523 return tzId.equalsIgnoreCase(TimeZones.GMT_ID);
524 }
525
526 private final Locale locale;
527
528
529
530
531
532 private final Map<String, TzInfo> tzNames = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
533
534
535
536
537
538
539 TimeZoneStrategy(final Locale locale) {
540 this.locale = LocaleUtils.toLocale(locale);
541
542 final StringBuilder sb = new StringBuilder();
543 sb.append("((?iu)" + RFC_822_TIME_ZONE + "|" + GMT_OPTION);
544
545 final Set<String> sorted = new TreeSet<>(LONGER_FIRST_LOWERCASE);
546
547
548
549 final String[][] zones = DateFormatSymbols.getInstance(locale).getZoneStrings();
550 for (final String[] zoneNames : zones) {
551
552 final String tzId = zoneNames[ID];
553 if (skipTimeZone(tzId)) {
554 continue;
555 }
556 final TimeZone tz = TimeZones.getTimeZone(tzId);
557
558
559 final TzInfo standard = new TzInfo(tz, false);
560 TzInfo tzInfo = standard;
561 for (int i = 1; i < zoneNames.length; ++i) {
562 switch (i) {
563 case 3:
564
565 tzInfo = new TzInfo(tz, true);
566 break;
567 case 5:
568 tzInfo = standard;
569 break;
570 default:
571 break;
572 }
573 final String zoneName = zoneNames[i];
574
575 if (zoneName != null && sorted.add(zoneName)) {
576 tzNames.put(zoneName, tzInfo);
577 }
578 }
579 }
580
581 for (final String tzId : ArraySorter.sort(TimeZone.getAvailableIDs())) {
582 if (skipTimeZone(tzId)) {
583 continue;
584 }
585 final TimeZone tz = TimeZones.getTimeZone(tzId);
586 final String zoneName = tz.getDisplayName(locale);
587 if (sorted.add(zoneName)) {
588 tzNames.put(zoneName, new TzInfo(tz, tz.observesDaylightTime()));
589 }
590 }
591
592
593 sorted.forEach(zoneName -> simpleQuote(sb.append('|'), zoneName));
594 sb.append(")");
595 createPattern(sb);
596 }
597
598
599
600
601 @Override
602 void setCalendar(final FastDateParser parser, final Calendar calendar, final String timeZone) {
603 final TimeZone tz = FastTimeZone.getGmtTimeZone(timeZone);
604 if (tz != null) {
605 calendar.setTimeZone(tz);
606 } else {
607 TzInfo tzInfo = tzNames.get(timeZone);
608 if (tzInfo == null) {
609
610 tzInfo = tzNames.get(timeZone + '.');
611 if (tzInfo == null) {
612
613 final char[] charArray = timeZone.toCharArray();
614 throw new IllegalStateException(String.format("Can't find time zone '%s' (%d %s) in %s", timeZone, charArray.length,
615 Arrays.toString(charArray), new TreeSet<>(tzNames.keySet())));
616 }
617 }
618 calendar.set(Calendar.DST_OFFSET, tzInfo.dstOffset);
619 calendar.set(Calendar.ZONE_OFFSET, tzInfo.zone.getRawOffset());
620 }
621 }
622
623
624
625
626
627
628 @Override
629 public String toString() {
630 return "TimeZoneStrategy [locale=" + locale + ", tzNames=" + tzNames + ", pattern=" + pattern + "]";
631 }
632
633 }
634
635
636
637
638
639
640 private static final long serialVersionUID = 3L;
641
642 static final Locale JAPANESE_IMPERIAL = new Locale("ja", "JP", "JP");
643
644
645
646
647
648 private static final Comparator<String> LONGER_FIRST_LOWERCASE = Comparator.reverseOrder();
649
650
651
652 @SuppressWarnings("unchecked")
653 private static final ConcurrentMap<Locale, Strategy>[] CACHES = new ConcurrentMap[Calendar.FIELD_COUNT];
654
655 private static final Strategy ABBREVIATED_YEAR_STRATEGY = new NumberStrategy(Calendar.YEAR) {
656
657
658
659 @Override
660 int modify(final FastDateParser parser, final int iValue) {
661 return iValue < 100 ? parser.adjustYear(iValue) : iValue;
662 }
663 };
664
665 private static final Strategy NUMBER_MONTH_STRATEGY = new NumberStrategy(Calendar.MONTH) {
666 @Override
667 int modify(final FastDateParser parser, final int iValue) {
668 return iValue - 1;
669 }
670 };
671
672 private static final Strategy LITERAL_YEAR_STRATEGY = new NumberStrategy(Calendar.YEAR);
673
674 private static final Strategy WEEK_OF_YEAR_STRATEGY = new NumberStrategy(Calendar.WEEK_OF_YEAR);
675
676 private static final Strategy WEEK_OF_MONTH_STRATEGY = new NumberStrategy(Calendar.WEEK_OF_MONTH);
677
678 private static final Strategy DAY_OF_YEAR_STRATEGY = new NumberStrategy(Calendar.DAY_OF_YEAR);
679
680 private static final Strategy DAY_OF_MONTH_STRATEGY = new NumberStrategy(Calendar.DAY_OF_MONTH);
681
682 private static final Strategy DAY_OF_WEEK_STRATEGY = new NumberStrategy(Calendar.DAY_OF_WEEK) {
683 @Override
684 int modify(final FastDateParser parser, final int iValue) {
685 return iValue == 7 ? Calendar.SUNDAY : iValue + 1;
686 }
687 };
688
689 private static final Strategy DAY_OF_WEEK_IN_MONTH_STRATEGY = new NumberStrategy(Calendar.DAY_OF_WEEK_IN_MONTH);
690
691 private static final Strategy HOUR_OF_DAY_STRATEGY = new NumberStrategy(Calendar.HOUR_OF_DAY);
692
693 private static final Strategy HOUR24_OF_DAY_STRATEGY = new NumberStrategy(Calendar.HOUR_OF_DAY) {
694 @Override
695 int modify(final FastDateParser parser, final int iValue) {
696 return iValue == 24 ? 0 : iValue;
697 }
698 };
699
700 private static final Strategy HOUR12_STRATEGY = new NumberStrategy(Calendar.HOUR) {
701 @Override
702 int modify(final FastDateParser parser, final int iValue) {
703 return iValue == 12 ? 0 : iValue;
704 }
705 };
706
707 private static final Strategy HOUR_STRATEGY = new NumberStrategy(Calendar.HOUR);
708
709 private static final Strategy MINUTE_STRATEGY = new NumberStrategy(Calendar.MINUTE);
710
711 private static final Strategy SECOND_STRATEGY = new NumberStrategy(Calendar.SECOND);
712
713 private static final Strategy MILLISECOND_STRATEGY = new NumberStrategy(Calendar.MILLISECOND);
714
715
716
717
718
719
720
721
722
723
724 private static Map<String, Integer> appendDisplayNames(final Calendar calendar, final Locale locale, final int field, final StringBuilder regex) {
725 Objects.requireNonNull(calendar, "calendar");
726 final Map<String, Integer> values = new HashMap<>();
727 final Locale actualLocale = LocaleUtils.toLocale(locale);
728 final Map<String, Integer> displayNames = calendar.getDisplayNames(field, Calendar.ALL_STYLES, actualLocale);
729 final TreeSet<String> sorted = new TreeSet<>(LONGER_FIRST_LOWERCASE);
730 displayNames.forEach((k, v) -> {
731 final String keyLc = k.toLowerCase(actualLocale);
732 if (sorted.add(keyLc)) {
733 values.put(keyLc, v);
734 }
735 });
736 sorted.forEach(symbol -> simpleQuote(regex, symbol).append('|'));
737 return values;
738 }
739
740
741
742
743 static void clear() {
744 Stream.of(CACHES).filter(Objects::nonNull).forEach(ConcurrentMap::clear);
745 }
746
747
748
749
750
751
752
753 private static ConcurrentMap<Locale, Strategy> getCache(final int field) {
754 synchronized (CACHES) {
755 if (CACHES[field] == null) {
756 CACHES[field] = new ConcurrentHashMap<>(3);
757 }
758 return CACHES[field];
759 }
760 }
761
762 private static StringBuilder simpleQuote(final StringBuilder sb, final String value) {
763 for (int i = 0; i < value.length(); ++i) {
764 final char c = value.charAt(i);
765 switch (c) {
766 case '\\':
767 case '^':
768 case '$':
769 case '.':
770 case '|':
771 case '?':
772 case '*':
773 case '+':
774 case '(':
775 case ')':
776 case '[':
777 case '{':
778 sb.append('\\');
779
780 default:
781 sb.append(c);
782 }
783 }
784 if (sb.charAt(sb.length() - 1) == '.') {
785
786 sb.append('?');
787 }
788 return sb;
789 }
790
791
792 private final String pattern;
793
794
795 private final TimeZone timeZone;
796
797
798 private final Locale locale;
799
800
801
802
803 private final int century;
804
805
806
807
808 private final int startYear;
809
810
811 private transient List<StrategyAndWidth> patterns;
812
813
814
815
816
817
818
819
820
821
822
823 protected FastDateParser(final String pattern, final TimeZone timeZone, final Locale locale) {
824 this(pattern, timeZone, locale, null);
825 }
826
827
828
829
830
831
832
833
834
835
836 protected FastDateParser(final String pattern, final TimeZone timeZone, final Locale locale, final Date centuryStart) {
837 this.pattern = Objects.requireNonNull(pattern, "pattern");
838 this.timeZone = Objects.requireNonNull(timeZone, "timeZone");
839 this.locale = LocaleUtils.toLocale(locale);
840 final Calendar definingCalendar = Calendar.getInstance(timeZone, this.locale);
841 final int centuryStartYear;
842 if (centuryStart != null) {
843 definingCalendar.setTime(centuryStart);
844 centuryStartYear = definingCalendar.get(Calendar.YEAR);
845 } else if (this.locale.equals(JAPANESE_IMPERIAL)) {
846 centuryStartYear = 0;
847 } else {
848
849 definingCalendar.setTime(new Date());
850 centuryStartYear = definingCalendar.get(Calendar.YEAR) - 80;
851 }
852 century = centuryStartYear / 100 * 100;
853 startYear = centuryStartYear - century;
854 init(definingCalendar);
855 }
856
857
858
859
860
861
862
863 private int adjustYear(final int twoDigitYear) {
864 final int trial = century + twoDigitYear;
865 return twoDigitYear >= startYear ? trial : trial + 100;
866 }
867
868
869
870
871
872
873
874 @Override
875 public boolean equals(final Object obj) {
876 if (!(obj instanceof FastDateParser)) {
877 return false;
878 }
879 final FastDateParser other = (FastDateParser) obj;
880 return pattern.equals(other.pattern) && timeZone.equals(other.timeZone) && locale.equals(other.locale);
881 }
882
883
884
885
886
887
888 @Override
889 public Locale getLocale() {
890 return locale;
891 }
892
893
894
895
896
897
898
899
900 private Strategy getLocaleSpecificStrategy(final int field, final Calendar definingCalendar) {
901 final ConcurrentMap<Locale, Strategy> cache = getCache(field);
902 return cache.computeIfAbsent(locale,
903 k -> field == Calendar.ZONE_OFFSET ? new TimeZoneStrategy(locale) : new CaseInsensitiveTextStrategy(field, definingCalendar, locale));
904 }
905
906
907
908
909
910
911 @Override
912 public String getPattern() {
913 return pattern;
914 }
915
916
917
918
919
920
921
922
923 private Strategy getStrategy(final char f, final int width, final Calendar definingCalendar) {
924 switch (f) {
925 case 'D':
926 return DAY_OF_YEAR_STRATEGY;
927 case 'E':
928 return getLocaleSpecificStrategy(Calendar.DAY_OF_WEEK, definingCalendar);
929 case 'F':
930 return DAY_OF_WEEK_IN_MONTH_STRATEGY;
931 case 'G':
932 return getLocaleSpecificStrategy(Calendar.ERA, definingCalendar);
933 case 'H':
934 return HOUR_OF_DAY_STRATEGY;
935 case 'K':
936 return HOUR_STRATEGY;
937 case 'M':
938 case 'L':
939 return width >= 3 ? getLocaleSpecificStrategy(Calendar.MONTH, definingCalendar) : NUMBER_MONTH_STRATEGY;
940 case 'S':
941 return MILLISECOND_STRATEGY;
942 case 'W':
943 return WEEK_OF_MONTH_STRATEGY;
944 case 'a':
945 return getLocaleSpecificStrategy(Calendar.AM_PM, definingCalendar);
946 case 'd':
947 return DAY_OF_MONTH_STRATEGY;
948 case 'h':
949 return HOUR12_STRATEGY;
950 case 'k':
951 return HOUR24_OF_DAY_STRATEGY;
952 case 'm':
953 return MINUTE_STRATEGY;
954 case 's':
955 return SECOND_STRATEGY;
956 case 'u':
957 return DAY_OF_WEEK_STRATEGY;
958 case 'w':
959 return WEEK_OF_YEAR_STRATEGY;
960 case 'y':
961 case 'Y':
962 return width > 2 ? LITERAL_YEAR_STRATEGY : ABBREVIATED_YEAR_STRATEGY;
963 case 'X':
964 return ISO8601TimeZoneStrategy.getStrategy(width);
965 case 'Z':
966 if (width == 2) {
967 return ISO8601TimeZoneStrategy.ISO_8601_3_STRATEGY;
968 }
969
970 case 'z':
971 return getLocaleSpecificStrategy(Calendar.ZONE_OFFSET, definingCalendar);
972 default:
973 throw new IllegalArgumentException("Format '" + f + "' not supported");
974 }
975 }
976
977
978
979
980
981
982 @Override
983 public TimeZone getTimeZone() {
984 return timeZone;
985 }
986
987
988
989
990
991
992 @Override
993 public int hashCode() {
994 return pattern.hashCode() + 13 * (timeZone.hashCode() + 13 * locale.hashCode());
995 }
996
997
998
999
1000
1001
1002 private void init(final Calendar definingCalendar) {
1003 patterns = new ArrayList<>();
1004
1005 final StrategyParser strategyParser = new StrategyParser(definingCalendar);
1006 for (;;) {
1007 final StrategyAndWidth field = strategyParser.getNextStrategy();
1008 if (field == null) {
1009 break;
1010 }
1011 patterns.add(field);
1012 }
1013 }
1014
1015
1016
1017
1018
1019
1020 @Override
1021 public Date parse(final String source) throws ParseException {
1022 final ParsePosition pp = new ParsePosition(0);
1023 final Date date = parse(source, pp);
1024 if (date == null) {
1025
1026 if (locale.equals(JAPANESE_IMPERIAL)) {
1027 throw new ParseException("(The " + locale + " locale does not support dates before 1868 AD)\nUnparseable date: \"" + source,
1028 pp.getErrorIndex());
1029 }
1030 throw new ParseException("Unparseable date: " + source, pp.getErrorIndex());
1031 }
1032 return date;
1033 }
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044 @Override
1045 public Date parse(final String source, final ParsePosition pos) {
1046
1047 final Calendar cal = Calendar.getInstance(timeZone, locale);
1048 cal.clear();
1049 return parse(source, pos, cal) ? cal.getTime() : null;
1050 }
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062 @Override
1063 public boolean parse(final String source, final ParsePosition pos, final Calendar calendar) {
1064 final ListIterator<StrategyAndWidth> lt = patterns.listIterator();
1065 while (lt.hasNext()) {
1066 final StrategyAndWidth strategyAndWidth = lt.next();
1067 final int maxWidth = strategyAndWidth.getMaxWidth(lt);
1068 if (!strategyAndWidth.strategy.parse(this, calendar, source, pos, maxWidth)) {
1069 return false;
1070 }
1071 }
1072 return true;
1073 }
1074
1075
1076
1077
1078
1079
1080 @Override
1081 public Object parseObject(final String source) throws ParseException {
1082 return parse(source);
1083 }
1084
1085
1086
1087
1088
1089
1090 @Override
1091 public Object parseObject(final String source, final ParsePosition pos) {
1092 return parse(source, pos);
1093 }
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103 private void readObject(final ObjectInputStream in) throws IOException, ClassNotFoundException {
1104 in.defaultReadObject();
1105 final Calendar definingCalendar = Calendar.getInstance(timeZone, locale);
1106 init(definingCalendar);
1107 }
1108
1109
1110
1111
1112
1113
1114 @Override
1115 public String toString() {
1116 return "FastDateParser[" + pattern + ", " + locale + ", " + timeZone.getID() + "]";
1117 }
1118
1119
1120
1121
1122
1123
1124
1125 public String toStringAll() {
1126 return "FastDateParser [pattern=" + pattern + ", timeZone=" + timeZone + ", locale=" + locale + ", century=" + century + ", startYear=" + startYear
1127 + ", patterns=" + patterns + "]";
1128 }
1129 }