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