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 import org.apache.commons.lang.text.StrBuilder;
039
040 /**
041 * <p>FastDateFormat is a fast and thread-safe version of
042 * {@link java.text.SimpleDateFormat}.</p>
043 *
044 * <p>This class can be used as a direct replacement to
045 * <code>SimpleDateFormat</code> in most formatting situations.
046 * This class is especially useful in multi-threaded server environments.
047 * <code>SimpleDateFormat</code> is not thread-safe in any JDK version,
048 * nor will it be as Sun have closed the bug/RFE.
049 * </p>
050 *
051 * <p>Only formatting is supported, but all patterns are compatible with
052 * SimpleDateFormat (except time zones - see below).</p>
053 *
054 * <p>Java 1.4 introduced a new pattern letter, <code>'Z'</code>, to represent
055 * time zones in RFC822 format (eg. <code>+0800</code> or <code>-1100</code>).
056 * This pattern letter can be used here (on all JDK versions).</p>
057 *
058 * <p>In addition, the pattern <code>'ZZ'</code> has been made to represent
059 * ISO8601 full format time zones (eg. <code>+08:00</code> or <code>-11:00</code>).
060 * This introduces a minor incompatibility with Java 1.4, but at a gain of
061 * useful functionality.</p>
062 *
063 * @author Apache Software Foundation
064 * @author TeaTrove project
065 * @author Brian S O'Neill
066 * @author Sean Schofield
067 * @author Gary Gregory
068 * @author Nikolay Metchev
069 * @since 2.0
070 * @version $Id: FastDateFormat.java 1057072 2011-01-10 01:55:57Z niallp $
071 */
072 public class FastDateFormat extends Format {
073 // A lot of the speed in this class comes from caching, but some comes
074 // from the special int to StringBuffer conversion.
075 //
076 // The following produces a padded 2 digit number:
077 // buffer.append((char)(value / 10 + '0'));
078 // buffer.append((char)(value % 10 + '0'));
079 //
080 // Note that the fastest append to StringBuffer is a single char (used here).
081 // Note that Integer.toString() is not called, the conversion is simply
082 // taking the value and adding (mathematically) the ASCII value for '0'.
083 // So, don't change this code! It works and is very fast.
084
085 /**
086 * Required for serialization support.
087 *
088 * @see java.io.Serializable
089 */
090 private static final long serialVersionUID = 1L;
091
092 /**
093 * FULL locale dependent date or time style.
094 */
095 public static final int FULL = DateFormat.FULL;
096 /**
097 * LONG locale dependent date or time style.
098 */
099 public static final int LONG = DateFormat.LONG;
100 /**
101 * MEDIUM locale dependent date or time style.
102 */
103 public static final int MEDIUM = DateFormat.MEDIUM;
104 /**
105 * SHORT locale dependent date or time style.
106 */
107 public static final int SHORT = DateFormat.SHORT;
108
109 private static String cDefaultPattern; // lazily initialised by getInstance()
110
111 private static final Map cInstanceCache = new HashMap(7);
112 private static final Map cDateInstanceCache = new HashMap(7);
113 private static final Map cTimeInstanceCache = new HashMap(7);
114 private static final Map cDateTimeInstanceCache = new HashMap(7);
115 private static final Map cTimeZoneDisplayCache = new HashMap(7);
116
117 /**
118 * The pattern.
119 */
120 private final String mPattern;
121 /**
122 * The time zone.
123 */
124 private final TimeZone mTimeZone;
125 /**
126 * Whether the time zone overrides any on Calendars.
127 */
128 private final boolean mTimeZoneForced;
129 /**
130 * The locale.
131 */
132 private final Locale mLocale;
133 /**
134 * Whether the locale overrides the default.
135 */
136 private final boolean mLocaleForced;
137 /**
138 * The parsed rules.
139 */
140 private transient Rule[] mRules;
141 /**
142 * The estimated maximum length.
143 */
144 private transient int mMaxLengthEstimate;
145
146 //-----------------------------------------------------------------------
147 /**
148 * <p>Gets a formatter instance using the default pattern in the
149 * default locale.</p>
150 *
151 * @return a date/time formatter
152 */
153 public static FastDateFormat getInstance() {
154 return getInstance(getDefaultPattern(), null, null);
155 }
156
157 /**
158 * <p>Gets a formatter instance using the specified pattern in the
159 * default locale.</p>
160 *
161 * @param pattern {@link java.text.SimpleDateFormat} compatible
162 * pattern
163 * @return a pattern based date/time formatter
164 * @throws IllegalArgumentException if pattern is invalid
165 */
166 public static FastDateFormat getInstance(String pattern) {
167 return getInstance(pattern, null, null);
168 }
169
170 /**
171 * <p>Gets a formatter instance using the specified pattern and
172 * time zone.</p>
173 *
174 * @param pattern {@link java.text.SimpleDateFormat} compatible
175 * pattern
176 * @param timeZone optional time zone, overrides time zone of
177 * formatted date
178 * @return a pattern based date/time formatter
179 * @throws IllegalArgumentException if pattern is invalid
180 */
181 public static FastDateFormat getInstance(String pattern, TimeZone timeZone) {
182 return getInstance(pattern, timeZone, null);
183 }
184
185 /**
186 * <p>Gets a formatter instance using the specified pattern and
187 * locale.</p>
188 *
189 * @param pattern {@link java.text.SimpleDateFormat} compatible
190 * pattern
191 * @param locale optional locale, overrides system locale
192 * @return a pattern based date/time formatter
193 * @throws IllegalArgumentException if pattern is invalid
194 */
195 public static FastDateFormat getInstance(String pattern, Locale locale) {
196 return getInstance(pattern, null, locale);
197 }
198
199 /**
200 * <p>Gets a formatter instance using the specified pattern, time zone
201 * and locale.</p>
202 *
203 * @param pattern {@link java.text.SimpleDateFormat} compatible
204 * pattern
205 * @param timeZone optional time zone, overrides time zone of
206 * formatted date
207 * @param locale optional locale, overrides system locale
208 * @return a pattern based date/time formatter
209 * @throws IllegalArgumentException if pattern is invalid
210 * or <code>null</code>
211 */
212 public static synchronized FastDateFormat getInstance(String pattern, TimeZone timeZone, Locale locale) {
213 FastDateFormat emptyFormat = new FastDateFormat(pattern, timeZone, locale);
214 FastDateFormat format = (FastDateFormat) cInstanceCache.get(emptyFormat);
215 if (format == null) {
216 format = emptyFormat;
217 format.init(); // convert shell format into usable one
218 cInstanceCache.put(format, format); // this is OK!
219 }
220 return format;
221 }
222
223 //-----------------------------------------------------------------------
224 /**
225 * <p>Gets a date formatter instance using the specified style in the
226 * default time zone and locale.</p>
227 *
228 * @param style date style: FULL, LONG, MEDIUM, or SHORT
229 * @return a localized standard date formatter
230 * @throws IllegalArgumentException if the Locale has no date
231 * pattern defined
232 * @since 2.1
233 */
234 public static FastDateFormat getDateInstance(int style) {
235 return getDateInstance(style, null, null);
236 }
237
238 /**
239 * <p>Gets a date formatter instance using the specified style and
240 * locale in the default time zone.</p>
241 *
242 * @param style date style: FULL, LONG, MEDIUM, or SHORT
243 * @param locale optional locale, overrides system locale
244 * @return a localized standard date formatter
245 * @throws IllegalArgumentException if the Locale has no date
246 * pattern defined
247 * @since 2.1
248 */
249 public static FastDateFormat getDateInstance(int style, Locale locale) {
250 return getDateInstance(style, null, locale);
251 }
252
253 /**
254 * <p>Gets a date formatter instance using the specified style and
255 * time zone in the default locale.</p>
256 *
257 * @param style date style: FULL, LONG, MEDIUM, or SHORT
258 * @param timeZone optional time zone, overrides time zone of
259 * formatted date
260 * @return a localized standard date formatter
261 * @throws IllegalArgumentException if the Locale has no date
262 * pattern defined
263 * @since 2.1
264 */
265 public static FastDateFormat getDateInstance(int style, TimeZone timeZone) {
266 return getDateInstance(style, timeZone, null);
267 }
268 /**
269 * <p>Gets a date formatter instance using the specified style, time
270 * zone and locale.</p>
271 *
272 * @param style date style: FULL, LONG, MEDIUM, or SHORT
273 * @param timeZone optional time zone, overrides time zone of
274 * formatted date
275 * @param locale optional locale, overrides system locale
276 * @return a localized standard date formatter
277 * @throws IllegalArgumentException if the Locale has no date
278 * pattern defined
279 */
280 public static synchronized FastDateFormat getDateInstance(int style, TimeZone timeZone, Locale locale) {
281 Object key = new Integer(style);
282 if (timeZone != null) {
283 key = new Pair(key, timeZone);
284 }
285
286 if (locale == null) {
287 locale = Locale.getDefault();
288 }
289
290 key = new Pair(key, locale);
291
292 FastDateFormat format = (FastDateFormat) cDateInstanceCache.get(key);
293 if (format == null) {
294 try {
295 SimpleDateFormat formatter = (SimpleDateFormat) DateFormat.getDateInstance(style, locale);
296 String pattern = formatter.toPattern();
297 format = getInstance(pattern, timeZone, locale);
298 cDateInstanceCache.put(key, format);
299
300 } catch (ClassCastException ex) {
301 throw new IllegalArgumentException("No date pattern for locale: " + locale);
302 }
303 }
304 return format;
305 }
306
307 //-----------------------------------------------------------------------
308 /**
309 * <p>Gets a time formatter instance using the specified style in the
310 * default time zone and locale.</p>
311 *
312 * @param style time style: FULL, LONG, MEDIUM, or SHORT
313 * @return a localized standard time formatter
314 * @throws IllegalArgumentException if the Locale has no time
315 * pattern defined
316 * @since 2.1
317 */
318 public static FastDateFormat getTimeInstance(int style) {
319 return getTimeInstance(style, null, null);
320 }
321
322 /**
323 * <p>Gets a time formatter instance using the specified style and
324 * locale in the default time zone.</p>
325 *
326 * @param style time style: FULL, LONG, MEDIUM, or SHORT
327 * @param locale optional locale, overrides system locale
328 * @return a localized standard time formatter
329 * @throws IllegalArgumentException if the Locale has no time
330 * pattern defined
331 * @since 2.1
332 */
333 public static FastDateFormat getTimeInstance(int style, Locale locale) {
334 return getTimeInstance(style, null, locale);
335 }
336
337 /**
338 * <p>Gets a time formatter instance using the specified style and
339 * time zone in the default locale.</p>
340 *
341 * @param style time style: FULL, LONG, MEDIUM, or SHORT
342 * @param timeZone optional time zone, overrides time zone of
343 * formatted time
344 * @return a localized standard time formatter
345 * @throws IllegalArgumentException if the Locale has no time
346 * pattern defined
347 * @since 2.1
348 */
349 public static FastDateFormat getTimeInstance(int style, TimeZone timeZone) {
350 return getTimeInstance(style, timeZone, null);
351 }
352
353 /**
354 * <p>Gets a time formatter instance using the specified style, time
355 * zone and locale.</p>
356 *
357 * @param style time style: FULL, LONG, MEDIUM, or SHORT
358 * @param timeZone optional time zone, overrides time zone of
359 * formatted time
360 * @param locale optional locale, overrides system locale
361 * @return a localized standard time formatter
362 * @throws IllegalArgumentException if the Locale has no time
363 * pattern defined
364 */
365 public static synchronized FastDateFormat getTimeInstance(int style, TimeZone timeZone, Locale locale) {
366 Object key = new Integer(style);
367 if (timeZone != null) {
368 key = new Pair(key, timeZone);
369 }
370 if (locale != null) {
371 key = new Pair(key, locale);
372 }
373
374 FastDateFormat format = (FastDateFormat) cTimeInstanceCache.get(key);
375 if (format == null) {
376 if (locale == null) {
377 locale = Locale.getDefault();
378 }
379
380 try {
381 SimpleDateFormat formatter = (SimpleDateFormat) DateFormat.getTimeInstance(style, locale);
382 String pattern = formatter.toPattern();
383 format = getInstance(pattern, timeZone, locale);
384 cTimeInstanceCache.put(key, format);
385
386 } catch (ClassCastException ex) {
387 throw new IllegalArgumentException("No date pattern for locale: " + locale);
388 }
389 }
390 return format;
391 }
392
393 //-----------------------------------------------------------------------
394 /**
395 * <p>Gets a date/time formatter instance using the specified style
396 * in the default time zone and locale.</p>
397 *
398 * @param dateStyle date style: FULL, LONG, MEDIUM, or SHORT
399 * @param timeStyle time style: FULL, LONG, MEDIUM, or SHORT
400 * @return a localized standard date/time formatter
401 * @throws IllegalArgumentException if the Locale has no date/time
402 * pattern defined
403 * @since 2.1
404 */
405 public static FastDateFormat getDateTimeInstance(
406 int dateStyle, int timeStyle) {
407 return getDateTimeInstance(dateStyle, timeStyle, null, null);
408 }
409
410 /**
411 * <p>Gets a date/time formatter instance using the specified style and
412 * locale in the default time zone.</p>
413 *
414 * @param dateStyle date style: FULL, LONG, MEDIUM, or SHORT
415 * @param timeStyle time style: FULL, LONG, MEDIUM, or SHORT
416 * @param locale optional locale, overrides system locale
417 * @return a localized standard date/time formatter
418 * @throws IllegalArgumentException if the Locale has no date/time
419 * pattern defined
420 * @since 2.1
421 */
422 public static FastDateFormat getDateTimeInstance(
423 int dateStyle, int timeStyle, Locale locale) {
424 return getDateTimeInstance(dateStyle, timeStyle, null, locale);
425 }
426
427 /**
428 * <p>Gets a date/time formatter instance using the specified style and
429 * time zone in the default locale.</p>
430 *
431 * @param dateStyle date style: FULL, LONG, MEDIUM, or SHORT
432 * @param timeStyle time style: FULL, LONG, MEDIUM, or SHORT
433 * @param timeZone optional time zone, overrides time zone of
434 * formatted date
435 * @return a localized standard date/time formatter
436 * @throws IllegalArgumentException if the Locale has no date/time
437 * pattern defined
438 * @since 2.1
439 */
440 public static FastDateFormat getDateTimeInstance(
441 int dateStyle, int timeStyle, TimeZone timeZone) {
442 return getDateTimeInstance(dateStyle, timeStyle, timeZone, null);
443 }
444 /**
445 * <p>Gets a date/time formatter instance using the specified style,
446 * time zone and locale.</p>
447 *
448 * @param dateStyle date style: FULL, LONG, MEDIUM, or SHORT
449 * @param timeStyle time style: FULL, LONG, MEDIUM, or SHORT
450 * @param timeZone optional time zone, overrides time zone of
451 * formatted date
452 * @param locale optional locale, overrides system locale
453 * @return a localized standard date/time formatter
454 * @throws IllegalArgumentException if the Locale has no date/time
455 * pattern defined
456 */
457 public static synchronized FastDateFormat getDateTimeInstance(int dateStyle, int timeStyle, TimeZone timeZone,
458 Locale locale) {
459
460 Object key = new Pair(new Integer(dateStyle), new Integer(timeStyle));
461 if (timeZone != null) {
462 key = new Pair(key, timeZone);
463 }
464 if (locale == null) {
465 locale = Locale.getDefault();
466 }
467 key = new Pair(key, locale);
468
469 FastDateFormat format = (FastDateFormat) cDateTimeInstanceCache.get(key);
470 if (format == null) {
471 try {
472 SimpleDateFormat formatter = (SimpleDateFormat) DateFormat.getDateTimeInstance(dateStyle, timeStyle,
473 locale);
474 String pattern = formatter.toPattern();
475 format = getInstance(pattern, timeZone, locale);
476 cDateTimeInstanceCache.put(key, format);
477
478 } catch (ClassCastException ex) {
479 throw new IllegalArgumentException("No date time pattern for locale: " + locale);
480 }
481 }
482 return format;
483 }
484
485 //-----------------------------------------------------------------------
486 /**
487 * <p>Gets the time zone display name, using a cache for performance.</p>
488 *
489 * @param tz the zone to query
490 * @param daylight true if daylight savings
491 * @param style the style to use <code>TimeZone.LONG</code>
492 * or <code>TimeZone.SHORT</code>
493 * @param locale the locale to use
494 * @return the textual name of the time zone
495 */
496 static synchronized String getTimeZoneDisplay(TimeZone tz, boolean daylight, int style, Locale locale) {
497 Object key = new TimeZoneDisplayKey(tz, daylight, style, locale);
498 String value = (String) cTimeZoneDisplayCache.get(key);
499 if (value == null) {
500 // This is a very slow call, so cache the results.
501 value = tz.getDisplayName(daylight, style, locale);
502 cTimeZoneDisplayCache.put(key, value);
503 }
504 return value;
505 }
506
507 /**
508 * <p>Gets the default pattern.</p>
509 *
510 * @return the default pattern
511 */
512 private static synchronized String getDefaultPattern() {
513 if (cDefaultPattern == null) {
514 cDefaultPattern = new SimpleDateFormat().toPattern();
515 }
516 return cDefaultPattern;
517 }
518
519 // Constructor
520 //-----------------------------------------------------------------------
521 /**
522 * <p>Constructs a new FastDateFormat.</p>
523 *
524 * @param pattern {@link java.text.SimpleDateFormat} compatible
525 * pattern
526 * @param timeZone time zone to use, <code>null</code> means use
527 * default for <code>Date</code> and value within for
528 * <code>Calendar</code>
529 * @param locale locale, <code>null</code> means use system
530 * default
531 * @throws IllegalArgumentException if pattern is invalid or
532 * <code>null</code>
533 */
534 protected FastDateFormat(String pattern, TimeZone timeZone, Locale locale) {
535 super();
536 if (pattern == null) {
537 throw new IllegalArgumentException("The pattern must not be null");
538 }
539 mPattern = pattern;
540
541 mTimeZoneForced = (timeZone != null);
542 if (timeZone == null) {
543 timeZone = TimeZone.getDefault();
544 }
545 mTimeZone = timeZone;
546
547 mLocaleForced = (locale != null);
548 if (locale == null) {
549 locale = Locale.getDefault();
550 }
551 mLocale = locale;
552 }
553
554 /**
555 * <p>Initializes the instance for first use.</p>
556 */
557 protected void init() {
558 List rulesList = parsePattern();
559 mRules = (Rule[]) rulesList.toArray(new Rule[rulesList.size()]);
560
561 int len = 0;
562 for (int i=mRules.length; --i >= 0; ) {
563 len += mRules[i].estimateLength();
564 }
565
566 mMaxLengthEstimate = len;
567 }
568
569 // Parse the pattern
570 //-----------------------------------------------------------------------
571 /**
572 * <p>Returns a list of Rules given a pattern.</p>
573 *
574 * @return a <code>List</code> of Rule objects
575 * @throws IllegalArgumentException if pattern is invalid
576 */
577 protected List parsePattern() {
578 DateFormatSymbols symbols = new DateFormatSymbols(mLocale);
579 List rules = new ArrayList();
580
581 String[] ERAs = symbols.getEras();
582 String[] months = symbols.getMonths();
583 String[] shortMonths = symbols.getShortMonths();
584 String[] weekdays = symbols.getWeekdays();
585 String[] shortWeekdays = symbols.getShortWeekdays();
586 String[] AmPmStrings = symbols.getAmPmStrings();
587
588 int length = mPattern.length();
589 int[] indexRef = new int[1];
590
591 for (int i = 0; i < length; i++) {
592 indexRef[0] = i;
593 String token = parseToken(mPattern, indexRef);
594 i = indexRef[0];
595
596 int tokenLen = token.length();
597 if (tokenLen == 0) {
598 break;
599 }
600
601 Rule rule;
602 char c = token.charAt(0);
603
604 switch (c) {
605 case 'G': // era designator (text)
606 rule = new TextField(Calendar.ERA, ERAs);
607 break;
608 case 'y': // year (number)
609 if (tokenLen >= 4) {
610 rule = selectNumberRule(Calendar.YEAR, tokenLen);
611 } else {
612 rule = TwoDigitYearField.INSTANCE;
613 }
614 break;
615 case 'M': // month in year (text and number)
616 if (tokenLen >= 4) {
617 rule = new TextField(Calendar.MONTH, months);
618 } else if (tokenLen == 3) {
619 rule = new TextField(Calendar.MONTH, shortMonths);
620 } else if (tokenLen == 2) {
621 rule = TwoDigitMonthField.INSTANCE;
622 } else {
623 rule = UnpaddedMonthField.INSTANCE;
624 }
625 break;
626 case 'd': // day in month (number)
627 rule = selectNumberRule(Calendar.DAY_OF_MONTH, tokenLen);
628 break;
629 case 'h': // hour in am/pm (number, 1..12)
630 rule = new TwelveHourField(selectNumberRule(Calendar.HOUR, tokenLen));
631 break;
632 case 'H': // hour in day (number, 0..23)
633 rule = selectNumberRule(Calendar.HOUR_OF_DAY, tokenLen);
634 break;
635 case 'm': // minute in hour (number)
636 rule = selectNumberRule(Calendar.MINUTE, tokenLen);
637 break;
638 case 's': // second in minute (number)
639 rule = selectNumberRule(Calendar.SECOND, tokenLen);
640 break;
641 case 'S': // millisecond (number)
642 rule = selectNumberRule(Calendar.MILLISECOND, tokenLen);
643 break;
644 case 'E': // day in week (text)
645 rule = new TextField(Calendar.DAY_OF_WEEK, tokenLen < 4 ? shortWeekdays : weekdays);
646 break;
647 case 'D': // day in year (number)
648 rule = selectNumberRule(Calendar.DAY_OF_YEAR, tokenLen);
649 break;
650 case 'F': // day of week in month (number)
651 rule = selectNumberRule(Calendar.DAY_OF_WEEK_IN_MONTH, tokenLen);
652 break;
653 case 'w': // week in year (number)
654 rule = selectNumberRule(Calendar.WEEK_OF_YEAR, tokenLen);
655 break;
656 case 'W': // week in month (number)
657 rule = selectNumberRule(Calendar.WEEK_OF_MONTH, tokenLen);
658 break;
659 case 'a': // am/pm marker (text)
660 rule = new TextField(Calendar.AM_PM, AmPmStrings);
661 break;
662 case 'k': // hour in day (1..24)
663 rule = new TwentyFourHourField(selectNumberRule(Calendar.HOUR_OF_DAY, tokenLen));
664 break;
665 case 'K': // hour in am/pm (0..11)
666 rule = selectNumberRule(Calendar.HOUR, tokenLen);
667 break;
668 case 'z': // time zone (text)
669 if (tokenLen >= 4) {
670 rule = new TimeZoneNameRule(mTimeZone, mTimeZoneForced, mLocale, TimeZone.LONG);
671 } else {
672 rule = new TimeZoneNameRule(mTimeZone, mTimeZoneForced, mLocale, TimeZone.SHORT);
673 }
674 break;
675 case 'Z': // time zone (value)
676 if (tokenLen == 1) {
677 rule = TimeZoneNumberRule.INSTANCE_NO_COLON;
678 } else {
679 rule = TimeZoneNumberRule.INSTANCE_COLON;
680 }
681 break;
682 case '\'': // literal text
683 String sub = token.substring(1);
684 if (sub.length() == 1) {
685 rule = new CharacterLiteral(sub.charAt(0));
686 } else {
687 rule = new StringLiteral(sub);
688 }
689 break;
690 default:
691 throw new IllegalArgumentException("Illegal pattern component: " + token);
692 }
693
694 rules.add(rule);
695 }
696
697 return rules;
698 }
699
700 /**
701 * <p>Performs the parsing of tokens.</p>
702 *
703 * @param pattern the pattern
704 * @param indexRef index references
705 * @return parsed token
706 */
707 protected String parseToken(String pattern, int[] indexRef) {
708 StrBuilder buf = new StrBuilder();
709
710 int i = indexRef[0];
711 int length = pattern.length();
712
713 char c = pattern.charAt(i);
714 if (c >= 'A' && c <= 'Z' || c >= 'a' && c <= 'z') {
715 // Scan a run of the same character, which indicates a time
716 // pattern.
717 buf.append(c);
718
719 while (i + 1 < length) {
720 char peek = pattern.charAt(i + 1);
721 if (peek == c) {
722 buf.append(c);
723 i++;
724 } else {
725 break;
726 }
727 }
728 } else {
729 // This will identify token as text.
730 buf.append('\'');
731
732 boolean inLiteral = false;
733
734 for (; i < length; i++) {
735 c = pattern.charAt(i);
736
737 if (c == '\'') {
738 if (i + 1 < length && pattern.charAt(i + 1) == '\'') {
739 // '' is treated as escaped '
740 i++;
741 buf.append(c);
742 } else {
743 inLiteral = !inLiteral;
744 }
745 } else if (!inLiteral &&
746 (c >= 'A' && c <= 'Z' || c >= 'a' && c <= 'z')) {
747 i--;
748 break;
749 } else {
750 buf.append(c);
751 }
752 }
753 }
754
755 indexRef[0] = i;
756 return buf.toString();
757 }
758
759 /**
760 * <p>Gets an appropriate rule for the padding required.</p>
761 *
762 * @param field the field to get a rule for
763 * @param padding the padding required
764 * @return a new rule with the correct padding
765 */
766 protected NumberRule selectNumberRule(int field, int padding) {
767 switch (padding) {
768 case 1:
769 return new UnpaddedNumberField(field);
770 case 2:
771 return new TwoDigitNumberField(field);
772 default:
773 return new PaddedNumberField(field, padding);
774 }
775 }
776
777 // Format methods
778 //-----------------------------------------------------------------------
779 /**
780 * <p>Formats a <code>Date</code>, <code>Calendar</code> or
781 * <code>Long</code> (milliseconds) object.</p>
782 *
783 * @param obj the object to format
784 * @param toAppendTo the buffer to append to
785 * @param pos the position - ignored
786 * @return the buffer passed in
787 */
788 public StringBuffer format(Object obj, StringBuffer toAppendTo, FieldPosition pos) {
789 if (obj instanceof Date) {
790 return format((Date) obj, toAppendTo);
791 } else if (obj instanceof Calendar) {
792 return format((Calendar) obj, toAppendTo);
793 } else if (obj instanceof Long) {
794 return format(((Long) obj).longValue(), toAppendTo);
795 } else {
796 throw new IllegalArgumentException("Unknown class: " +
797 (obj == null ? "<null>" : obj.getClass().getName()));
798 }
799 }
800
801 /**
802 * <p>Formats a millisecond <code>long</code> value.</p>
803 *
804 * @param millis the millisecond value to format
805 * @return the formatted string
806 * @since 2.1
807 */
808 public String format(long millis) {
809 return format(new Date(millis));
810 }
811
812 /**
813 * <p>Formats a <code>Date</code> object.</p>
814 *
815 * @param date the date to format
816 * @return the formatted string
817 */
818 public String format(Date date) {
819 Calendar c = new GregorianCalendar(mTimeZone, mLocale);
820 c.setTime(date);
821 return applyRules(c, new StringBuffer(mMaxLengthEstimate)).toString();
822 }
823
824 /**
825 * <p>Formats a <code>Calendar</code> object.</p>
826 *
827 * @param calendar the calendar to format
828 * @return the formatted string
829 */
830 public String format(Calendar calendar) {
831 return format(calendar, new StringBuffer(mMaxLengthEstimate)).toString();
832 }
833
834 /**
835 * <p>Formats a milliseond <code>long</code> value into the
836 * supplied <code>StringBuffer</code>.</p>
837 *
838 * @param millis the millisecond value to format
839 * @param buf the buffer to format into
840 * @return the specified string buffer
841 * @since 2.1
842 */
843 public StringBuffer format(long millis, StringBuffer buf) {
844 return format(new Date(millis), buf);
845 }
846
847 /**
848 * <p>Formats a <code>Date</code> object into the
849 * supplied <code>StringBuffer</code>.</p>
850 *
851 * @param date the date to format
852 * @param buf the buffer to format into
853 * @return the specified string buffer
854 */
855 public StringBuffer format(Date date, StringBuffer buf) {
856 Calendar c = new GregorianCalendar(mTimeZone);
857 c.setTime(date);
858 return applyRules(c, buf);
859 }
860
861 /**
862 * <p>Formats a <code>Calendar</code> object into the
863 * supplied <code>StringBuffer</code>.</p>
864 *
865 * @param calendar the calendar to format
866 * @param buf the buffer to format into
867 * @return the specified string buffer
868 */
869 public StringBuffer format(Calendar calendar, StringBuffer buf) {
870 if (mTimeZoneForced) {
871 calendar.getTime(); /// LANG-538
872 calendar = (Calendar) calendar.clone();
873 calendar.setTimeZone(mTimeZone);
874 }
875 return applyRules(calendar, buf);
876 }
877
878 /**
879 * <p>Performs the formatting by applying the rules to the
880 * specified calendar.</p>
881 *
882 * @param calendar the calendar to format
883 * @param buf the buffer to format into
884 * @return the specified string buffer
885 */
886 protected StringBuffer applyRules(Calendar calendar, StringBuffer buf) {
887 Rule[] rules = mRules;
888 int len = mRules.length;
889 for (int i = 0; i < len; i++) {
890 rules[i].appendTo(buf, calendar);
891 }
892 return buf;
893 }
894
895 // Parsing
896 //-----------------------------------------------------------------------
897 /**
898 * <p>Parsing is not supported.</p>
899 *
900 * @param source the string to parse
901 * @param pos the parsing position
902 * @return <code>null</code> as not supported
903 */
904 public Object parseObject(String source, ParsePosition pos) {
905 pos.setIndex(0);
906 pos.setErrorIndex(0);
907 return null;
908 }
909
910 // Accessors
911 //-----------------------------------------------------------------------
912 /**
913 * <p>Gets the pattern used by this formatter.</p>
914 *
915 * @return the pattern, {@link java.text.SimpleDateFormat} compatible
916 */
917 public String getPattern() {
918 return mPattern;
919 }
920
921 /**
922 * <p>Gets the time zone used by this formatter.</p>
923 *
924 * <p>This zone is always used for <code>Date</code> formatting.
925 * If a <code>Calendar</code> is passed in to be formatted, the
926 * time zone on that may be used depending on
927 * {@link #getTimeZoneOverridesCalendar()}.</p>
928 *
929 * @return the time zone
930 */
931 public TimeZone getTimeZone() {
932 return mTimeZone;
933 }
934
935 /**
936 * <p>Returns <code>true</code> if the time zone of the
937 * calendar overrides the time zone of the formatter.</p>
938 *
939 * @return <code>true</code> if time zone of formatter
940 * overridden for calendars
941 */
942 public boolean getTimeZoneOverridesCalendar() {
943 return mTimeZoneForced;
944 }
945
946 /**
947 * <p>Gets the locale used by this formatter.</p>
948 *
949 * @return the locale
950 */
951 public Locale getLocale() {
952 return mLocale;
953 }
954
955 /**
956 * <p>Gets an estimate for the maximum string length that the
957 * formatter will produce.</p>
958 *
959 * <p>The actual formatted length will almost always be less than or
960 * equal to this amount.</p>
961 *
962 * @return the maximum formatted length
963 */
964 public int getMaxLengthEstimate() {
965 return mMaxLengthEstimate;
966 }
967
968 // Basics
969 //-----------------------------------------------------------------------
970 /**
971 * <p>Compares two objects for equality.</p>
972 *
973 * @param obj the object to compare to
974 * @return <code>true</code> if equal
975 */
976 public boolean equals(Object obj) {
977 if (obj instanceof FastDateFormat == false) {
978 return false;
979 }
980 FastDateFormat other = (FastDateFormat) obj;
981 if (
982 (mPattern == other.mPattern || mPattern.equals(other.mPattern)) &&
983 (mTimeZone == other.mTimeZone || mTimeZone.equals(other.mTimeZone)) &&
984 (mLocale == other.mLocale || mLocale.equals(other.mLocale)) &&
985 (mTimeZoneForced == other.mTimeZoneForced) &&
986 (mLocaleForced == other.mLocaleForced)
987 ) {
988 return true;
989 }
990 return false;
991 }
992
993 /**
994 * <p>Returns a hashcode compatible with equals.</p>
995 *
996 * @return a hashcode compatible with equals
997 */
998 public int hashCode() {
999 int total = 0;
1000 total += mPattern.hashCode();
1001 total += mTimeZone.hashCode();
1002 total += (mTimeZoneForced ? 1 : 0);
1003 total += mLocale.hashCode();
1004 total += (mLocaleForced ? 1 : 0);
1005 return total;
1006 }
1007
1008 /**
1009 * <p>Gets a debugging string version of this formatter.</p>
1010 *
1011 * @return a debugging string
1012 */
1013 public String toString() {
1014 return "FastDateFormat[" + mPattern + "]";
1015 }
1016
1017 // Serializing
1018 //-----------------------------------------------------------------------
1019 /**
1020 * Create the object after serialization. This implementation reinitializes the
1021 * transient properties.
1022 *
1023 * @param in ObjectInputStream from which the object is being deserialized.
1024 * @throws IOException if there is an IO issue.
1025 * @throws ClassNotFoundException if a class cannot be found.
1026 */
1027 private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
1028 in.defaultReadObject();
1029 init();
1030 }
1031
1032 // Rules
1033 //-----------------------------------------------------------------------
1034 /**
1035 * <p>Inner class defining a rule.</p>
1036 */
1037 private interface Rule {
1038 /**
1039 * Returns the estimated lentgh of the result.
1040 *
1041 * @return the estimated length
1042 */
1043 int estimateLength();
1044
1045 /**
1046 * Appends the value of the specified calendar to the output buffer based on the rule implementation.
1047 *
1048 * @param buffer the output buffer
1049 * @param calendar calendar to be appended
1050 */
1051 void appendTo(StringBuffer buffer, Calendar calendar);
1052 }
1053
1054 /**
1055 * <p>Inner class defining a numeric rule.</p>
1056 */
1057 private interface NumberRule extends Rule {
1058 /**
1059 * Appends the specified value to the output buffer based on the rule implementation.
1060 *
1061 * @param buffer the output buffer
1062 * @param value the value to be appended
1063 */
1064 void appendTo(StringBuffer buffer, int value);
1065 }
1066
1067 /**
1068 * <p>Inner class to output a constant single character.</p>
1069 */
1070 private static class CharacterLiteral implements Rule {
1071 private final char mValue;
1072
1073 /**
1074 * Constructs a new instance of <code>CharacterLiteral</code>
1075 * to hold the specified value.
1076 *
1077 * @param value the character literal
1078 */
1079 CharacterLiteral(char value) {
1080 mValue = value;
1081 }
1082
1083 /**
1084 * {@inheritDoc}
1085 */
1086 public int estimateLength() {
1087 return 1;
1088 }
1089
1090 /**
1091 * {@inheritDoc}
1092 */
1093 public void appendTo(StringBuffer buffer, Calendar calendar) {
1094 buffer.append(mValue);
1095 }
1096 }
1097
1098 /**
1099 * <p>Inner class to output a constant string.</p>
1100 */
1101 private static class StringLiteral implements Rule {
1102 private final String mValue;
1103
1104 /**
1105 * Constructs a new instance of <code>StringLiteral</code>
1106 * to hold the specified value.
1107 *
1108 * @param value the string literal
1109 */
1110 StringLiteral(String value) {
1111 mValue = value;
1112 }
1113
1114 /**
1115 * {@inheritDoc}
1116 */
1117 public int estimateLength() {
1118 return mValue.length();
1119 }
1120
1121 /**
1122 * {@inheritDoc}
1123 */
1124 public void appendTo(StringBuffer buffer, Calendar calendar) {
1125 buffer.append(mValue);
1126 }
1127 }
1128
1129 /**
1130 * <p>Inner class to output one of a set of values.</p>
1131 */
1132 private static class TextField implements Rule {
1133 private final int mField;
1134 private final String[] mValues;
1135
1136 /**
1137 * Constructs an instance of <code>TextField</code>
1138 * with the specified field and values.
1139 *
1140 * @param field the field
1141 * @param values the field values
1142 */
1143 TextField(int field, String[] values) {
1144 mField = field;
1145 mValues = values;
1146 }
1147
1148 /**
1149 * {@inheritDoc}
1150 */
1151 public int estimateLength() {
1152 int max = 0;
1153 for (int i=mValues.length; --i >= 0; ) {
1154 int len = mValues[i].length();
1155 if (len > max) {
1156 max = len;
1157 }
1158 }
1159 return max;
1160 }
1161
1162 /**
1163 * {@inheritDoc}
1164 */
1165 public void appendTo(StringBuffer buffer, Calendar calendar) {
1166 buffer.append(mValues[calendar.get(mField)]);
1167 }
1168 }
1169
1170 /**
1171 * <p>Inner class to output an unpadded number.</p>
1172 */
1173 private static class UnpaddedNumberField implements NumberRule {
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 }