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