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