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