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