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.DateFormat;
23 import java.text.DateFormatSymbols;
24 import java.text.FieldPosition;
25 import java.text.SimpleDateFormat;
26 import java.util.ArrayList;
27 import java.util.Calendar;
28 import java.util.Date;
29 import java.util.List;
30 import java.util.Locale;
31 import java.util.TimeZone;
32 import java.util.concurrent.ConcurrentHashMap;
33 import java.util.concurrent.ConcurrentMap;
34
35 import org.apache.commons.lang3.ClassUtils;
36 import org.apache.commons.lang3.LocaleUtils;
37 import org.apache.commons.lang3.exception.ExceptionUtils;
38
39
40
41
42
43
44
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
83 public class FastDatePrinter implements DatePrinter, Serializable {
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99 private static class CharacterLiteral implements Rule {
100 private final char value;
101
102
103
104
105
106
107
108 CharacterLiteral(final char value) {
109 this.value = value;
110 }
111
112
113
114
115 @Override
116 public void appendTo(final Appendable buffer, final Calendar calendar) throws IOException {
117 buffer.append(value);
118 }
119
120
121
122
123 @Override
124 public int estimateLength() {
125 return 1;
126 }
127 }
128
129
130
131
132 private static class DayInWeekField implements NumberRule {
133 private final NumberRule rule;
134
135 DayInWeekField(final NumberRule rule) {
136 this.rule = rule;
137 }
138
139 @Override
140 public void appendTo(final Appendable buffer, final Calendar calendar) throws IOException {
141 final int value = calendar.get(Calendar.DAY_OF_WEEK);
142 rule.appendTo(buffer, value == Calendar.SUNDAY ? 7 : value - 1);
143 }
144
145 @Override
146 public void appendTo(final Appendable buffer, final int value) throws IOException {
147 rule.appendTo(buffer, value);
148 }
149
150 @Override
151 public int estimateLength() {
152 return rule.estimateLength();
153 }
154 }
155
156
157
158
159
160 private static class Iso8601_Rule implements Rule {
161
162
163 static final Iso8601_Rule ISO8601_HOURS = new Iso8601_Rule(3);
164
165 static final Iso8601_Rule ISO8601_HOURS_MINUTES = new Iso8601_Rule(5);
166
167 static final Iso8601_Rule ISO8601_HOURS_COLON_MINUTES = new Iso8601_Rule(6);
168
169
170
171
172
173
174
175
176 static Iso8601_Rule getRule(final int tokenLen) {
177 switch (tokenLen) {
178 case 1:
179 return ISO8601_HOURS;
180 case 2:
181 return ISO8601_HOURS_MINUTES;
182 case 3:
183 return ISO8601_HOURS_COLON_MINUTES;
184 default:
185 throw new IllegalArgumentException("invalid number of X");
186 }
187 }
188
189 private final int length;
190
191
192
193
194
195
196 Iso8601_Rule(final int length) {
197 this.length = length;
198 }
199
200
201
202
203 @Override
204 public void appendTo(final Appendable buffer, final Calendar calendar) throws IOException {
205 int offset = calendar.get(Calendar.ZONE_OFFSET) + calendar.get(Calendar.DST_OFFSET);
206 if (offset == 0) {
207 buffer.append("Z");
208 return;
209 }
210
211 if (offset < 0) {
212 buffer.append('-');
213 offset = -offset;
214 } else {
215 buffer.append('+');
216 }
217
218 final int hours = offset / (60 * 60 * 1000);
219 appendDigits(buffer, hours);
220
221 if (length < 5) {
222 return;
223 }
224
225 if (length == 6) {
226 buffer.append(':');
227 }
228
229 final int minutes = offset / (60 * 1000) - 60 * hours;
230 appendDigits(buffer, minutes);
231 }
232
233
234
235
236 @Override
237 public int estimateLength() {
238 return length;
239 }
240 }
241
242
243
244 private interface NumberRule extends Rule {
245
246
247
248
249
250
251
252 void appendTo(Appendable buffer, int value) throws IOException;
253 }
254
255
256
257 private static final class PaddedNumberField implements NumberRule {
258
259 private final int field;
260 private final int size;
261
262
263
264
265
266
267
268 PaddedNumberField(final int field, final int size) {
269 if (size < 3) {
270
271 throw new IllegalArgumentException();
272 }
273 this.field = field;
274 this.size = size;
275 }
276
277
278
279
280 @Override
281 public void appendTo(final Appendable buffer, final Calendar calendar) throws IOException {
282 appendTo(buffer, calendar.get(field));
283 }
284
285
286
287
288 @Override
289 public void appendTo(final Appendable buffer, final int value) throws IOException {
290
291 appendFullDigits(buffer, value, size);
292 }
293
294
295
296
297 @Override
298 public int estimateLength() {
299 return size;
300 }
301 }
302
303
304
305
306 private interface Rule {
307
308
309
310
311
312
313
314 void appendTo(Appendable buf, Calendar calendar) throws IOException;
315
316
317
318
319
320
321 int estimateLength();
322 }
323
324
325
326
327 private static class StringLiteral implements Rule {
328 private final String value;
329
330
331
332
333
334
335
336 StringLiteral(final String value) {
337 this.value = value;
338 }
339
340
341
342
343 @Override
344 public void appendTo(final Appendable buffer, final Calendar calendar) throws IOException {
345 buffer.append(value);
346 }
347
348
349
350
351 @Override
352 public int estimateLength() {
353 return value.length();
354 }
355 }
356
357
358
359 private static class TextField implements Rule {
360 private final int field;
361 private final String[] values;
362
363
364
365
366
367
368
369
370 TextField(final int field, final String[] values) {
371 this.field = field;
372 this.values = values;
373 }
374
375
376
377
378 @Override
379 public void appendTo(final Appendable buffer, final Calendar calendar) throws IOException {
380 buffer.append(values[calendar.get(field)]);
381 }
382
383
384
385
386 @Override
387 public int estimateLength() {
388 int max = 0;
389 for (int i = values.length; --i >= 0;) {
390 final int len = values[i].length();
391 if (len > max) {
392 max = len;
393 }
394 }
395 return max;
396 }
397 }
398
399
400
401 private static class TimeZoneDisplayKey {
402 private final TimeZone timeZone;
403 private final int style;
404 private final Locale locale;
405
406
407
408
409
410
411
412
413
414 TimeZoneDisplayKey(final TimeZone timeZone,
415 final boolean daylight, final int style, final Locale locale) {
416 this.timeZone = timeZone;
417 if (daylight) {
418 this.style = style | 0x80000000;
419 } else {
420 this.style = style;
421 }
422 this.locale = LocaleUtils.toLocale(locale);
423 }
424
425
426
427
428 @Override
429 public boolean equals(final Object obj) {
430 if (this == obj) {
431 return true;
432 }
433 if (obj instanceof TimeZoneDisplayKey) {
434 final TimeZoneDisplayKey other = (TimeZoneDisplayKey) obj;
435 return
436 timeZone.equals(other.timeZone) &&
437 style == other.style &&
438 locale.equals(other.locale);
439 }
440 return false;
441 }
442
443
444
445
446 @Override
447 public int hashCode() {
448 return (style * 31 + locale.hashCode() ) * 31 + timeZone.hashCode();
449 }
450 }
451
452
453
454 private static class TimeZoneNameRule implements Rule {
455 private final Locale locale;
456 private final int style;
457 private final String standard;
458 private final String daylight;
459
460
461
462
463
464
465
466
467 TimeZoneNameRule(final TimeZone timeZone, final Locale locale, final int style) {
468 this.locale = LocaleUtils.toLocale(locale);
469 this.style = style;
470 this.standard = getTimeZoneDisplay(timeZone, false, style, locale);
471 this.daylight = getTimeZoneDisplay(timeZone, true, style, locale);
472 }
473
474
475
476
477 @Override
478 public void appendTo(final Appendable buffer, final Calendar calendar) throws IOException {
479 final TimeZone zone = calendar.getTimeZone();
480 final boolean daylight = calendar.get(Calendar.DST_OFFSET) != 0;
481 buffer.append(getTimeZoneDisplay(zone, daylight, style, locale));
482 }
483
484
485
486
487 @Override
488 public int estimateLength() {
489
490
491
492 return Math.max(standard.length(), daylight.length());
493 }
494 }
495
496
497
498
499 private static class TimeZoneNumberRule implements Rule {
500 static final TimeZoneNumberRule INSTANCE_COLON = new TimeZoneNumberRule(true);
501 static final TimeZoneNumberRule INSTANCE_NO_COLON = new TimeZoneNumberRule(false);
502
503 private final boolean colon;
504
505
506
507
508
509
510 TimeZoneNumberRule(final boolean colon) {
511 this.colon = colon;
512 }
513
514
515
516
517 @Override
518 public void appendTo(final Appendable buffer, final Calendar calendar) throws IOException {
519
520 int offset = calendar.get(Calendar.ZONE_OFFSET) + calendar.get(Calendar.DST_OFFSET);
521
522 if (offset < 0) {
523 buffer.append('-');
524 offset = -offset;
525 } else {
526 buffer.append('+');
527 }
528
529 final int hours = offset / (60 * 60 * 1000);
530 appendDigits(buffer, hours);
531
532 if (colon) {
533 buffer.append(':');
534 }
535
536 final int minutes = offset / (60 * 1000) - 60 * hours;
537 appendDigits(buffer, minutes);
538 }
539
540
541
542
543 @Override
544 public int estimateLength() {
545 return 5;
546 }
547 }
548
549
550
551
552 private static class TwelveHourField implements NumberRule {
553 private final NumberRule rule;
554
555
556
557
558
559
560
561 TwelveHourField(final NumberRule rule) {
562 this.rule = rule;
563 }
564
565
566
567
568 @Override
569 public void appendTo(final Appendable buffer, final Calendar calendar) throws IOException {
570 int value = calendar.get(Calendar.HOUR);
571 if (value == 0) {
572 value = calendar.getLeastMaximum(Calendar.HOUR) + 1;
573 }
574 rule.appendTo(buffer, value);
575 }
576
577
578
579
580 @Override
581 public void appendTo(final Appendable buffer, final int value) throws IOException {
582 rule.appendTo(buffer, value);
583 }
584
585
586
587
588 @Override
589 public int estimateLength() {
590 return rule.estimateLength();
591 }
592 }
593
594
595
596
597 private static class TwentyFourHourField implements NumberRule {
598 private final NumberRule rule;
599
600
601
602
603
604
605
606 TwentyFourHourField(final NumberRule rule) {
607 this.rule = rule;
608 }
609
610
611
612
613 @Override
614 public void appendTo(final Appendable buffer, final Calendar calendar) throws IOException {
615 int value = calendar.get(Calendar.HOUR_OF_DAY);
616 if (value == 0) {
617 value = calendar.getMaximum(Calendar.HOUR_OF_DAY) + 1;
618 }
619 rule.appendTo(buffer, value);
620 }
621
622
623
624
625 @Override
626 public void appendTo(final Appendable buffer, final int value) throws IOException {
627 rule.appendTo(buffer, value);
628 }
629
630
631
632
633 @Override
634 public int estimateLength() {
635 return rule.estimateLength();
636 }
637 }
638
639
640
641
642 private static class TwoDigitMonthField implements NumberRule {
643 static final TwoDigitMonthField INSTANCE = new TwoDigitMonthField();
644
645
646
647
648 TwoDigitMonthField() {
649 }
650
651
652
653
654 @Override
655 public void appendTo(final Appendable buffer, final Calendar calendar) throws IOException {
656 appendTo(buffer, calendar.get(Calendar.MONTH) + 1);
657 }
658
659
660
661
662 @Override
663 public final void appendTo(final Appendable buffer, final int value) throws IOException {
664 appendDigits(buffer, value);
665 }
666
667
668
669
670 @Override
671 public int estimateLength() {
672 return 2;
673 }
674 }
675
676
677
678
679 private static class TwoDigitNumberField implements NumberRule {
680 private final int field;
681
682
683
684
685
686
687 TwoDigitNumberField(final int field) {
688 this.field = field;
689 }
690
691
692
693
694 @Override
695 public void appendTo(final Appendable buffer, final Calendar calendar) throws IOException {
696 appendTo(buffer, calendar.get(field));
697 }
698
699
700
701
702 @Override
703 public final void appendTo(final Appendable buffer, final int value) throws IOException {
704 if (value < 100) {
705 appendDigits(buffer, value);
706 } else {
707 appendFullDigits(buffer, value, 2);
708 }
709 }
710
711
712
713
714 @Override
715 public int estimateLength() {
716 return 2;
717 }
718 }
719
720
721
722
723 private static class TwoDigitYearField implements NumberRule {
724 static final TwoDigitYearField INSTANCE = new TwoDigitYearField();
725
726
727
728
729 TwoDigitYearField() {
730 }
731
732
733
734
735 @Override
736 public void appendTo(final Appendable buffer, final Calendar calendar) throws IOException {
737 appendTo(buffer, calendar.get(Calendar.YEAR) % 100);
738 }
739
740
741
742
743 @Override
744 public final void appendTo(final Appendable buffer, final int value) throws IOException {
745 appendDigits(buffer, value % 100);
746 }
747
748
749
750
751 @Override
752 public int estimateLength() {
753 return 2;
754 }
755 }
756
757
758
759
760 private static class UnpaddedMonthField implements NumberRule {
761 static final UnpaddedMonthField INSTANCE = new UnpaddedMonthField();
762
763
764
765
766 UnpaddedMonthField() {
767 }
768
769
770
771
772 @Override
773 public void appendTo(final Appendable buffer, final Calendar calendar) throws IOException {
774 appendTo(buffer, calendar.get(Calendar.MONTH) + 1);
775 }
776
777
778
779
780 @Override
781 public final void appendTo(final Appendable buffer, final int value) throws IOException {
782 if (value < 10) {
783 buffer.append((char) (value + '0'));
784 } else {
785 appendDigits(buffer, value);
786 }
787 }
788
789
790
791
792 @Override
793 public int estimateLength() {
794 return 2;
795 }
796 }
797
798
799
800
801 private static class UnpaddedNumberField implements NumberRule {
802 private final int field;
803
804
805
806
807
808
809 UnpaddedNumberField(final int field) {
810 this.field = field;
811 }
812
813
814
815
816 @Override
817 public void appendTo(final Appendable buffer, final Calendar calendar) throws IOException {
818 appendTo(buffer, calendar.get(field));
819 }
820
821
822
823
824 @Override
825 public final void appendTo(final Appendable buffer, final int value) throws IOException {
826 if (value < 10) {
827 buffer.append((char) (value + '0'));
828 } else if (value < 100) {
829 appendDigits(buffer, value);
830 } else {
831 appendFullDigits(buffer, value, 1);
832 }
833 }
834
835
836
837
838 @Override
839 public int estimateLength() {
840 return 4;
841 }
842 }
843
844
845
846
847 private static class WeekYear implements NumberRule {
848 private final NumberRule rule;
849
850 WeekYear(final NumberRule rule) {
851 this.rule = rule;
852 }
853
854 @Override
855 public void appendTo(final Appendable buffer, final Calendar calendar) throws IOException {
856 rule.appendTo(buffer, calendar.getWeekYear());
857 }
858
859 @Override
860 public void appendTo(final Appendable buffer, final int value) throws IOException {
861 rule.appendTo(buffer, value);
862 }
863
864 @Override
865 public int estimateLength() {
866 return rule.estimateLength();
867 }
868 }
869
870
871 private static final Rule[] EMPTY_RULE_ARRAY = {};
872
873
874
875
876
877
878 private static final long serialVersionUID = 1L;
879
880
881
882
883 public static final int FULL = DateFormat.FULL;
884
885
886
887
888 public static final int LONG = DateFormat.LONG;
889
890
891
892
893 public static final int MEDIUM = DateFormat.MEDIUM;
894
895
896
897
898 public static final int SHORT = DateFormat.SHORT;
899
900 private static final int MAX_DIGITS = 10;
901
902 private static final ConcurrentMap<TimeZoneDisplayKey, String> cTimeZoneDisplayCache =
903 new ConcurrentHashMap<>(7);
904
905
906
907
908
909
910
911
912 private static void appendDigits(final Appendable buffer, final int value) throws IOException {
913 buffer.append((char) (value / 10 + '0'));
914 buffer.append((char) (value % 10 + '0'));
915 }
916
917
918
919
920
921
922
923
924
925 private static void appendFullDigits(final Appendable buffer, int value, int minFieldWidth) throws IOException {
926
927
928 if (value < 10000) {
929
930
931 int nDigits = 4;
932 if (value < 1000) {
933 --nDigits;
934 if (value < 100) {
935 --nDigits;
936 if (value < 10) {
937 --nDigits;
938 }
939 }
940 }
941
942 for (int i = minFieldWidth - nDigits; i > 0; --i) {
943 buffer.append('0');
944 }
945
946 switch (nDigits) {
947 case 4:
948 buffer.append((char) (value / 1000 + '0'));
949 value %= 1000;
950 case 3:
951 if (value >= 100) {
952 buffer.append((char) (value / 100 + '0'));
953 value %= 100;
954 } else {
955 buffer.append('0');
956 }
957 case 2:
958 if (value >= 10) {
959 buffer.append((char) (value / 10 + '0'));
960 value %= 10;
961 } else {
962 buffer.append('0');
963 }
964 case 1:
965 buffer.append((char) (value + '0'));
966 }
967 } else {
968
969
970
971 final char[] work = new char[MAX_DIGITS];
972 int digit = 0;
973 while (value != 0) {
974 work[digit++] = (char) (value % 10 + '0');
975 value /= 10;
976 }
977
978
979 while (digit < minFieldWidth) {
980 buffer.append('0');
981 --minFieldWidth;
982 }
983
984
985 while (--digit >= 0) {
986 buffer.append(work[digit]);
987 }
988 }
989 }
990
991
992
993
994
995
996
997
998
999
1000 static String getTimeZoneDisplay(final TimeZone tz, final boolean daylight, final int style, final Locale locale) {
1001 final TimeZoneDisplayKey key = new TimeZoneDisplayKey(tz, daylight, style, locale);
1002
1003 return cTimeZoneDisplayCache.computeIfAbsent(key, k -> tz.getDisplayName(daylight, style, locale));
1004 }
1005
1006
1007
1008
1009 private final String pattern;
1010
1011
1012
1013
1014 private final TimeZone timeZone;
1015
1016
1017
1018
1019 private final Locale locale;
1020
1021
1022
1023
1024 private transient Rule[] rules;
1025
1026
1027
1028
1029 private transient int maxLengthEstimate;
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042 protected FastDatePrinter(final String pattern, final TimeZone timeZone, final Locale locale) {
1043 this.pattern = pattern;
1044 this.timeZone = timeZone;
1045 this.locale = LocaleUtils.toLocale(locale);
1046 init();
1047 }
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058 private <B extends Appendable> B applyRules(final Calendar calendar, final B buf) {
1059 try {
1060 for (final Rule rule : rules) {
1061 rule.appendTo(buf, calendar);
1062 }
1063 } catch (final IOException ioe) {
1064 ExceptionUtils.asRuntimeException(ioe);
1065 }
1066 return buf;
1067 }
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079 @Deprecated
1080 protected StringBuffer applyRules(final Calendar calendar, final StringBuffer buf) {
1081 return (StringBuffer) applyRules(calendar, (Appendable) buf);
1082 }
1083
1084
1085
1086
1087
1088
1089 private String applyRulesToString(final Calendar c) {
1090 return applyRules(c, new StringBuilder(maxLengthEstimate)).toString();
1091 }
1092
1093
1094
1095
1096
1097
1098
1099
1100 @Override
1101 public boolean equals(final Object obj) {
1102 if (!(obj instanceof FastDatePrinter)) {
1103 return false;
1104 }
1105 final FastDatePrinter other = (FastDatePrinter) obj;
1106 return pattern.equals(other.pattern)
1107 && timeZone.equals(other.timeZone)
1108 && locale.equals(other.locale);
1109 }
1110
1111
1112
1113
1114 @Override
1115 public String format(final Calendar calendar) {
1116 return format(calendar, new StringBuilder(maxLengthEstimate)).toString();
1117 }
1118
1119
1120
1121
1122 @Override
1123 public <B extends Appendable> B format(Calendar calendar, final B buf) {
1124
1125 if (!calendar.getTimeZone().equals(timeZone)) {
1126 calendar = (Calendar) calendar.clone();
1127 calendar.setTimeZone(timeZone);
1128 }
1129 return applyRules(calendar, buf);
1130 }
1131
1132
1133
1134
1135 @Override
1136 public StringBuffer format(final Calendar calendar, final StringBuffer buf) {
1137
1138 return format(calendar.getTime(), buf);
1139 }
1140
1141
1142
1143
1144 @Override
1145 public String format(final Date date) {
1146 final Calendar c = newCalendar();
1147 c.setTime(date);
1148 return applyRulesToString(c);
1149 }
1150
1151
1152
1153
1154 @Override
1155 public <B extends Appendable> B format(final Date date, final B buf) {
1156 final Calendar c = newCalendar();
1157 c.setTime(date);
1158 return applyRules(c, buf);
1159 }
1160
1161
1162
1163
1164 @Override
1165 public StringBuffer format(final Date date, final StringBuffer buf) {
1166 final Calendar c = newCalendar();
1167 c.setTime(date);
1168 return (StringBuffer) applyRules(c, (Appendable) buf);
1169 }
1170
1171
1172
1173
1174 @Override
1175 public String format(final long millis) {
1176 final Calendar c = newCalendar();
1177 c.setTimeInMillis(millis);
1178 return applyRulesToString(c);
1179 }
1180
1181
1182
1183
1184 @Override
1185 public <B extends Appendable> B format(final long millis, final B buf) {
1186 final Calendar c = newCalendar();
1187 c.setTimeInMillis(millis);
1188 return applyRules(c, buf);
1189 }
1190
1191
1192
1193
1194 @Override
1195 public StringBuffer format(final long millis, final StringBuffer buf) {
1196 final Calendar c = newCalendar();
1197 c.setTimeInMillis(millis);
1198 return (StringBuffer) applyRules(c, (Appendable) buf);
1199 }
1200
1201
1202
1203
1204
1205
1206
1207
1208 String format(final Object obj) {
1209 if (obj instanceof Date) {
1210 return format((Date) obj);
1211 }
1212 if (obj instanceof Calendar) {
1213 return format((Calendar) obj);
1214 }
1215 if (obj instanceof Long) {
1216 return format(((Long) obj).longValue());
1217 }
1218 throw new IllegalArgumentException("Unknown class: " + ClassUtils.getName(obj, "<null>"));
1219 }
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231 @Deprecated
1232 @Override
1233 public StringBuffer format(final Object obj, final StringBuffer toAppendTo, final FieldPosition pos) {
1234 if (obj instanceof Date) {
1235 return format((Date) obj, toAppendTo);
1236 }
1237 if (obj instanceof Calendar) {
1238 return format((Calendar) obj, toAppendTo);
1239 }
1240 if (obj instanceof Long) {
1241 return format(((Long) obj).longValue(), toAppendTo);
1242 }
1243 throw new IllegalArgumentException("Unknown class: " + ClassUtils.getName(obj, "<null>"));
1244 }
1245
1246
1247
1248
1249 @Override
1250 public Locale getLocale() {
1251 return locale;
1252 }
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263 public int getMaxLengthEstimate() {
1264 return maxLengthEstimate;
1265 }
1266
1267
1268
1269
1270
1271 @Override
1272 public String getPattern() {
1273 return pattern;
1274 }
1275
1276
1277
1278
1279 @Override
1280 public TimeZone getTimeZone() {
1281 return timeZone;
1282 }
1283
1284
1285
1286
1287
1288
1289 @Override
1290 public int hashCode() {
1291 return pattern.hashCode() + 13 * (timeZone.hashCode() + 13 * locale.hashCode());
1292 }
1293
1294
1295
1296
1297 private void init() {
1298 final List<Rule> rulesList = parsePattern();
1299 rules = rulesList.toArray(EMPTY_RULE_ARRAY);
1300
1301 int len = 0;
1302 for (int i = rules.length; --i >= 0;) {
1303 len += rules[i].estimateLength();
1304 }
1305
1306 maxLengthEstimate = len;
1307 }
1308
1309
1310
1311
1312
1313 private Calendar newCalendar() {
1314 return Calendar.getInstance(timeZone, locale);
1315 }
1316
1317
1318
1319
1320
1321
1322
1323
1324 protected List<Rule> parsePattern() {
1325 final DateFormatSymbols symbols = new DateFormatSymbols(locale);
1326 final List<Rule> rules = new ArrayList<>();
1327
1328 final String[] ERAs = symbols.getEras();
1329 final String[] months = symbols.getMonths();
1330 final String[] shortMonths = symbols.getShortMonths();
1331 final String[] weekdays = symbols.getWeekdays();
1332 final String[] shortWeekdays = symbols.getShortWeekdays();
1333 final String[] AmPmStrings = symbols.getAmPmStrings();
1334
1335 final int length = pattern.length();
1336 final int[] indexRef = new int[1];
1337
1338 for (int i = 0; i < length; i++) {
1339 indexRef[0] = i;
1340 final String token = parseToken(pattern, indexRef);
1341 i = indexRef[0];
1342
1343 final int tokenLen = token.length();
1344 if (tokenLen == 0) {
1345 break;
1346 }
1347
1348 Rule rule;
1349 final char c = token.charAt(0);
1350
1351 switch (c) {
1352 case 'G':
1353 rule = new TextField(Calendar.ERA, ERAs);
1354 break;
1355 case 'y':
1356 case 'Y':
1357 if (tokenLen == 2) {
1358 rule = TwoDigitYearField.INSTANCE;
1359 } else {
1360 rule = selectNumberRule(Calendar.YEAR, Math.max(tokenLen, 4));
1361 }
1362 if (c == 'Y') {
1363 rule = new WeekYear((NumberRule) rule);
1364 }
1365 break;
1366 case 'M':
1367 if (tokenLen >= 4) {
1368 rule = new TextField(Calendar.MONTH, months);
1369 } else if (tokenLen == 3) {
1370 rule = new TextField(Calendar.MONTH, shortMonths);
1371 } else if (tokenLen == 2) {
1372 rule = TwoDigitMonthField.INSTANCE;
1373 } else {
1374 rule = UnpaddedMonthField.INSTANCE;
1375 }
1376 break;
1377 case 'L':
1378 if (tokenLen >= 4) {
1379 rule = new TextField(Calendar.MONTH, CalendarUtils.getInstance(locale).getStandaloneLongMonthNames());
1380 } else if (tokenLen == 3) {
1381 rule = new TextField(Calendar.MONTH, CalendarUtils.getInstance(locale).getStandaloneShortMonthNames());
1382 } else if (tokenLen == 2) {
1383 rule = TwoDigitMonthField.INSTANCE;
1384 } else {
1385 rule = UnpaddedMonthField.INSTANCE;
1386 }
1387 break;
1388 case 'd':
1389 rule = selectNumberRule(Calendar.DAY_OF_MONTH, tokenLen);
1390 break;
1391 case 'h':
1392 rule = new TwelveHourField(selectNumberRule(Calendar.HOUR, tokenLen));
1393 break;
1394 case 'H':
1395 rule = selectNumberRule(Calendar.HOUR_OF_DAY, tokenLen);
1396 break;
1397 case 'm':
1398 rule = selectNumberRule(Calendar.MINUTE, tokenLen);
1399 break;
1400 case 's':
1401 rule = selectNumberRule(Calendar.SECOND, tokenLen);
1402 break;
1403 case 'S':
1404 rule = selectNumberRule(Calendar.MILLISECOND, tokenLen);
1405 break;
1406 case 'E':
1407 rule = new TextField(Calendar.DAY_OF_WEEK, tokenLen < 4 ? shortWeekdays : weekdays);
1408 break;
1409 case 'u':
1410 rule = new DayInWeekField(selectNumberRule(Calendar.DAY_OF_WEEK, tokenLen));
1411 break;
1412 case 'D':
1413 rule = selectNumberRule(Calendar.DAY_OF_YEAR, tokenLen);
1414 break;
1415 case 'F':
1416 rule = selectNumberRule(Calendar.DAY_OF_WEEK_IN_MONTH, tokenLen);
1417 break;
1418 case 'w':
1419 rule = selectNumberRule(Calendar.WEEK_OF_YEAR, tokenLen);
1420 break;
1421 case 'W':
1422 rule = selectNumberRule(Calendar.WEEK_OF_MONTH, tokenLen);
1423 break;
1424 case 'a':
1425 rule = new TextField(Calendar.AM_PM, AmPmStrings);
1426 break;
1427 case 'k':
1428 rule = new TwentyFourHourField(selectNumberRule(Calendar.HOUR_OF_DAY, tokenLen));
1429 break;
1430 case 'K':
1431 rule = selectNumberRule(Calendar.HOUR, tokenLen);
1432 break;
1433 case 'X':
1434 rule = Iso8601_Rule.getRule(tokenLen);
1435 break;
1436 case 'z':
1437 if (tokenLen >= 4) {
1438 rule = new TimeZoneNameRule(timeZone, locale, TimeZone.LONG);
1439 } else {
1440 rule = new TimeZoneNameRule(timeZone, locale, TimeZone.SHORT);
1441 }
1442 break;
1443 case 'Z':
1444 if (tokenLen == 1) {
1445 rule = TimeZoneNumberRule.INSTANCE_NO_COLON;
1446 } else if (tokenLen == 2) {
1447 rule = Iso8601_Rule.ISO8601_HOURS_COLON_MINUTES;
1448 } else {
1449 rule = TimeZoneNumberRule.INSTANCE_COLON;
1450 }
1451 break;
1452 case '\'':
1453 final String sub = token.substring(1);
1454 if (sub.length() == 1) {
1455 rule = new CharacterLiteral(sub.charAt(0));
1456 } else {
1457 rule = new StringLiteral(sub);
1458 }
1459 break;
1460 default:
1461 throw new IllegalArgumentException("Illegal pattern component: " + token);
1462 }
1463
1464 rules.add(rule);
1465 }
1466
1467 return rules;
1468 }
1469
1470
1471
1472
1473
1474
1475
1476
1477 protected String parseToken(final String pattern, final int[] indexRef) {
1478 final StringBuilder buf = new StringBuilder();
1479
1480 int i = indexRef[0];
1481 final int length = pattern.length();
1482
1483 char c = pattern.charAt(i);
1484 if (c >= 'A' && c <= 'Z' || c >= 'a' && c <= 'z') {
1485
1486
1487 buf.append(c);
1488
1489 while (i + 1 < length) {
1490 final char peek = pattern.charAt(i + 1);
1491 if (peek != c) {
1492 break;
1493 }
1494 buf.append(c);
1495 i++;
1496 }
1497 } else {
1498
1499 buf.append('\'');
1500
1501 boolean inLiteral = false;
1502
1503 for (; i < length; i++) {
1504 c = pattern.charAt(i);
1505
1506 if (c == '\'') {
1507 if (i + 1 < length && pattern.charAt(i + 1) == '\'') {
1508
1509 i++;
1510 buf.append(c);
1511 } else {
1512 inLiteral = !inLiteral;
1513 }
1514 } else if (!inLiteral &&
1515 (c >= 'A' && c <= 'Z' || c >= 'a' && c <= 'z')) {
1516 i--;
1517 break;
1518 } else {
1519 buf.append(c);
1520 }
1521 }
1522 }
1523
1524 indexRef[0] = i;
1525 return buf.toString();
1526 }
1527
1528
1529
1530
1531
1532
1533
1534
1535
1536
1537 private void readObject(final ObjectInputStream in) throws IOException, ClassNotFoundException {
1538 in.defaultReadObject();
1539 init();
1540 }
1541
1542
1543
1544
1545
1546
1547
1548
1549 protected NumberRule selectNumberRule(final int field, final int padding) {
1550 switch (padding) {
1551 case 1:
1552 return new UnpaddedNumberField(field);
1553 case 2:
1554 return new TwoDigitNumberField(field);
1555 default:
1556 return new PaddedNumberField(field, padding);
1557 }
1558 }
1559
1560
1561
1562
1563
1564
1565 @Override
1566 public String toString() {
1567 return "FastDatePrinter[" + pattern + "," + locale + "," + timeZone.getID() + "]";
1568 }
1569 }