comparison src/com/go/trove/util/FastDateFormat.java @ 0:3dc0c5604566

Initial checkin of blitz 2.0 fcs - no installer yet.
author Dan Creswell <dan.creswell@gmail.com>
date Sat, 21 Mar 2009 11:00:06 +0000
parents
children
comparison
equal deleted inserted replaced
-1:000000000000 0:3dc0c5604566
1 /* ====================================================================
2 * Trove - Copyright (c) 1997-2001 Walt Disney Internet Group
3 * ====================================================================
4 * The Tea Software License, Version 1.1
5 *
6 * Copyright (c) 2000 Walt Disney Internet Group. All rights reserved.
7 *
8 * Redistribution and use in source and binary forms, with or without
9 * modification, are permitted provided that the following conditions
10 * are met:
11 *
12 * 1. Redistributions of source code must retain the above copyright
13 * notice, this list of conditions and the following disclaimer.
14 *
15 * 2. Redistributions in binary form must reproduce the above copyright
16 * notice, this list of conditions and the following disclaimer in
17 * the documentation and/or other materials provided with the
18 * distribution.
19 *
20 * 3. The end-user documentation included with the redistribution,
21 * if any, must include the following acknowledgment:
22 * "This product includes software developed by the
23 * Walt Disney Internet Group (http://opensource.go.com/)."
24 * Alternately, this acknowledgment may appear in the software itself,
25 * if and wherever such third-party acknowledgments normally appear.
26 *
27 * 4. The names "Tea", "TeaServlet", "Kettle", "Trove" and "BeanDoc" must
28 * not be used to endorse or promote products derived from this
29 * software without prior written permission. For written
30 * permission, please contact opensource@dig.com.
31 *
32 * 5. Products derived from this software may not be called "Tea",
33 * "TeaServlet", "Kettle" or "Trove", nor may "Tea", "TeaServlet",
34 * "Kettle", "Trove" or "BeanDoc" appear in their name, without prior
35 * written permission of the Walt Disney Internet Group.
36 *
37 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
38 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
39 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
40 * DISCLAIMED. IN NO EVENT SHALL THE WALT DISNEY INTERNET GROUP OR ITS
41 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
42 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
43 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
44 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
45 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
46 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
47 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
48 * ====================================================================
49 *
50 * For more information about Tea, please see http://opensource.go.com/.
51 */
52
53 package com.go.trove.util;
54
55 import java.util.Date;
56 import java.util.Calendar;
57 import java.util.GregorianCalendar;
58 import java.util.Locale;
59 import java.util.TimeZone;
60 import java.util.List;
61 import java.util.ArrayList;
62 import java.util.Map;
63 import java.util.HashMap;
64 import java.text.DateFormatSymbols;
65 import java.text.DateFormat;
66 import java.text.SimpleDateFormat;
67
68 /******************************************************************************
69 * Similar to {@link java.text.SimpleDateFormat}, but faster and thread-safe.
70 * Only formatting is supported, but all patterns are compatible with
71 * SimpleDateFormat.
72 *
73 * @author Brian S O'Neill
74 * @version
75 * <!--$$Revision: 1.1 $-->, <!--$$JustDate:--> 01/07/03 <!-- $-->
76 */
77 public class FastDateFormat {
78 /** Style pattern */
79 public static final Object
80 FULL = new Integer(SimpleDateFormat.FULL),
81 LONG = new Integer(SimpleDateFormat.LONG),
82 MEDIUM = new Integer(SimpleDateFormat.MEDIUM),
83 SHORT = new Integer(SimpleDateFormat.SHORT);
84
85 private static final double LOG_10 = Math.log(10);
86
87 private static String cDefaultPattern;
88 private static TimeZone cDefaultTimeZone = TimeZone.getDefault();
89
90 private static Map cTimeZoneDisplayCache = new HashMap();
91
92 private static Map cInstanceCache = new HashMap(7);
93 private static Map cDateInstanceCache = new HashMap(7);
94 private static Map cTimeInstanceCache = new HashMap(7);
95 private static Map cDateTimeInstanceCache = new HashMap(7);
96
97 public static FastDateFormat getInstance() {
98 return getInstance(getDefaultPattern(), null, null, null);
99 }
100
101 /**
102 * @param pattern {@link java.text.SimpleDateFormat} compatible pattern
103 */
104 public static FastDateFormat getInstance(String pattern)
105 throws IllegalArgumentException
106 {
107 return getInstance(pattern, null, null, null);
108 }
109
110 /**
111 * @param pattern {@link java.text.SimpleDateFormat} compatible pattern
112 * @param timeZone optional time zone, overrides time zone of formatted
113 * date
114 */
115 public static FastDateFormat getInstance
116 (String pattern, TimeZone timeZone) throws IllegalArgumentException
117 {
118 return getInstance(pattern, timeZone, null, null);
119 }
120
121 /**
122 * @param pattern {@link java.text.SimpleDateFormat} compatible pattern
123 * @param locale optional locale, overrides system locale
124 */
125 public static FastDateFormat getInstance
126 (String pattern, Locale locale) throws IllegalArgumentException
127 {
128 return getInstance(pattern, null, locale, null);
129 }
130
131 /**
132 * @param pattern {@link java.text.SimpleDateFormat} compatible pattern
133 * @param symbols optional date format symbols, overrides symbols for
134 * system locale
135 */
136 public static FastDateFormat getInstance
137 (String pattern, DateFormatSymbols symbols)
138 throws IllegalArgumentException
139 {
140 return getInstance(pattern, null, null, symbols);
141 }
142
143 /**
144 * @param pattern {@link java.text.SimpleDateFormat} compatible pattern
145 * @param timeZone optional time zone, overrides time zone of formatted
146 * date
147 * @param locale optional locale, overrides system locale
148 */
149 public static FastDateFormat getInstance
150 (String pattern, TimeZone timeZone, Locale locale)
151 throws IllegalArgumentException
152 {
153 return getInstance(pattern, timeZone, locale, null);
154 }
155
156 /**
157 * @param pattern {@link java.text.SimpleDateFormat} compatible pattern
158 * @param timeZone optional time zone, overrides time zone of formatted
159 * date
160 * @param locale optional locale, overrides system locale
161 * @param symbols optional date format symbols, overrides symbols for
162 * provided locale
163 */
164 public static synchronized FastDateFormat getInstance
165 (String pattern, TimeZone timeZone, Locale locale,
166 DateFormatSymbols symbols)
167 throws IllegalArgumentException
168 {
169 Object key = pattern;
170
171 if (timeZone != null) {
172 key = new Pair(key, timeZone);
173 }
174 if (locale != null) {
175 key = new Pair(key, locale);
176 }
177 if (symbols != null) {
178 key = new Pair(key, symbols);
179 }
180
181 FastDateFormat format = (FastDateFormat)cInstanceCache.get(key);
182 if (format == null) {
183 if (locale == null) {
184 locale = Locale.getDefault();
185 }
186 if (symbols == null) {
187 symbols = new DateFormatSymbols(locale);
188 }
189 format = new FastDateFormat(pattern, timeZone, locale, symbols);
190 cInstanceCache.put(key, format);
191 }
192 return format;
193 }
194
195 /**
196 * @param style date style: FULL, LONG, MEDIUM, or SHORT
197 * @param timeZone optional time zone, overrides time zone of formatted
198 * date
199 * @param locale optional locale, overrides system locale
200 */
201 public static synchronized FastDateFormat getDateInstance
202 (Object style, TimeZone timeZone, Locale locale)
203 throws IllegalArgumentException
204 {
205 Object key = style;
206
207 if (timeZone != null) {
208 key = new Pair(key, timeZone);
209 }
210 if (locale == null) {
211 key = new Pair(key, locale);
212 }
213
214 FastDateFormat format = (FastDateFormat)cDateInstanceCache.get(key);
215
216 if (format == null) {
217 int ds;
218 try {
219 ds = ((Integer)style).intValue();
220 }
221 catch (ClassCastException e) {
222 throw new IllegalArgumentException
223 ("Illegal date style: " + style);
224 }
225
226 if (locale == null) {
227 locale = Locale.getDefault();
228 }
229
230 try {
231 String pattern = ((SimpleDateFormat)DateFormat.getDateInstance(ds, locale)).toPattern();
232 format = getInstance(pattern, timeZone, locale);
233 cDateInstanceCache.put(key, format);
234 }
235 catch (ClassCastException e) {
236 throw new IllegalArgumentException
237 ("No date pattern for locale: " + locale);
238 }
239 }
240
241 return format;
242 }
243
244 /**
245 * @param style time style: FULL, LONG, MEDIUM, or SHORT
246 * @param timeZone optional time zone, overrides time zone of formatted
247 * date
248 * @param locale optional locale, overrides system locale
249 */
250 public static synchronized FastDateFormat getTimeInstance
251 (Object style, TimeZone timeZone, Locale locale)
252 throws IllegalArgumentException
253 {
254 Object key = style;
255
256 if (timeZone != null) {
257 key = new Pair(key, timeZone);
258 }
259 if (locale != null) {
260 key = new Pair(key, locale);
261 }
262
263 FastDateFormat format = (FastDateFormat)cTimeInstanceCache.get(key);
264
265 if (format == null) {
266 int ts;
267 try {
268 ts = ((Integer)style).intValue();
269 }
270 catch (ClassCastException e) {
271 throw new IllegalArgumentException
272 ("Illegal time style: " + style);
273 }
274
275 if (locale == null) {
276 locale = Locale.getDefault();
277 }
278
279 try {
280 String pattern = ((SimpleDateFormat)DateFormat.getTimeInstance(ts, locale)).toPattern();
281 format = getInstance(pattern, timeZone, locale);
282 cTimeInstanceCache.put(key, format);
283 }
284 catch (ClassCastException e) {
285 throw new IllegalArgumentException
286 ("No date pattern for locale: " + locale);
287 }
288 }
289
290 return format;
291 }
292
293 /**
294 * @param dateStyle date style: FULL, LONG, MEDIUM, or SHORT
295 * @param timeStyle time style: FULL, LONG, MEDIUM, or SHORT
296 * @param timeZone optional time zone, overrides time zone of formatted
297 * date
298 * @param locale optional locale, overrides system locale
299 */
300 public static synchronized FastDateFormat getDateTimeInstance
301 (Object dateStyle, Object timeStyle, TimeZone timeZone, Locale locale)
302 throws IllegalArgumentException
303 {
304 Object key = new Pair(dateStyle, timeStyle);
305
306 if (timeZone != null) {
307 key = new Pair(key, timeZone);
308 }
309 if (locale != null) {
310 key = new Pair(key, locale);
311 }
312
313 FastDateFormat format =
314 (FastDateFormat)cDateTimeInstanceCache.get(key);
315
316 if (format == null) {
317 int ds;
318 try {
319 ds = ((Integer)dateStyle).intValue();
320 }
321 catch (ClassCastException e) {
322 throw new IllegalArgumentException
323 ("Illegal date style: " + dateStyle);
324 }
325
326 int ts;
327 try {
328 ts = ((Integer)timeStyle).intValue();
329 }
330 catch (ClassCastException e) {
331 throw new IllegalArgumentException
332 ("Illegal time style: " + timeStyle);
333 }
334
335 if (locale == null) {
336 locale = Locale.getDefault();
337 }
338
339 try {
340 String pattern = ((SimpleDateFormat)DateFormat.getDateTimeInstance(ds, ts, locale)).toPattern();
341 format = getInstance(pattern, timeZone, locale);
342 cDateTimeInstanceCache.put(key, format);
343 }
344 catch (ClassCastException e) {
345 throw new IllegalArgumentException
346 ("No date time pattern for locale: " + locale);
347 }
348 }
349
350 return format;
351 }
352
353 static synchronized String getTimeZoneDisplay(TimeZone tz,
354 boolean daylight,
355 int style,
356 Locale locale) {
357 Object key = new TimeZoneDisplayKey(tz, daylight, style, locale);
358 String value = (String)cTimeZoneDisplayCache.get(key);
359 if (value == null) {
360 // This is a very slow call, so cache the results.
361 value = tz.getDisplayName(daylight, style, locale);
362 cTimeZoneDisplayCache.put(key, value);
363 }
364 return value;
365 }
366
367 private static synchronized String getDefaultPattern() {
368 if (cDefaultPattern == null) {
369 cDefaultPattern = new SimpleDateFormat().toPattern();
370 }
371 return cDefaultPattern;
372 }
373
374 /**
375 * Returns a list of Rules.
376 */
377 private static List parse(String pattern, TimeZone timeZone, Locale locale,
378 DateFormatSymbols symbols) {
379 List rules = new ArrayList();
380
381 String[] ERAs = symbols.getEras();
382 String[] months = symbols.getMonths();
383 String[] shortMonths = symbols.getShortMonths();
384 String[] weekdays = symbols.getWeekdays();
385 String[] shortWeekdays = symbols.getShortWeekdays();
386 String[] AmPmStrings = symbols.getAmPmStrings();
387
388 int length = pattern.length();
389 int[] indexRef = new int[1];
390
391 for (int i=0; i<length; i++) {
392 indexRef[0] = i;
393 String token = parseToken(pattern, indexRef);
394 i = indexRef[0];
395
396 int tokenLen = token.length();
397 if (tokenLen == 0) {
398 break;
399 }
400
401 Rule rule;
402 char c = token.charAt(0);
403
404 switch (c) {
405 case 'G': // era designator (text)
406 rule = new TextField(Calendar.ERA, ERAs);
407 break;
408 case 'y': // year (number)
409 if (tokenLen >= 4) {
410 rule = new UnpaddedNumberField(Calendar.YEAR);
411 }
412 else {
413 rule = new TwoDigitYearField();
414 }
415 break;
416 case 'M': // month in year (text and number)
417 if (tokenLen >= 4) {
418 rule = new TextField(Calendar.MONTH, months);
419 }
420 else if (tokenLen == 3) {
421 rule = new TextField(Calendar.MONTH, shortMonths);
422 }
423 else if (tokenLen == 2) {
424 rule = new TwoDigitMonthField();
425 }
426 else {
427 rule = new UnpaddedMonthField();
428 }
429 break;
430 case 'd': // day in month (number)
431 rule = selectNumberRule(Calendar.DAY_OF_MONTH, tokenLen);
432 break;
433 case 'h': // hour in am/pm (number, 1..12)
434 rule = new TwelveHourField
435 (selectNumberRule(Calendar.HOUR, tokenLen));
436 break;
437 case 'H': // hour in day (number, 0..23)
438 rule = selectNumberRule(Calendar.HOUR_OF_DAY, tokenLen);
439 break;
440 case 'm': // minute in hour (number)
441 rule = selectNumberRule(Calendar.MINUTE, tokenLen);
442 break;
443 case 's': // second in minute (number)
444 rule = selectNumberRule(Calendar.SECOND, tokenLen);
445 break;
446 case 'S': // millisecond (number)
447 rule = selectNumberRule(Calendar.MILLISECOND, tokenLen);
448 break;
449 case 'E': // day in week (text)
450 rule = new TextField
451 (Calendar.DAY_OF_WEEK,
452 tokenLen < 4 ? shortWeekdays : weekdays);
453 break;
454 case 'D': // day in year (number)
455 rule = selectNumberRule(Calendar.DAY_OF_YEAR, tokenLen);
456 break;
457 case 'F': // day of week in month (number)
458 rule = selectNumberRule
459 (Calendar.DAY_OF_WEEK_IN_MONTH, tokenLen);
460 break;
461 case 'w': // week in year (number)
462 rule = selectNumberRule(Calendar.WEEK_OF_YEAR, tokenLen);
463 break;
464 case 'W': // week in month (number)
465 rule = selectNumberRule(Calendar.WEEK_OF_MONTH, tokenLen);
466 break;
467 case 'a': // am/pm marker (text)
468 rule = new TextField(Calendar.AM_PM, AmPmStrings);
469 break;
470 case 'k': // hour in day (1..24)
471 rule = new TwentyFourHourField
472 (selectNumberRule(Calendar.HOUR_OF_DAY, tokenLen));
473 break;
474 case 'K': // hour in am/pm (0..11)
475 rule = selectNumberRule(Calendar.HOUR, tokenLen);
476 break;
477 case 'z': // time zone (text)
478 if (tokenLen >= 4) {
479 rule = new TimeZoneRule(timeZone, locale, TimeZone.LONG);
480 }
481 else {
482 rule = new TimeZoneRule(timeZone, locale, TimeZone.SHORT);
483 }
484 break;
485 case '\'': // literal text
486 String sub = token.substring(1);
487 if (sub.length() == 1) {
488 rule = new CharacterLiteral(sub.charAt(0));
489 }
490 else {
491 rule = new StringLiteral(new String(sub));
492 }
493 break;
494 default:
495 throw new IllegalArgumentException
496 ("Illegal pattern component: " + token);
497 }
498
499 rules.add(rule);
500 }
501
502 return rules;
503 }
504
505 private static String parseToken(String pattern, int[] indexRef) {
506 StringBuffer buf = new StringBuffer();
507
508 int i = indexRef[0];
509 int length = pattern.length();
510
511 char c = pattern.charAt(i);
512 if (c >= 'A' && c <= 'Z' || c >= 'a' && c <= 'z') {
513 // Scan a run of the same character, which indicates a time
514 // pattern.
515 buf.append(c);
516
517 while (i + 1 < length) {
518 char peek = pattern.charAt(i + 1);
519 if (peek == c) {
520 buf.append(c);
521 i++;
522 }
523 else {
524 break;
525 }
526 }
527 }
528 else {
529 // This will identify token as text.
530 buf.append('\'');
531
532 boolean inLiteral = false;
533
534 for (; i < length; i++) {
535 c = pattern.charAt(i);
536
537 if (c == '\'') {
538 if (i + 1 < length && pattern.charAt(i + 1) == '\'') {
539 // '' is treated as escaped '
540 i++;
541 buf.append(c);
542 }
543 else {
544 inLiteral = !inLiteral;
545 }
546 }
547 else if (!inLiteral &&
548 (c >= 'A' && c <= 'Z' || c >= 'a' && c <= 'z')) {
549 i--;
550 break;
551 }
552 else {
553 buf.append(c);
554 }
555 }
556 }
557
558 indexRef[0] = i;
559 return buf.toString();
560 }
561
562 private static NumberRule selectNumberRule(int field, int padding) {
563 switch (padding) {
564 case 1:
565 return new UnpaddedNumberField(field);
566 case 2:
567 return new TwoDigitNumberField(field);
568 default:
569 return new PaddedNumberField(field, padding);
570 }
571 }
572
573 private final String mPattern;
574 private final TimeZone mTimeZone;
575 private final Locale mLocale;
576 private final Rule[] mRules;
577 private final int mMaxLengthEstimate;
578
579 private FastDateFormat() {
580 this(getDefaultPattern(), null, null, null);
581 }
582
583 /**
584 * @param pattern {@link java.text.SimpleDateFormat} compatible pattern
585 */
586 private FastDateFormat(String pattern) throws IllegalArgumentException {
587 this(pattern, null, null, null);
588 }
589
590 /**
591 * @param pattern {@link java.text.SimpleDateFormat} compatible pattern
592 * @param timeZone optional time zone, overrides time zone of formatted
593 * date
594 */
595 private FastDateFormat(String pattern, TimeZone timeZone)
596 throws IllegalArgumentException
597 {
598 this(pattern, timeZone, null, null);
599 }
600
601 /**
602 * @param pattern {@link java.text.SimpleDateFormat} compatible pattern
603 * @param locale optional locale, overrides system locale
604 */
605 private FastDateFormat(String pattern, Locale locale)
606 throws IllegalArgumentException
607 {
608 this(pattern, null, locale, null);
609 }
610
611 /**
612 * @param pattern {@link java.text.SimpleDateFormat} compatible pattern
613 * @param symbols optional date format symbols, overrides symbols for
614 * system locale
615 */
616 private FastDateFormat(String pattern, DateFormatSymbols symbols)
617 throws IllegalArgumentException
618 {
619 this(pattern, null, null, symbols);
620 }
621
622 /**
623 * @param pattern {@link java.text.SimpleDateFormat} compatible pattern
624 * @param timeZone optional time zone, overrides time zone of formatted
625 * date
626 * @param locale optional locale, overrides system locale
627 */
628 private FastDateFormat(String pattern, TimeZone timeZone, Locale locale)
629 throws IllegalArgumentException
630 {
631 this(pattern, timeZone, locale, null);
632 }
633
634 /**
635 * @param pattern {@link java.text.SimpleDateFormat} compatible pattern
636 * @param timeZone optional time zone, overrides time zone of formatted
637 * date
638 * @param locale optional locale, overrides system locale
639 * @param symbols optional date format symbols, overrides symbols for
640 * provided locale
641 */
642 private FastDateFormat(String pattern, TimeZone timeZone, Locale locale,
643 DateFormatSymbols symbols)
644 throws IllegalArgumentException
645 {
646 if (locale == null) {
647 locale = Locale.getDefault();
648 }
649
650 mPattern = pattern;
651 mTimeZone = timeZone;
652 mLocale = locale;
653
654 if (symbols == null) {
655 symbols = new DateFormatSymbols(locale);
656 }
657
658 List rulesList = parse(pattern, timeZone, locale, symbols);
659 mRules = (Rule[])rulesList.toArray(new Rule[rulesList.size()]);
660
661 int len = 0;
662 for (int i=mRules.length; --i >= 0; ) {
663 len += mRules[i].estimateLength();
664 }
665
666 mMaxLengthEstimate = len;
667 }
668
669 public String format(Date date) {
670 Calendar c = new GregorianCalendar(cDefaultTimeZone);
671 c.setTime(date);
672 if (mTimeZone != null) {
673 c.setTimeZone(mTimeZone);
674 }
675 return applyRules(c, new StringBuffer(mMaxLengthEstimate)).toString();
676 }
677
678 public String format(Calendar calendar) {
679 return format(calendar, new StringBuffer(mMaxLengthEstimate))
680 .toString();
681 }
682
683 public StringBuffer format(Date date, StringBuffer buf) {
684 Calendar c = new GregorianCalendar(cDefaultTimeZone);
685 c.setTime(date);
686 if (mTimeZone != null) {
687 c.setTimeZone(mTimeZone);
688 }
689 return applyRules(c, buf);
690 }
691
692 public StringBuffer format(Calendar calendar, StringBuffer buf) {
693 if (mTimeZone != null) {
694 calendar = (Calendar)calendar.clone();
695 calendar.setTimeZone(mTimeZone);
696 }
697 return applyRules(calendar, buf);
698 }
699
700 private StringBuffer applyRules(Calendar calendar, StringBuffer buf) {
701 Rule[] rules = mRules;
702 int len = mRules.length;
703 for (int i=0; i<len; i++) {
704 rules[i].appendTo(buf, calendar);
705 }
706 return buf;
707 }
708
709 public String getPattern() {
710 return mPattern;
711 }
712
713 /**
714 * Returns the time zone used by this formatter, or null if time zone of
715 * formatted dates is used instead.
716 */
717 public TimeZone getTimeZone() {
718 return mTimeZone;
719 }
720
721 public Locale getLocale() {
722 return mLocale;
723 }
724
725 /**
726 * Returns an estimate for the maximum length date that this date
727 * formatter will produce. The actual formatted length will almost always
728 * be less than or equal to this amount.
729 */
730 public int getMaxLengthEstimate() {
731 return mMaxLengthEstimate;
732 }
733
734 private interface Rule {
735 int estimateLength();
736
737 void appendTo(StringBuffer buffer, Calendar calendar);
738 }
739
740 private interface NumberRule extends Rule {
741 void appendTo(StringBuffer buffer, int value);
742 }
743
744 private static class CharacterLiteral implements Rule {
745 private final char mValue;
746
747 CharacterLiteral(char value) {
748 mValue = value;
749 }
750
751 public int estimateLength() {
752 return 1;
753 }
754
755 public void appendTo(StringBuffer buffer, Calendar calendar) {
756 buffer.append(mValue);
757 }
758 }
759
760 private static class StringLiteral implements Rule {
761 private final String mValue;
762
763 StringLiteral(String value) {
764 mValue = value;
765 }
766
767 public int estimateLength() {
768 return mValue.length();
769 }
770
771 public void appendTo(StringBuffer buffer, Calendar calendar) {
772 buffer.append(mValue);
773 }
774 }
775
776 private static class TextField implements Rule {
777 private final int mField;
778 private final String[] mValues;
779
780 TextField(int field, String[] values) {
781 mField = field;
782 mValues = values;
783 }
784
785 public int estimateLength() {
786 int max = 0;
787 for (int i=mValues.length; --i >= 0; ) {
788 int len = mValues[i].length();
789 if (len > max) {
790 max = len;
791 }
792 }
793 return max;
794 }
795
796 public void appendTo(StringBuffer buffer, Calendar calendar) {
797 buffer.append(mValues[calendar.get(mField)]);
798 }
799 }
800
801 private static class UnpaddedNumberField implements NumberRule {
802 private final int mField;
803
804 UnpaddedNumberField(int field) {
805 mField = field;
806 }
807
808 public int estimateLength() {
809 return 4;
810 }
811
812 public void appendTo(StringBuffer buffer, Calendar calendar) {
813 appendTo(buffer, calendar.get(mField));
814 }
815
816 public final void appendTo(StringBuffer buffer, int value) {
817 if (value < 10) {
818 buffer.append((char)(value + '0'));
819 }
820 else if (value < 100) {
821 buffer.append((char)(value / 10 + '0'));
822 buffer.append((char)(value % 10 + '0'));
823 }
824 else {
825 buffer.append(Integer.toString(value));
826 }
827 }
828 }
829
830 private static class UnpaddedMonthField implements NumberRule {
831 UnpaddedMonthField() {
832 }
833
834 public int estimateLength() {
835 return 2;
836 }
837
838 public void appendTo(StringBuffer buffer, Calendar calendar) {
839 appendTo(buffer, calendar.get(Calendar.MONTH) + 1);
840 }
841
842 public final void appendTo(StringBuffer buffer, int value) {
843 if (value < 10) {
844 buffer.append((char)(value + '0'));
845 }
846 else {
847 buffer.append((char)(value / 10 + '0'));
848 buffer.append((char)(value % 10 + '0'));
849 }
850 }
851 }
852
853 private static class PaddedNumberField implements NumberRule {
854 private final int mField;
855 private final int mSize;
856
857 PaddedNumberField(int field, int size) {
858 if (size < 3) {
859 // Should use UnpaddedNumberField or TwoDigitNumberField.
860 throw new IllegalArgumentException();
861 }
862 mField = field;
863 mSize = size;
864 }
865
866 public int estimateLength() {
867 return 4;
868 }
869
870 public void appendTo(StringBuffer buffer, Calendar calendar) {
871 appendTo(buffer, calendar.get(mField));
872 }
873
874 public final void appendTo(StringBuffer buffer, int value) {
875 if (value < 100) {
876 for (int i = mSize; --i >= 2; ) {
877 buffer.append('0');
878 }
879 buffer.append((char)(value / 10 + '0'));
880 buffer.append((char)(value % 10 + '0'));
881 }
882 else {
883 int digits;
884 if (value < 1000) {
885 digits = 3;
886 }
887 else {
888 digits = (int)(Math.log(value) / LOG_10) + 1;
889 }
890 for (int i = mSize; --i >= digits; ) {
891 buffer.append('0');
892 }
893 buffer.append(Integer.toString(value));
894 }
895 }
896 }
897
898 private static class TwoDigitNumberField implements NumberRule {
899 private final int mField;
900
901 TwoDigitNumberField(int field) {
902 mField = field;
903 }
904
905 public int estimateLength() {
906 return 2;
907 }
908
909 public void appendTo(StringBuffer buffer, Calendar calendar) {
910 appendTo(buffer, calendar.get(mField));
911 }
912
913 public final void appendTo(StringBuffer buffer, int value) {
914 if (value < 100) {
915 buffer.append((char)(value / 10 + '0'));
916 buffer.append((char)(value % 10 + '0'));
917 }
918 else {
919 buffer.append(Integer.toString(value));
920 }
921 }
922 }
923
924 private static class TwoDigitYearField implements NumberRule {
925 TwoDigitYearField() {
926 }
927
928 public int estimateLength() {
929 return 2;
930 }
931
932 public void appendTo(StringBuffer buffer, Calendar calendar) {
933 appendTo(buffer, calendar.get(Calendar.YEAR) % 100);
934 }
935
936 public final void appendTo(StringBuffer buffer, int value) {
937 buffer.append((char)(value / 10 + '0'));
938 buffer.append((char)(value % 10 + '0'));
939 }
940 }
941
942 private static class TwoDigitMonthField implements NumberRule {
943 TwoDigitMonthField() {
944 }
945
946 public int estimateLength() {
947 return 2;
948 }
949
950 public void appendTo(StringBuffer buffer, Calendar calendar) {
951 appendTo(buffer, calendar.get(Calendar.MONTH) + 1);
952 }
953
954 public final void appendTo(StringBuffer buffer, int value) {
955 buffer.append((char)(value / 10 + '0'));
956 buffer.append((char)(value % 10 + '0'));
957 }
958 }
959
960 private static class TwelveHourField implements NumberRule {
961 private final NumberRule mRule;
962
963 TwelveHourField(NumberRule rule) {
964 mRule = rule;
965 }
966
967 public int estimateLength() {
968 return mRule.estimateLength();
969 }
970
971 public void appendTo(StringBuffer buffer, Calendar calendar) {
972 int value = calendar.get(Calendar.HOUR);
973 if (value == 0) {
974 value = calendar.getLeastMaximum(Calendar.HOUR) + 1;
975 }
976 mRule.appendTo(buffer, value);
977 }
978
979 public void appendTo(StringBuffer buffer, int value) {
980 mRule.appendTo(buffer, value);
981 }
982 }
983
984 private static class TwentyFourHourField implements NumberRule {
985 private final NumberRule mRule;
986
987 TwentyFourHourField(NumberRule rule) {
988 mRule = rule;
989 }
990
991 public int estimateLength() {
992 return mRule.estimateLength();
993 }
994
995 public void appendTo(StringBuffer buffer, Calendar calendar) {
996 int value = calendar.get(Calendar.HOUR_OF_DAY);
997 if (value == 0) {
998 value = calendar.getMaximum(Calendar.HOUR_OF_DAY) + 1;
999 }
1000 mRule.appendTo(buffer, value);
1001 }
1002
1003 public void appendTo(StringBuffer buffer, int value) {
1004 mRule.appendTo(buffer, value);
1005 }
1006 }
1007
1008 private static class TimeZoneRule implements Rule {
1009 private final TimeZone mTimeZone;
1010 private final Locale mLocale;
1011 private final int mStyle;
1012 private final String mStandard;
1013 private final String mDaylight;
1014
1015 TimeZoneRule(TimeZone timeZone, Locale locale, int style) {
1016 mTimeZone = timeZone;
1017 mLocale = locale;
1018 mStyle = style;
1019
1020 if (timeZone != null) {
1021 mStandard = getTimeZoneDisplay(timeZone, false, style, locale);
1022 mDaylight = getTimeZoneDisplay(timeZone, true, style, locale);
1023 }
1024 else {
1025 mStandard = null;
1026 mDaylight = null;
1027 }
1028 }
1029
1030 public int estimateLength() {
1031 if (mTimeZone != null) {
1032 return Math.max(mStandard.length(), mDaylight.length());
1033 }
1034 else if (mStyle == TimeZone.SHORT) {
1035 return 4;
1036 }
1037 else {
1038 return 40;
1039 }
1040 }
1041
1042 public void appendTo(StringBuffer buffer, Calendar calendar) {
1043 TimeZone timeZone;
1044 if ((timeZone = mTimeZone) != null) {
1045 if (timeZone.useDaylightTime() &&
1046 calendar.get(Calendar.DST_OFFSET) != 0) {
1047
1048 buffer.append(mDaylight);
1049 }
1050 else {
1051 buffer.append(mStandard);
1052 }
1053 }
1054 else {
1055 timeZone = calendar.getTimeZone();
1056 if (timeZone.useDaylightTime() &&
1057 calendar.get(Calendar.DST_OFFSET) != 0) {
1058
1059 buffer.append(getTimeZoneDisplay
1060 (timeZone, true, mStyle, mLocale));
1061 }
1062 else {
1063 buffer.append(getTimeZoneDisplay
1064 (timeZone, false, mStyle, mLocale));
1065 }
1066 }
1067 }
1068 }
1069
1070 private static class TimeZoneDisplayKey {
1071 private final TimeZone mTimeZone;
1072 private final int mStyle;
1073 private final Locale mLocale;
1074
1075 TimeZoneDisplayKey(TimeZone timeZone,
1076 boolean daylight, int style, Locale locale) {
1077 mTimeZone = timeZone;
1078 if (daylight) {
1079 style |= 0x80000000;
1080 }
1081 mStyle = style;
1082 mLocale = locale;
1083 }
1084
1085 public int hashCode() {
1086 return mStyle * 31 + mLocale.hashCode();
1087 }
1088
1089 public boolean equals(Object obj) {
1090 if (this == obj) {
1091 return true;
1092 }
1093 if (obj instanceof TimeZoneDisplayKey) {
1094 TimeZoneDisplayKey other = (TimeZoneDisplayKey)obj;
1095 return
1096 mTimeZone.equals(other.mTimeZone) &&
1097 mStyle == other.mStyle &&
1098 mLocale.equals(other.mLocale);
1099 }
1100 return false;
1101 }
1102 }
1103 }