001 /*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements. See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License. You may obtain a copy of the License at
008 *
009 * http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017 package org.apache.commons.lang.time;
018
019 import java.io.IOException;
020 import java.io.ObjectInputStream;
021 import java.text.DateFormat;
022 import java.text.DateFormatSymbols;
023 import java.text.FieldPosition;
024 import java.text.Format;
025 import java.text.ParsePosition;
026 import java.text.SimpleDateFormat;
027 import java.util.ArrayList;
028 import java.util.Calendar;
029 import java.util.Date;
030 import java.util.GregorianCalendar;
031 import java.util.HashMap;
032 import java.util.List;
033 import java.util.Locale;
034 import java.util.Map;
035 import java.util.TimeZone;
036
037 import org.apache.commons.lang.Validate;
038
039 /**
040 * <p>FastDateFormat is a fast and thread-safe version of
041 * {@link java.text.SimpleDateFormat}.</p>
042 *
043 * <p>This class can be used as a direct replacement to
044 * <code>SimpleDateFormat</code> in most formatting situations.
045 * This class is especially useful in multi-threaded server environments.
046 * <code>SimpleDateFormat</code> is not thread-safe in any JDK version,
047 * nor will it be as Sun have closed the bug/RFE.
048 * </p>
049 *
050 * <p>Only formatting is supported, but all patterns are compatible with
051 * SimpleDateFormat (except time zones - see below).</p>
052 *
053 * <p>Java 1.4 introduced a new pattern letter, <code>'Z'</code>, to represent
054 * time zones in RFC822 format (eg. <code>+0800</code> or <code>-1100</code>).
055 * This pattern letter can be used here (on all JDK versions).</p>
056 *
057 * <p>In addition, the pattern <code>'ZZ'</code> has been made to represent
058 * ISO8601 full format time zones (eg. <code>+08:00</code> or <code>-11:00</code>).
059 * This introduces a minor incompatibility with Java 1.4, but at a gain of
060 * useful functionality.</p>
061 *
062 * @author TeaTrove project
063 * @author Brian S O'Neill
064 * @author Sean Schofield
065 * @author Gary Gregory
066 * @author Stephen Colebourne
067 * @author Nikolay Metchev
068 * @since 2.0
069 * @version $Id: FastDateFormat.java 590552 2007-10-31 04:04:32Z bayard $
070 */
071 public class FastDateFormat extends Format {
072 // A lot of the speed in this class comes from caching, but some comes
073 // from the special int to StringBuffer conversion.
074 //
075 // The following produces a padded 2 digit number:
076 // buffer.append((char)(value / 10 + '0'));
077 // buffer.append((char)(value % 10 + '0'));
078 //
079 // Note that the fastest append to StringBuffer is a single char (used here).
080 // Note that Integer.toString() is not called, the conversion is simply
081 // taking the value and adding (mathematically) the ASCII value for '0'.
082 // So, don't change this code! It works and is very fast.
083
084 /**
085 * Required for serialization support.
086 *
087 * @see java.io.Serializable
088 */
089 private static final long serialVersionUID = 1L;
090
091 /**
092 * FULL locale dependent date or time style.
093 */
094 public static final int FULL = DateFormat.FULL;
095 /**
096 * LONG locale dependent date or time style.
097 */
098 public static final int LONG = DateFormat.LONG;
099 /**
100 * MEDIUM locale dependent date or time style.
101 */
102 public static final int MEDIUM = DateFormat.MEDIUM;
103 /**
104 * SHORT locale dependent date or time style.
105 */
106 public static final int SHORT = DateFormat.SHORT;
107
108 private static String cDefaultPattern;
109
110 private static final Map cInstanceCache = new HashMap(7);
111 private static final Map cDateInstanceCache = new HashMap(7);
112 private static final Map cTimeInstanceCache = new HashMap(7);
113 private static final Map cDateTimeInstanceCache = new HashMap(7);
114 private static final Map cTimeZoneDisplayCache = new HashMap(7);
115
116 /**
117 * The pattern.
118 */
119 private final String mPattern;
120 /**
121 * The time zone.
122 */
123 private final TimeZone mTimeZone;
124 /**
125 * Whether the time zone overrides any on Calendars.
126 */
127 private final boolean mTimeZoneForced;
128 /**
129 * The locale.
130 */
131 private final Locale mLocale;
132 /**
133 * Whether the locale overrides the default.
134 */
135 private final boolean mLocaleForced;
136 /**
137 * The parsed rules.
138 */
139 private transient Rule[] mRules;
140 /**
141 * The estimated maximum length.
142 */
143 private transient int mMaxLengthEstimate;
144
145 //-----------------------------------------------------------------------
146 /**
147 * <p>Gets a formatter instance using the default pattern in the
148 * default locale.</p>
149 *
150 * @return a date/time formatter
151 */
152 public static FastDateFormat getInstance() {
153 return getInstance(getDefaultPattern(), null, null);
154 }
155
156 /**
157 * <p>Gets a formatter instance using the specified pattern in the
158 * default locale.</p>
159 *
160 * @param pattern {@link java.text.SimpleDateFormat} compatible
161 * pattern
162 * @return a pattern based date/time formatter
163 * @throws IllegalArgumentException if pattern is invalid
164 */
165 public static FastDateFormat getInstance(String pattern) {
166 return getInstance(pattern, null, null);
167 }
168
169 /**
170 * <p>Gets a formatter instance using the specified pattern and
171 * time zone.</p>
172 *
173 * @param pattern {@link java.text.SimpleDateFormat} compatible
174 * pattern
175 * @param timeZone optional time zone, overrides time zone of
176 * formatted date
177 * @return a pattern based date/time formatter
178 * @throws IllegalArgumentException if pattern is invalid
179 */
180 public static FastDateFormat getInstance(String pattern, TimeZone timeZone) {
181 return getInstance(pattern, timeZone, null);
182 }
183
184 /**
185 * <p>Gets a formatter instance using the specified pattern and
186 * locale.</p>
187 *
188 * @param pattern {@link java.text.SimpleDateFormat} compatible
189 * pattern
190 * @param locale optional locale, overrides system locale
191 * @return a pattern based date/time formatter
192 * @throws IllegalArgumentException if pattern is invalid
193 */
194 public static FastDateFormat getInstance(String pattern, Locale locale) {
195 return getInstance(pattern, null, locale);
196 }
197
198 /**
199 * <p>Gets a formatter instance using the specified pattern, time zone
200 * and locale.</p>
201 *
202 * @param pattern {@link java.text.SimpleDateFormat} compatible
203 * pattern
204 * @param timeZone optional time zone, overrides time zone of
205 * formatted date
206 * @param locale optional locale, overrides system locale
207 * @return a pattern based date/time formatter
208 * @throws IllegalArgumentException if pattern is invalid
209 * or <code>null</code>
210 */
211 public static synchronized FastDateFormat getInstance(String pattern, TimeZone timeZone, Locale locale) {
212 FastDateFormat emptyFormat = new FastDateFormat(pattern, timeZone, locale);
213 FastDateFormat format = (FastDateFormat) cInstanceCache.get(emptyFormat);
214 if (format == null) {
215 format = emptyFormat;
216 format.init(); // convert shell format into usable one
217 cInstanceCache.put(format, format); // this is OK!
218 }
219 return format;
220 }
221
222 //-----------------------------------------------------------------------
223 /**
224 * <p>Gets a date formatter instance using the specified style in the
225 * default time zone and locale.</p>
226 *
227 * @param style date style: FULL, LONG, MEDIUM, or SHORT
228 * @return a localized standard date formatter
229 * @throws IllegalArgumentException if the Locale has no date
230 * pattern defined
231 * @since 2.1
232 */
233 public static FastDateFormat getDateInstance(int style) {
234 return getDateInstance(style, null, null);
235 }
236
237 /**
238 * <p>Gets a date formatter instance using the specified style and
239 * locale in the default time zone.</p>
240 *
241 * @param style date style: FULL, LONG, MEDIUM, or SHORT
242 * @param locale optional locale, overrides system locale
243 * @return a localized standard date formatter
244 * @throws IllegalArgumentException if the Locale has no date
245 * pattern defined
246 * @since 2.1
247 */
248 public static FastDateFormat getDateInstance(int style, Locale locale) {
249 return getDateInstance(style, null, locale);
250 }
251
252 /**
253 * <p>Gets a date formatter instance using the specified style and
254 * time zone in the default locale.</p>
255 *
256 * @param style date style: FULL, LONG, MEDIUM, or SHORT
257 * @param timeZone optional time zone, overrides time zone of
258 * formatted date
259 * @return a localized standard date formatter
260 * @throws IllegalArgumentException if the Locale has no date
261 * pattern defined
262 * @since 2.1
263 */
264 public static FastDateFormat getDateInstance(int style, TimeZone timeZone) {
265 return getDateInstance(style, timeZone, null);
266 }
267 /**
268 * <p>Gets a date formatter instance using the specified style, time
269 * zone and locale.</p>
270 *
271 * @param style date style: FULL, LONG, MEDIUM, or SHORT
272 * @param timeZone optional time zone, overrides time zone of
273 * formatted date
274 * @param locale optional locale, overrides system locale
275 * @return a localized standard date formatter
276 * @throws IllegalArgumentException if the Locale has no date
277 * pattern defined
278 */
279 public static synchronized FastDateFormat getDateInstance(int style, TimeZone timeZone, Locale locale) {
280 Object key = new Integer(style);
281 if (timeZone != null) {
282 key = new Pair(key, timeZone);
283 }
284
285 if (locale == null) {
286 locale = Locale.getDefault();
287 }
288
289 key = new Pair(key, locale);
290
291 FastDateFormat format = (FastDateFormat) cDateInstanceCache.get(key);
292 if (format == null) {
293 try {
294 SimpleDateFormat formatter = (SimpleDateFormat) DateFormat.getDateInstance(style, locale);
295 String pattern = formatter.toPattern();
296 format = getInstance(pattern, timeZone, locale);
297 cDateInstanceCache.put(key, format);
298
299 } catch (ClassCastException ex) {
300 throw new IllegalArgumentException("No date pattern for locale: " + locale);
301 }
302 }
303 return format;
304 }
305
306 //-----------------------------------------------------------------------
307 /**
308 * <p>Gets a time formatter instance using the specified style in the
309 * default time zone and locale.</p>
310 *
311 * @param style time style: FULL, LONG, MEDIUM, or SHORT
312 * @return a localized standard time formatter
313 * @throws IllegalArgumentException if the Locale has no time
314 * pattern defined
315 * @since 2.1
316 */
317 public static FastDateFormat getTimeInstance(int style) {
318 return getTimeInstance(style, null, null);
319 }
320
321 /**
322 * <p>Gets a time formatter instance using the specified style and
323 * locale in the default time zone.</p>
324 *
325 * @param style time style: FULL, LONG, MEDIUM, or SHORT
326 * @param locale optional locale, overrides system locale
327 * @return a localized standard time formatter
328 * @throws IllegalArgumentException if the Locale has no time
329 * pattern defined
330 * @since 2.1
331 */
332 public static FastDateFormat getTimeInstance(int style, Locale locale) {
333 return getTimeInstance(style, null, locale);
334 }
335
336 /**
337 * <p>Gets a time formatter instance using the specified style and
338 * time zone in the default locale.</p>
339 *
340 * @param style time style: FULL, LONG, MEDIUM, or SHORT
341 * @param timeZone optional time zone, overrides time zone of
342 * formatted time
343 * @return a localized standard time formatter
344 * @throws IllegalArgumentException if the Locale has no time
345 * pattern defined
346 * @since 2.1
347 */
348 public static FastDateFormat getTimeInstance(int style, TimeZone timeZone) {
349 return getTimeInstance(style, timeZone, null);
350 }
351
352 /**
353 * <p>Gets a time formatter instance using the specified style, time
354 * zone and locale.</p>
355 *
356 * @param style time style: FULL, LONG, MEDIUM, or SHORT
357 * @param timeZone optional time zone, overrides time zone of
358 * formatted time
359 * @param locale optional locale, overrides system locale
360 * @return a localized standard time formatter
361 * @throws IllegalArgumentException if the Locale has no time
362 * pattern defined
363 */
364 public static synchronized FastDateFormat getTimeInstance(int style, TimeZone timeZone, Locale locale) {
365 Object key = new Integer(style);
366 if (timeZone != null) {
367 key = new Pair(key, timeZone);
368 }
369 if (locale != null) {
370 key = new Pair(key, locale);
371 }
372
373 FastDateFormat format = (FastDateFormat) cTimeInstanceCache.get(key);
374 if (format == null) {
375 if (locale == null) {
376 locale = Locale.getDefault();
377 }
378
379 try {
380 SimpleDateFormat formatter = (SimpleDateFormat) DateFormat.getTimeInstance(style, locale);
381 String pattern = formatter.toPattern();
382 format = getInstance(pattern, timeZone, locale);
383 cTimeInstanceCache.put(key, format);
384
385 } catch (ClassCastException ex) {
386 throw new IllegalArgumentException("No date pattern for locale: " + locale);
387 }
388 }
389 return format;
390 }
391
392 //-----------------------------------------------------------------------
393 /**
394 * <p>Gets a date/time formatter instance using the specified style
395 * in the default time zone and locale.</p>
396 *
397 * @param dateStyle date style: FULL, LONG, MEDIUM, or SHORT
398 * @param timeStyle time style: FULL, LONG, MEDIUM, or SHORT
399 * @return a localized standard date/time formatter
400 * @throws IllegalArgumentException if the Locale has no date/time
401 * pattern defined
402 * @since 2.1
403 */
404 public static FastDateFormat getDateTimeInstance(
405 int dateStyle, int timeStyle) {
406 return getDateTimeInstance(dateStyle, timeStyle, null, null);
407 }
408
409 /**
410 * <p>Gets a date/time formatter instance using the specified style and
411 * locale in the default time zone.</p>
412 *
413 * @param dateStyle date style: FULL, LONG, MEDIUM, or SHORT
414 * @param timeStyle time style: FULL, LONG, MEDIUM, or SHORT
415 * @param locale optional locale, overrides system locale
416 * @return a localized standard date/time formatter
417 * @throws IllegalArgumentException if the Locale has no date/time
418 * pattern defined
419 * @since 2.1
420 */
421 public static FastDateFormat getDateTimeInstance(
422 int dateStyle, int timeStyle, Locale locale) {
423 return getDateTimeInstance(dateStyle, timeStyle, null, locale);
424 }
425
426 /**
427 * <p>Gets a date/time formatter instance using the specified style and
428 * time zone in the default locale.</p>
429 *
430 * @param dateStyle date style: FULL, LONG, MEDIUM, or SHORT
431 * @param timeStyle time style: FULL, LONG, MEDIUM, or SHORT
432 * @param timeZone optional time zone, overrides time zone of
433 * formatted date
434 * @return a localized standard date/time formatter
435 * @throws IllegalArgumentException if the Locale has no date/time
436 * pattern defined
437 * @since 2.1
438 */
439 public static FastDateFormat getDateTimeInstance(
440 int dateStyle, int timeStyle, TimeZone timeZone) {
441 return getDateTimeInstance(dateStyle, timeStyle, timeZone, null);
442 }
443 /**
444 * <p>Gets a date/time formatter instance using the specified style,
445 * time zone and locale.</p>
446 *
447 * @param dateStyle date style: FULL, LONG, MEDIUM, or SHORT
448 * @param timeStyle time style: FULL, LONG, MEDIUM, or SHORT
449 * @param timeZone optional time zone, overrides time zone of
450 * formatted date
451 * @param locale optional locale, overrides system locale
452 * @return a localized standard date/time formatter
453 * @throws IllegalArgumentException if the Locale has no date/time
454 * pattern defined
455 */
456 public static synchronized FastDateFormat getDateTimeInstance(int dateStyle, int timeStyle, TimeZone timeZone,
457 Locale locale) {
458
459 Object key = new Pair(new Integer(dateStyle), new Integer(timeStyle));
460 if (timeZone != null) {
461 key = new Pair(key, timeZone);
462 }
463 if (locale == null) {
464 locale = Locale.getDefault();
465 }
466 key = new Pair(key, locale);
467
468 FastDateFormat format = (FastDateFormat) cDateTimeInstanceCache.get(key);
469 if (format == null) {
470 try {
471 SimpleDateFormat formatter = (SimpleDateFormat) DateFormat.getDateTimeInstance(dateStyle, timeStyle,
472 locale);
473 String pattern = formatter.toPattern();
474 format = getInstance(pattern, timeZone, locale);
475 cDateTimeInstanceCache.put(key, format);
476
477 } catch (ClassCastException ex) {
478 throw new IllegalArgumentException("No date time pattern for locale: " + locale);
479 }
480 }
481 return format;
482 }
483
484 //-----------------------------------------------------------------------
485 /**
486 * <p>Gets the time zone display name, using a cache for performance.</p>
487 *
488 * @param tz the zone to query
489 * @param daylight true if daylight savings
490 * @param style the style to use <code>TimeZone.LONG</code>
491 * or <code>TimeZone.SHORT</code>
492 * @param locale the locale to use
493 * @return the textual name of the time zone
494 */
495 static synchronized String getTimeZoneDisplay(TimeZone tz, boolean daylight, int style, Locale locale) {
496 Object key = new TimeZoneDisplayKey(tz, daylight, style, locale);
497 String value = (String) cTimeZoneDisplayCache.get(key);
498 if (value == null) {
499 // This is a very slow call, so cache the results.
500 value = tz.getDisplayName(daylight, style, locale);
501 cTimeZoneDisplayCache.put(key, value);
502 }
503 return value;
504 }
505
506 /**
507 * <p>Gets the default pattern.</p>
508 *
509 * @return the default pattern
510 */
511 private static synchronized String getDefaultPattern() {
512 if (cDefaultPattern == null) {
513 cDefaultPattern = new SimpleDateFormat().toPattern();
514 }
515 return cDefaultPattern;
516 }
517
518 // Constructor
519 //-----------------------------------------------------------------------
520 /**
521 * <p>Constructs a new FastDateFormat.</p>
522 *
523 * @param pattern {@link java.text.SimpleDateFormat} compatible
524 * pattern
525 * @param timeZone time zone to use, <code>null</code> means use
526 * default for <code>Date</code> and value within for
527 * <code>Calendar</code>
528 * @param locale locale, <code>null</code> means use system
529 * default
530 * @throws IllegalArgumentException if pattern is invalid or
531 * <code>null</code>
532 */
533 protected FastDateFormat(String pattern, TimeZone timeZone, Locale locale) {
534 super();
535 if (pattern == null) {
536 throw new IllegalArgumentException("The pattern must not be null");
537 }
538 mPattern = pattern;
539
540 mTimeZoneForced = (timeZone != null);
541 if (timeZone == null) {
542 timeZone = TimeZone.getDefault();
543 }
544 mTimeZone = timeZone;
545
546 mLocaleForced = (locale != null);
547 if (locale == null) {
548 locale = Locale.getDefault();
549 }
550 mLocale = locale;
551 }
552
553 /**
554 * <p>Initializes the instance for first use.</p>
555 */
556 protected void init() {
557 List rulesList = parsePattern();
558 mRules = (Rule[]) rulesList.toArray(new Rule[rulesList.size()]);
559
560 int len = 0;
561 for (int i=mRules.length; --i >= 0; ) {
562 len += mRules[i].estimateLength();
563 }
564
565 mMaxLengthEstimate = len;
566 }
567
568 // Parse the pattern
569 //-----------------------------------------------------------------------
570 /**
571 * <p>Returns a list of Rules given a pattern.</p>
572 *
573 * @return a <code>List</code> of Rule objects
574 * @throws IllegalArgumentException if pattern is invalid
575 */
576 protected List parsePattern() {
577 DateFormatSymbols symbols = new DateFormatSymbols(mLocale);
578 List rules = new ArrayList();
579
580 String[] ERAs = symbols.getEras();
581 String[] months = symbols.getMonths();
582 String[] shortMonths = symbols.getShortMonths();
583 String[] weekdays = symbols.getWeekdays();
584 String[] shortWeekdays = symbols.getShortWeekdays();
585 String[] AmPmStrings = symbols.getAmPmStrings();
586
587 int length = mPattern.length();
588 int[] indexRef = new int[1];
589
590 for (int i = 0; i < length; i++) {
591 indexRef[0] = i;
592 String token = parseToken(mPattern, indexRef);
593 i = indexRef[0];
594
595 int tokenLen = token.length();
596 if (tokenLen == 0) {
597 break;
598 }
599
600 Rule rule;
601 char c = token.charAt(0);
602
603 switch (c) {
604 case 'G': // era designator (text)
605 rule = new TextField(Calendar.ERA, ERAs);
606 break;
607 case 'y': // year (number)
608 if (tokenLen >= 4) {
609 rule = selectNumberRule(Calendar.YEAR, tokenLen);
610 } else {
611 rule = TwoDigitYearField.INSTANCE;
612 }
613 break;
614 case 'M': // month in year (text and number)
615 if (tokenLen >= 4) {
616 rule = new TextField(Calendar.MONTH, months);
617 } else if (tokenLen == 3) {
618 rule = new TextField(Calendar.MONTH, shortMonths);
619 } else if (tokenLen == 2) {
620 rule = TwoDigitMonthField.INSTANCE;
621 } else {
622 rule = UnpaddedMonthField.INSTANCE;
623 }
624 break;
625 case 'd': // day in month (number)
626 rule = selectNumberRule(Calendar.DAY_OF_MONTH, tokenLen);
627 break;
628 case 'h': // hour in am/pm (number, 1..12)
629 rule = new TwelveHourField(selectNumberRule(Calendar.HOUR, tokenLen));
630 break;
631 case 'H': // hour in day (number, 0..23)
632 rule = selectNumberRule(Calendar.HOUR_OF_DAY, tokenLen);
633 break;
634 case 'm': // minute in hour (number)
635 rule = selectNumberRule(Calendar.MINUTE, tokenLen);
636 break;
637 case 's': // second in minute (number)
638 rule = selectNumberRule(Calendar.SECOND, tokenLen);
639 break;
640 case 'S': // millisecond (number)
641 rule = selectNumberRule(Calendar.MILLISECOND, tokenLen);
642 break;
643 case 'E': // day in week (text)
644 rule = new TextField(Calendar.DAY_OF_WEEK, tokenLen < 4 ? shortWeekdays : weekdays);
645 break;
646 case 'D': // day in year (number)
647 rule = selectNumberRule(Calendar.DAY_OF_YEAR, tokenLen);
648 break;
649 case 'F': // day of week in month (number)
650 rule = selectNumberRule(Calendar.DAY_OF_WEEK_IN_MONTH, tokenLen);
651 break;
652 case 'w': // week in year (number)
653 rule = selectNumberRule(Calendar.WEEK_OF_YEAR, tokenLen);
654 break;
655 case 'W': // week in month (number)
656 rule = selectNumberRule(Calendar.WEEK_OF_MONTH, tokenLen);
657 break;
658 case 'a': // am/pm marker (text)
659 rule = new TextField(Calendar.AM_PM, AmPmStrings);
660 break;
661 case 'k': // hour in day (1..24)
662 rule = new TwentyFourHourField(selectNumberRule(Calendar.HOUR_OF_DAY, tokenLen));
663 break;
664 case 'K': // hour in am/pm (0..11)
665 rule = selectNumberRule(Calendar.HOUR, tokenLen);
666 break;
667 case 'z': // time zone (text)
668 if (tokenLen >= 4) {
669 rule = new TimeZoneNameRule(mTimeZone, mTimeZoneForced, mLocale, TimeZone.LONG);
670 } else {
671 rule = new TimeZoneNameRule(mTimeZone, mTimeZoneForced, mLocale, TimeZone.SHORT);
672 }
673 break;
674 case 'Z': // time zone (value)
675 if (tokenLen == 1) {
676 rule = TimeZoneNumberRule.INSTANCE_NO_COLON;
677 } else {
678 rule = TimeZoneNumberRule.INSTANCE_COLON;
679 }
680 break;
681 case '\'': // literal text
682 String sub = token.substring(1);
683 if (sub.length() == 1) {
684 rule = new CharacterLiteral(sub.charAt(0));
685 } else {
686 rule = new StringLiteral(sub);
687 }
688 break;
689 default:
690 throw new IllegalArgumentException("Illegal pattern component: " + token);
691 }
692
693 rules.add(rule);
694 }
695
696 return rules;
697 }
698
699 /**
700 * <p>Performs the parsing of tokens.</p>
701 *
702 * @param pattern the pattern
703 * @param indexRef index references
704 * @return parsed token
705 */
706 protected String parseToken(String pattern, int[] indexRef) {
707 StringBuffer buf = new StringBuffer();
708
709 int i = indexRef[0];
710 int length = pattern.length();
711
712 char c = pattern.charAt(i);
713 if (c >= 'A' && c <= 'Z' || c >= 'a' && c <= 'z') {
714 // Scan a run of the same character, which indicates a time
715 // pattern.
716 buf.append(c);
717
718 while (i + 1 < length) {
719 char peek = pattern.charAt(i + 1);
720 if (peek == c) {
721 buf.append(c);
722 i++;
723 } else {
724 break;
725 }
726 }
727 } else {
728 // This will identify token as text.
729 buf.append('\'');
730
731 boolean inLiteral = false;
732
733 for (; i < length; i++) {
734 c = pattern.charAt(i);
735
736 if (c == '\'') {
737 if (i + 1 < length && pattern.charAt(i + 1) == '\'') {
738 // '' is treated as escaped '
739 i++;
740 buf.append(c);
741 } else {
742 inLiteral = !inLiteral;
743 }
744 } else if (!inLiteral &&
745 (c >= 'A' && c <= 'Z' || c >= 'a' && c <= 'z')) {
746 i--;
747 break;
748 } else {
749 buf.append(c);
750 }
751 }
752 }
753
754 indexRef[0] = i;
755 return buf.toString();
756 }
757
758 /**
759 * <p>Gets an appropriate rule for the padding required.</p>
760 *
761 * @param field the field to get a rule for
762 * @param padding the padding required
763 * @return a new rule with the correct padding
764 */
765 protected NumberRule selectNumberRule(int field, int padding) {
766 switch (padding) {
767 case 1:
768 return new UnpaddedNumberField(field);
769 case 2:
770 return new TwoDigitNumberField(field);
771 default:
772 return new PaddedNumberField(field, padding);
773 }
774 }
775
776 // Format methods
777 //-----------------------------------------------------------------------
778 /**
779 * <p>Formats a <code>Date</code>, <code>Calendar</code> or
780 * <code>Long</code> (milliseconds) object.</p>
781 *
782 * @param obj the object to format
783 * @param toAppendTo the buffer to append to
784 * @param pos the position - ignored
785 * @return the buffer passed in
786 */
787 public StringBuffer format(Object obj, StringBuffer toAppendTo, FieldPosition pos) {
788 if (obj instanceof Date) {
789 return format((Date) obj, toAppendTo);
790 } else if (obj instanceof Calendar) {
791 return format((Calendar) obj, toAppendTo);
792 } else if (obj instanceof Long) {
793 return format(((Long) obj).longValue(), toAppendTo);
794 } else {
795 throw new IllegalArgumentException("Unknown class: " +
796 (obj == null ? "<null>" : obj.getClass().getName()));
797 }
798 }
799
800 /**
801 * <p>Formats a millisecond <code>long</code> value.</p>
802 *
803 * @param millis the millisecond value to format
804 * @return the formatted string
805 * @since 2.1
806 */
807 public String format(long millis) {
808 return format(new Date(millis));
809 }
810
811 /**
812 * <p>Formats a <code>Date</code> object.</p>
813 *
814 * @param date the date to format
815 * @return the formatted string
816 */
817 public String format(Date date) {
818 Calendar c = new GregorianCalendar(mTimeZone);
819 c.setTime(date);
820 return applyRules(c, new StringBuffer(mMaxLengthEstimate)).toString();
821 }
822
823 /**
824 * <p>Formats a <code>Calendar</code> object.</p>
825 *
826 * @param calendar the calendar to format
827 * @return the formatted string
828 */
829 public String format(Calendar calendar) {
830 return format(calendar, new StringBuffer(mMaxLengthEstimate)).toString();
831 }
832
833 /**
834 * <p>Formats a milliseond <code>long</code> value into the
835 * supplied <code>StringBuffer</code>.</p>
836 *
837 * @param millis the millisecond value to format
838 * @param buf the buffer to format into
839 * @return the specified string buffer
840 * @since 2.1
841 */
842 public StringBuffer format(long millis, StringBuffer buf) {
843 return format(new Date(millis), buf);
844 }
845
846 /**
847 * <p>Formats a <code>Date</code> object into the
848 * supplied <code>StringBuffer</code>.</p>
849 *
850 * @param date the date to format
851 * @param buf the buffer to format into
852 * @return the specified string buffer
853 */
854 public StringBuffer format(Date date, StringBuffer buf) {
855 Calendar c = new GregorianCalendar(mTimeZone);
856 c.setTime(date);
857 return applyRules(c, buf);
858 }
859
860 /**
861 * <p>Formats a <code>Calendar</code> object into the
862 * supplied <code>StringBuffer</code>.</p>
863 *
864 * @param calendar the calendar to format
865 * @param buf the buffer to format into
866 * @return the specified string buffer
867 */
868 public StringBuffer format(Calendar calendar, StringBuffer buf) {
869 if (mTimeZoneForced) {
870 calendar = (Calendar) calendar.clone();
871 calendar.setTimeZone(mTimeZone);
872 }
873 return applyRules(calendar, buf);
874 }
875
876 /**
877 * <p>Performs the formatting by applying the rules to the
878 * specified calendar.</p>
879 *
880 * @param calendar the calendar to format
881 * @param buf the buffer to format into
882 * @return the specified string buffer
883 */
884 protected StringBuffer applyRules(Calendar calendar, StringBuffer buf) {
885 Rule[] rules = mRules;
886 int len = mRules.length;
887 for (int i = 0; i < len; i++) {
888 rules[i].appendTo(buf, calendar);
889 }
890 return buf;
891 }
892
893 // Parsing
894 //-----------------------------------------------------------------------
895 /**
896 * <p>Parsing is not supported.</p>
897 *
898 * @param source the string to parse
899 * @param pos the parsing position
900 * @return <code>null</code> as not supported
901 */
902 public Object parseObject(String source, ParsePosition pos) {
903 pos.setIndex(0);
904 pos.setErrorIndex(0);
905 return null;
906 }
907
908 // Accessors
909 //-----------------------------------------------------------------------
910 /**
911 * <p>Gets the pattern used by this formatter.</p>
912 *
913 * @return the pattern, {@link java.text.SimpleDateFormat} compatible
914 */
915 public String getPattern() {
916 return mPattern;
917 }
918
919 /**
920 * <p>Gets the time zone used by this formatter.</p>
921 *
922 * <p>This zone is always used for <code>Date</code> formatting.
923 * If a <code>Calendar</code> is passed in to be formatted, the
924 * time zone on that may be used depending on
925 * {@link #getTimeZoneOverridesCalendar()}.</p>
926 *
927 * @return the time zone
928 */
929 public TimeZone getTimeZone() {
930 return mTimeZone;
931 }
932
933 /**
934 * <p>Returns <code>true</code> if the time zone of the
935 * calendar overrides the time zone of the formatter.</p>
936 *
937 * @return <code>true</code> if time zone of formatter
938 * overridden for calendars
939 */
940 public boolean getTimeZoneOverridesCalendar() {
941 return mTimeZoneForced;
942 }
943
944 /**
945 * <p>Gets the locale used by this formatter.</p>
946 *
947 * @return the locale
948 */
949 public Locale getLocale() {
950 return mLocale;
951 }
952
953 /**
954 * <p>Gets an estimate for the maximum string length that the
955 * formatter will produce.</p>
956 *
957 * <p>The actual formatted length will almost always be less than or
958 * equal to this amount.</p>
959 *
960 * @return the maximum formatted length
961 */
962 public int getMaxLengthEstimate() {
963 return mMaxLengthEstimate;
964 }
965
966 // Basics
967 //-----------------------------------------------------------------------
968 /**
969 * <p>Compares two objects for equality.</p>
970 *
971 * @param obj the object to compare to
972 * @return <code>true</code> if equal
973 */
974 public boolean equals(Object obj) {
975 if (obj instanceof FastDateFormat == false) {
976 return false;
977 }
978 FastDateFormat other = (FastDateFormat) obj;
979 if (
980 (mPattern == other.mPattern || mPattern.equals(other.mPattern)) &&
981 (mTimeZone == other.mTimeZone || mTimeZone.equals(other.mTimeZone)) &&
982 (mLocale == other.mLocale || mLocale.equals(other.mLocale)) &&
983 (mTimeZoneForced == other.mTimeZoneForced) &&
984 (mLocaleForced == other.mLocaleForced)
985 ) {
986 return true;
987 }
988 return false;
989 }
990
991 /**
992 * <p>Returns a hashcode compatible with equals.</p>
993 *
994 * @return a hashcode compatible with equals
995 */
996 public int hashCode() {
997 int total = 0;
998 total += mPattern.hashCode();
999 total += mTimeZone.hashCode();
1000 total += (mTimeZoneForced ? 1 : 0);
1001 total += mLocale.hashCode();
1002 total += (mLocaleForced ? 1 : 0);
1003 return total;
1004 }
1005
1006 /**
1007 * <p>Gets a debugging string version of this formatter.</p>
1008 *
1009 * @return a debugging string
1010 */
1011 public String toString() {
1012 return "FastDateFormat[" + mPattern + "]";
1013 }
1014
1015 // Serializing
1016 //-----------------------------------------------------------------------
1017 /**
1018 * Create the object after serialization. This implementation reinitializes the
1019 * transient properties.
1020 *
1021 * @param in ObjectInputStream from which the object is being deserialized.
1022 * @throws IOException if there is an IO issue.
1023 * @throws ClassNotFoundException if a class cannot be found.
1024 */
1025 private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
1026 in.defaultReadObject();
1027 init();
1028 }
1029
1030 // Rules
1031 //-----------------------------------------------------------------------
1032 /**
1033 * <p>Inner class defining a rule.</p>
1034 */
1035 private interface Rule {
1036 /**
1037 * Returns the estimated lentgh of the result.
1038 *
1039 * @return the estimated length
1040 */
1041 int estimateLength();
1042
1043 /**
1044 * Appends the value of the specified calendar to the output buffer based on the rule implementation.
1045 *
1046 * @param buffer the output buffer
1047 * @param calendar calendar to be appended
1048 */
1049 void appendTo(StringBuffer buffer, Calendar calendar);
1050 }
1051
1052 /**
1053 * <p>Inner class defining a numeric rule.</p>
1054 */
1055 private interface NumberRule extends Rule {
1056 /**
1057 * Appends the specified value to the output buffer based on the rule implementation.
1058 *
1059 * @param buffer the output buffer
1060 * @param value the value to be appended
1061 */
1062 void appendTo(StringBuffer buffer, int value);
1063 }
1064
1065 /**
1066 * <p>Inner class to output a constant single character.</p>
1067 */
1068 private static class CharacterLiteral implements Rule {
1069 private final char mValue;
1070
1071 /**
1072 * Constructs a new instance of <code>CharacterLiteral</code>
1073 * to hold the specified value.
1074 *
1075 * @param value the character literal
1076 */
1077 CharacterLiteral(char value) {
1078 mValue = value;
1079 }
1080
1081 /**
1082 * {@inheritDoc}
1083 */
1084 public int estimateLength() {
1085 return 1;
1086 }
1087
1088 /**
1089 * {@inheritDoc}
1090 */
1091 public void appendTo(StringBuffer buffer, Calendar calendar) {
1092 buffer.append(mValue);
1093 }
1094 }
1095
1096 /**
1097 * <p>Inner class to output a constant string.</p>
1098 */
1099 private static class StringLiteral implements Rule {
1100 private final String mValue;
1101
1102 /**
1103 * Constructs a new instance of <code>StringLiteral</code>
1104 * to hold the specified value.
1105 *
1106 * @param value the string literal
1107 */
1108 StringLiteral(String value) {
1109 mValue = value;
1110 }
1111
1112 /**
1113 * {@inheritDoc}
1114 */
1115 public int estimateLength() {
1116 return mValue.length();
1117 }
1118
1119 /**
1120 * {@inheritDoc}
1121 */
1122 public void appendTo(StringBuffer buffer, Calendar calendar) {
1123 buffer.append(mValue);
1124 }
1125 }
1126
1127 /**
1128 * <p>Inner class to output one of a set of values.</p>
1129 */
1130 private static class TextField implements Rule {
1131 private final int mField;
1132 private final String[] mValues;
1133
1134 /**
1135 * Constructs an instance of <code>TextField</code>
1136 * with the specified field and values.
1137 *
1138 * @param field the field
1139 * @param values the field values
1140 */
1141 TextField(int field, String[] values) {
1142 mField = field;
1143 mValues = values;
1144 }
1145
1146 /**
1147 * {@inheritDoc}
1148 */
1149 public int estimateLength() {
1150 int max = 0;
1151 for (int i=mValues.length; --i >= 0; ) {
1152 int len = mValues[i].length();
1153 if (len > max) {
1154 max = len;
1155 }
1156 }
1157 return max;
1158 }
1159
1160 /**
1161 * {@inheritDoc}
1162 */
1163 public void appendTo(StringBuffer buffer, Calendar calendar) {
1164 buffer.append(mValues[calendar.get(mField)]);
1165 }
1166 }
1167
1168 /**
1169 * <p>Inner class to output an unpadded number.</p>
1170 */
1171 private static class UnpaddedNumberField implements NumberRule {
1172 static final UnpaddedNumberField INSTANCE_YEAR = new UnpaddedNumberField(Calendar.YEAR);
1173
1174 private final int mField;
1175
1176 /**
1177 * Constructs an instance of <code>UnpadedNumberField</code> with the specified field.
1178 *
1179 * @param field the field
1180 */
1181 UnpaddedNumberField(int field) {
1182 mField = field;
1183 }
1184
1185 /**
1186 * {@inheritDoc}
1187 */
1188 public int estimateLength() {
1189 return 4;
1190 }
1191
1192 /**
1193 * {@inheritDoc}
1194 */
1195 public void appendTo(StringBuffer buffer, Calendar calendar) {
1196 appendTo(buffer, calendar.get(mField));
1197 }
1198
1199 /**
1200 * {@inheritDoc}
1201 */
1202 public final void appendTo(StringBuffer buffer, int value) {
1203 if (value < 10) {
1204 buffer.append((char)(value + '0'));
1205 } else if (value < 100) {
1206 buffer.append((char)(value / 10 + '0'));
1207 buffer.append((char)(value % 10 + '0'));
1208 } else {
1209 buffer.append(Integer.toString(value));
1210 }
1211 }
1212 }
1213
1214 /**
1215 * <p>Inner class to output an unpadded month.</p>
1216 */
1217 private static class UnpaddedMonthField implements NumberRule {
1218 static final UnpaddedMonthField INSTANCE = new UnpaddedMonthField();
1219
1220 /**
1221 * Constructs an instance of <code>UnpaddedMonthField</code>.
1222 *
1223 */
1224 UnpaddedMonthField() {
1225 super();
1226 }
1227
1228 /**
1229 * {@inheritDoc}
1230 */
1231 public int estimateLength() {
1232 return 2;
1233 }
1234
1235 /**
1236 * {@inheritDoc}
1237 */
1238 public void appendTo(StringBuffer buffer, Calendar calendar) {
1239 appendTo(buffer, calendar.get(Calendar.MONTH) + 1);
1240 }
1241
1242 /**
1243 * {@inheritDoc}
1244 */
1245 public final void appendTo(StringBuffer buffer, int value) {
1246 if (value < 10) {
1247 buffer.append((char)(value + '0'));
1248 } else {
1249 buffer.append((char)(value / 10 + '0'));
1250 buffer.append((char)(value % 10 + '0'));
1251 }
1252 }
1253 }
1254
1255 /**
1256 * <p>Inner class to output a padded number.</p>
1257 */
1258 private static class PaddedNumberField implements NumberRule {
1259 private final int mField;
1260 private final int mSize;
1261
1262 /**
1263 * Constructs an instance of <code>PaddedNumberField</code>.
1264 *
1265 * @param field the field
1266 * @param size size of the output field
1267 */
1268 PaddedNumberField(int field, int size) {
1269 if (size < 3) {
1270 // Should use UnpaddedNumberField or TwoDigitNumberField.
1271 throw new IllegalArgumentException();
1272 }
1273 mField = field;
1274 mSize = size;
1275 }
1276
1277 /**
1278 * {@inheritDoc}
1279 */
1280 public int estimateLength() {
1281 return 4;
1282 }
1283
1284 /**
1285 * {@inheritDoc}
1286 */
1287 public void appendTo(StringBuffer buffer, Calendar calendar) {
1288 appendTo(buffer, calendar.get(mField));
1289 }
1290
1291 /**
1292 * {@inheritDoc}
1293 */
1294 public final void appendTo(StringBuffer buffer, int value) {
1295 if (value < 100) {
1296 for (int i = mSize; --i >= 2; ) {
1297 buffer.append('0');
1298 }
1299 buffer.append((char)(value / 10 + '0'));
1300 buffer.append((char)(value % 10 + '0'));
1301 } else {
1302 int digits;
1303 if (value < 1000) {
1304 digits = 3;
1305 } else {
1306 Validate.isTrue(value > -1, "Negative values should not be possible", value);
1307 digits = Integer.toString(value).length();
1308 }
1309 for (int i = mSize; --i >= digits; ) {
1310 buffer.append('0');
1311 }
1312 buffer.append(Integer.toString(value));
1313 }
1314 }
1315 }
1316
1317 /**
1318 * <p>Inner class to output a two digit number.</p>
1319 */
1320 private static class TwoDigitNumberField implements NumberRule {
1321 private final int mField;
1322
1323 /**
1324 * Constructs an instance of <code>TwoDigitNumberField</code> with the specified field.
1325 *
1326 * @param field the field
1327 */
1328 TwoDigitNumberField(int field) {
1329 mField = field;
1330 }
1331
1332 /**
1333 * {@inheritDoc}
1334 */
1335 public int estimateLength() {
1336 return 2;
1337 }
1338
1339 /**
1340 * {@inheritDoc}
1341 */
1342 public void appendTo(StringBuffer buffer, Calendar calendar) {
1343 appendTo(buffer, calendar.get(mField));
1344 }
1345
1346 /**
1347 * {@inheritDoc}
1348 */
1349 public final void appendTo(StringBuffer buffer, int value) {
1350 if (value < 100) {
1351 buffer.append((char)(value / 10 + '0'));
1352 buffer.append((char)(value % 10 + '0'));
1353 } else {
1354 buffer.append(Integer.toString(value));
1355 }
1356 }
1357 }
1358
1359 /**
1360 * <p>Inner class to output a two digit year.</p>
1361 */
1362 private static class TwoDigitYearField implements NumberRule {
1363 static final TwoDigitYearField INSTANCE = new TwoDigitYearField();
1364
1365 /**
1366 * Constructs an instance of <code>TwoDigitYearField</code>.
1367 */
1368 TwoDigitYearField() {
1369 super();
1370 }
1371
1372 /**
1373 * {@inheritDoc}
1374 */
1375 public int estimateLength() {
1376 return 2;
1377 }
1378
1379 /**
1380 * {@inheritDoc}
1381 */
1382 public void appendTo(StringBuffer buffer, Calendar calendar) {
1383 appendTo(buffer, calendar.get(Calendar.YEAR) % 100);
1384 }
1385
1386 /**
1387 * {@inheritDoc}
1388 */
1389 public final void appendTo(StringBuffer buffer, int value) {
1390 buffer.append((char)(value / 10 + '0'));
1391 buffer.append((char)(value % 10 + '0'));
1392 }
1393 }
1394
1395 /**
1396 * <p>Inner class to output a two digit month.</p>
1397 */
1398 private static class TwoDigitMonthField implements NumberRule {
1399 static final TwoDigitMonthField INSTANCE = new TwoDigitMonthField();
1400
1401 /**
1402 * Constructs an instance of <code>TwoDigitMonthField</code>.
1403 */
1404 TwoDigitMonthField() {
1405 super();
1406 }
1407
1408 /**
1409 * {@inheritDoc}
1410 */
1411 public int estimateLength() {
1412 return 2;
1413 }
1414
1415 /**
1416 * {@inheritDoc}
1417 */
1418 public void appendTo(StringBuffer buffer, Calendar calendar) {
1419 appendTo(buffer, calendar.get(Calendar.MONTH) + 1);
1420 }
1421
1422 /**
1423 * {@inheritDoc}
1424 */
1425 public final void appendTo(StringBuffer buffer, int value) {
1426 buffer.append((char)(value / 10 + '0'));
1427 buffer.append((char)(value % 10 + '0'));
1428 }
1429 }
1430
1431 /**
1432 * <p>Inner class to output the twelve hour field.</p>
1433 */
1434 private static class TwelveHourField implements NumberRule {
1435 private final NumberRule mRule;
1436
1437 /**
1438 * Constructs an instance of <code>TwelveHourField</code> with the specified
1439 * <code>NumberRule</code>.
1440 *
1441 * @param rule the rule
1442 */
1443 TwelveHourField(NumberRule rule) {
1444 mRule = rule;
1445 }
1446
1447 /**
1448 * {@inheritDoc}
1449 */
1450 public int estimateLength() {
1451 return mRule.estimateLength();
1452 }
1453
1454 /**
1455 * {@inheritDoc}
1456 */
1457 public void appendTo(StringBuffer buffer, Calendar calendar) {
1458 int value = calendar.get(Calendar.HOUR);
1459 if (value == 0) {
1460 value = calendar.getLeastMaximum(Calendar.HOUR) + 1;
1461 }
1462 mRule.appendTo(buffer, value);
1463 }
1464
1465 /**
1466 * {@inheritDoc}
1467 */
1468 public void appendTo(StringBuffer buffer, int value) {
1469 mRule.appendTo(buffer, value);
1470 }
1471 }
1472
1473 /**
1474 * <p>Inner class to output the twenty four hour field.</p>
1475 */
1476 private static class TwentyFourHourField implements NumberRule {
1477 private final NumberRule mRule;
1478
1479 /**
1480 * Constructs an instance of <code>TwentyFourHourField</code> with the specified
1481 * <code>NumberRule</code>.
1482 *
1483 * @param rule the rule
1484 */
1485 TwentyFourHourField(NumberRule rule) {
1486 mRule = rule;
1487 }
1488
1489 /**
1490 * {@inheritDoc}
1491 */
1492 public int estimateLength() {
1493 return mRule.estimateLength();
1494 }
1495
1496 /**
1497 * {@inheritDoc}
1498 */
1499 public void appendTo(StringBuffer buffer, Calendar calendar) {
1500 int value = calendar.get(Calendar.HOUR_OF_DAY);
1501 if (value == 0) {
1502 value = calendar.getMaximum(Calendar.HOUR_OF_DAY) + 1;
1503 }
1504 mRule.appendTo(buffer, value);
1505 }
1506
1507 /**
1508 * {@inheritDoc}
1509 */
1510 public void appendTo(StringBuffer buffer, int value) {
1511 mRule.appendTo(buffer, value);
1512 }
1513 }
1514
1515 /**
1516 * <p>Inner class to output a time zone name.</p>
1517 */
1518 private static class TimeZoneNameRule implements Rule {
1519 private final TimeZone mTimeZone;
1520 private final boolean mTimeZoneForced;
1521 private final Locale mLocale;
1522 private final int mStyle;
1523 private final String mStandard;
1524 private final String mDaylight;
1525
1526 /**
1527 * Constructs an instance of <code>TimeZoneNameRule</code> with the specified properties.
1528 *
1529 * @param timeZone the time zone
1530 * @param timeZoneForced if <code>true</code> the time zone is forced into standard and daylight
1531 * @param locale the locale
1532 * @param style the style
1533 */
1534 TimeZoneNameRule(TimeZone timeZone, boolean timeZoneForced, Locale locale, int style) {
1535 mTimeZone = timeZone;
1536 mTimeZoneForced = timeZoneForced;
1537 mLocale = locale;
1538 mStyle = style;
1539
1540 if (timeZoneForced) {
1541 mStandard = getTimeZoneDisplay(timeZone, false, style, locale);
1542 mDaylight = getTimeZoneDisplay(timeZone, true, style, locale);
1543 } else {
1544 mStandard = null;
1545 mDaylight = null;
1546 }
1547 }
1548
1549 /**
1550 * {@inheritDoc}
1551 */
1552 public int estimateLength() {
1553 if (mTimeZoneForced) {
1554 return Math.max(mStandard.length(), mDaylight.length());
1555 } else if (mStyle == TimeZone.SHORT) {
1556 return 4;
1557 } else {
1558 return 40;
1559 }
1560 }
1561
1562 /**
1563 * {@inheritDoc}
1564 */
1565 public void appendTo(StringBuffer buffer, Calendar calendar) {
1566 if (mTimeZoneForced) {
1567 if (mTimeZone.useDaylightTime() && calendar.get(Calendar.DST_OFFSET) != 0) {
1568 buffer.append(mDaylight);
1569 } else {
1570 buffer.append(mStandard);
1571 }
1572 } else {
1573 TimeZone timeZone = calendar.getTimeZone();
1574 if (timeZone.useDaylightTime() && calendar.get(Calendar.DST_OFFSET) != 0) {
1575 buffer.append(getTimeZoneDisplay(timeZone, true, mStyle, mLocale));
1576 } else {
1577 buffer.append(getTimeZoneDisplay(timeZone, false, mStyle, mLocale));
1578 }
1579 }
1580 }
1581 }
1582
1583 /**
1584 * <p>Inner class to output a time zone as a number <code>+/-HHMM</code>
1585 * or <code>+/-HH:MM</code>.</p>
1586 */
1587 private static class TimeZoneNumberRule implements Rule {
1588 static final TimeZoneNumberRule INSTANCE_COLON = new TimeZoneNumberRule(true);
1589 static final TimeZoneNumberRule INSTANCE_NO_COLON = new TimeZoneNumberRule(false);
1590
1591 final boolean mColon;
1592
1593 /**
1594 * Constructs an instance of <code>TimeZoneNumberRule</code> with the specified properties.
1595 *
1596 * @param colon add colon between HH and MM in the output if <code>true</code>
1597 */
1598 TimeZoneNumberRule(boolean colon) {
1599 mColon = colon;
1600 }
1601
1602 /**
1603 * {@inheritDoc}
1604 */
1605 public int estimateLength() {
1606 return 5;
1607 }
1608
1609 /**
1610 * {@inheritDoc}
1611 */
1612 public void appendTo(StringBuffer buffer, Calendar calendar) {
1613 int offset = calendar.get(Calendar.ZONE_OFFSET) + calendar.get(Calendar.DST_OFFSET);
1614
1615 if (offset < 0) {
1616 buffer.append('-');
1617 offset = -offset;
1618 } else {
1619 buffer.append('+');
1620 }
1621
1622 int hours = offset / (60 * 60 * 1000);
1623 buffer.append((char)(hours / 10 + '0'));
1624 buffer.append((char)(hours % 10 + '0'));
1625
1626 if (mColon) {
1627 buffer.append(':');
1628 }
1629
1630 int minutes = offset / (60 * 1000) - 60 * hours;
1631 buffer.append((char)(minutes / 10 + '0'));
1632 buffer.append((char)(minutes % 10 + '0'));
1633 }
1634 }
1635
1636 // ----------------------------------------------------------------------
1637 /**
1638 * <p>Inner class that acts as a compound key for time zone names.</p>
1639 */
1640 private static class TimeZoneDisplayKey {
1641 private final TimeZone mTimeZone;
1642 private final int mStyle;
1643 private final Locale mLocale;
1644
1645 /**
1646 * Constructs an instance of <code>TimeZoneDisplayKey</code> with the specified properties.
1647 *
1648 * @param timeZone the time zone
1649 * @param daylight adjust the style for daylight saving time if <code>true</code>
1650 * @param style the timezone style
1651 * @param locale the timezone locale
1652 */
1653 TimeZoneDisplayKey(TimeZone timeZone,
1654 boolean daylight, int style, Locale locale) {
1655 mTimeZone = timeZone;
1656 if (daylight) {
1657 style |= 0x80000000;
1658 }
1659 mStyle = style;
1660 mLocale = locale;
1661 }
1662
1663 /**
1664 * {@inheritDoc}
1665 */
1666 public int hashCode() {
1667 return mStyle * 31 + mLocale.hashCode();
1668 }
1669
1670 /**
1671 * {@inheritDoc}
1672 */
1673 public boolean equals(Object obj) {
1674 if (this == obj) {
1675 return true;
1676 }
1677 if (obj instanceof TimeZoneDisplayKey) {
1678 TimeZoneDisplayKey other = (TimeZoneDisplayKey)obj;
1679 return
1680 mTimeZone.equals(other.mTimeZone) &&
1681 mStyle == other.mStyle &&
1682 mLocale.equals(other.mLocale);
1683 }
1684 return false;
1685 }
1686 }
1687
1688 // ----------------------------------------------------------------------
1689 /**
1690 * <p>Helper class for creating compound objects.</p>
1691 *
1692 * <p>One use for this class is to create a hashtable key
1693 * out of multiple objects.</p>
1694 */
1695 private static class Pair {
1696 private final Object mObj1;
1697 private final Object mObj2;
1698
1699 /**
1700 * Constructs an instance of <code>Pair</code> to hold the specified objects.
1701 * @param obj1 one object in the pair
1702 * @param obj2 second object in the pair
1703 */
1704 public Pair(Object obj1, Object obj2) {
1705 mObj1 = obj1;
1706 mObj2 = obj2;
1707 }
1708
1709 /**
1710 * {@inheritDoc}
1711 */
1712 public boolean equals(Object obj) {
1713 if (this == obj) {
1714 return true;
1715 }
1716
1717 if (!(obj instanceof Pair)) {
1718 return false;
1719 }
1720
1721 Pair key = (Pair)obj;
1722
1723 return
1724 (mObj1 == null ?
1725 key.mObj1 == null : mObj1.equals(key.mObj1)) &&
1726 (mObj2 == null ?
1727 key.mObj2 == null : mObj2.equals(key.mObj2));
1728 }
1729
1730 /**
1731 * {@inheritDoc}
1732 */
1733 public int hashCode() {
1734 return
1735 (mObj1 == null ? 0 : mObj1.hashCode()) +
1736 (mObj2 == null ? 0 : mObj2.hashCode());
1737 }
1738
1739 /**
1740 * {@inheritDoc}
1741 */
1742 public String toString() {
1743 return "[" + mObj1 + ':' + mObj2 + ']';
1744 }
1745 }
1746
1747 }