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