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 Apache Software Foundation
063 * @author TeaTrove project
064 * @author Brian S O'Neill
065 * @author Sean Schofield
066 * @author Gary Gregory
067 * @author Nikolay Metchev
068 * @since 2.0
069 * @version $Id: FastDateFormat.java 905684 2010-02-02 16:03:07Z niallp $
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; // lazily initialised by getInstance()
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.getTime(); /// LANG-538
871 calendar = (Calendar) calendar.clone();
872 calendar.setTimeZone(mTimeZone);
873 }
874 return applyRules(calendar, buf);
875 }
876
877 /**
878 * <p>Performs the formatting by applying the rules to the
879 * specified calendar.</p>
880 *
881 * @param calendar the calendar to format
882 * @param buf the buffer to format into
883 * @return the specified string buffer
884 */
885 protected StringBuffer applyRules(Calendar calendar, StringBuffer buf) {
886 Rule[] rules = mRules;
887 int len = mRules.length;
888 for (int i = 0; i < len; i++) {
889 rules[i].appendTo(buf, calendar);
890 }
891 return buf;
892 }
893
894 // Parsing
895 //-----------------------------------------------------------------------
896 /**
897 * <p>Parsing is not supported.</p>
898 *
899 * @param source the string to parse
900 * @param pos the parsing position
901 * @return <code>null</code> as not supported
902 */
903 public Object parseObject(String source, ParsePosition pos) {
904 pos.setIndex(0);
905 pos.setErrorIndex(0);
906 return null;
907 }
908
909 // Accessors
910 //-----------------------------------------------------------------------
911 /**
912 * <p>Gets the pattern used by this formatter.</p>
913 *
914 * @return the pattern, {@link java.text.SimpleDateFormat} compatible
915 */
916 public String getPattern() {
917 return mPattern;
918 }
919
920 /**
921 * <p>Gets the time zone used by this formatter.</p>
922 *
923 * <p>This zone is always used for <code>Date</code> formatting.
924 * If a <code>Calendar</code> is passed in to be formatted, the
925 * time zone on that may be used depending on
926 * {@link #getTimeZoneOverridesCalendar()}.</p>
927 *
928 * @return the time zone
929 */
930 public TimeZone getTimeZone() {
931 return mTimeZone;
932 }
933
934 /**
935 * <p>Returns <code>true</code> if the time zone of the
936 * calendar overrides the time zone of the formatter.</p>
937 *
938 * @return <code>true</code> if time zone of formatter
939 * overridden for calendars
940 */
941 public boolean getTimeZoneOverridesCalendar() {
942 return mTimeZoneForced;
943 }
944
945 /**
946 * <p>Gets the locale used by this formatter.</p>
947 *
948 * @return the locale
949 */
950 public Locale getLocale() {
951 return mLocale;
952 }
953
954 /**
955 * <p>Gets an estimate for the maximum string length that the
956 * formatter will produce.</p>
957 *
958 * <p>The actual formatted length will almost always be less than or
959 * equal to this amount.</p>
960 *
961 * @return the maximum formatted length
962 */
963 public int getMaxLengthEstimate() {
964 return mMaxLengthEstimate;
965 }
966
967 // Basics
968 //-----------------------------------------------------------------------
969 /**
970 * <p>Compares two objects for equality.</p>
971 *
972 * @param obj the object to compare to
973 * @return <code>true</code> if equal
974 */
975 public boolean equals(Object obj) {
976 if (obj instanceof FastDateFormat == false) {
977 return false;
978 }
979 FastDateFormat other = (FastDateFormat) obj;
980 if (
981 (mPattern == other.mPattern || mPattern.equals(other.mPattern)) &&
982 (mTimeZone == other.mTimeZone || mTimeZone.equals(other.mTimeZone)) &&
983 (mLocale == other.mLocale || mLocale.equals(other.mLocale)) &&
984 (mTimeZoneForced == other.mTimeZoneForced) &&
985 (mLocaleForced == other.mLocaleForced)
986 ) {
987 return true;
988 }
989 return false;
990 }
991
992 /**
993 * <p>Returns a hashcode compatible with equals.</p>
994 *
995 * @return a hashcode compatible with equals
996 */
997 public int hashCode() {
998 int total = 0;
999 total += mPattern.hashCode();
1000 total += mTimeZone.hashCode();
1001 total += (mTimeZoneForced ? 1 : 0);
1002 total += mLocale.hashCode();
1003 total += (mLocaleForced ? 1 : 0);
1004 return total;
1005 }
1006
1007 /**
1008 * <p>Gets a debugging string version of this formatter.</p>
1009 *
1010 * @return a debugging string
1011 */
1012 public String toString() {
1013 return "FastDateFormat[" + mPattern + "]";
1014 }
1015
1016 // Serializing
1017 //-----------------------------------------------------------------------
1018 /**
1019 * Create the object after serialization. This implementation reinitializes the
1020 * transient properties.
1021 *
1022 * @param in ObjectInputStream from which the object is being deserialized.
1023 * @throws IOException if there is an IO issue.
1024 * @throws ClassNotFoundException if a class cannot be found.
1025 */
1026 private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
1027 in.defaultReadObject();
1028 init();
1029 }
1030
1031 // Rules
1032 //-----------------------------------------------------------------------
1033 /**
1034 * <p>Inner class defining a rule.</p>
1035 */
1036 private interface Rule {
1037 /**
1038 * Returns the estimated lentgh of the result.
1039 *
1040 * @return the estimated length
1041 */
1042 int estimateLength();
1043
1044 /**
1045 * Appends the value of the specified calendar to the output buffer based on the rule implementation.
1046 *
1047 * @param buffer the output buffer
1048 * @param calendar calendar to be appended
1049 */
1050 void appendTo(StringBuffer buffer, Calendar calendar);
1051 }
1052
1053 /**
1054 * <p>Inner class defining a numeric rule.</p>
1055 */
1056 private interface NumberRule extends Rule {
1057 /**
1058 * Appends the specified value to the output buffer based on the rule implementation.
1059 *
1060 * @param buffer the output buffer
1061 * @param value the value to be appended
1062 */
1063 void appendTo(StringBuffer buffer, int value);
1064 }
1065
1066 /**
1067 * <p>Inner class to output a constant single character.</p>
1068 */
1069 private static class CharacterLiteral implements Rule {
1070 private final char mValue;
1071
1072 /**
1073 * Constructs a new instance of <code>CharacterLiteral</code>
1074 * to hold the specified value.
1075 *
1076 * @param value the character literal
1077 */
1078 CharacterLiteral(char value) {
1079 mValue = value;
1080 }
1081
1082 /**
1083 * {@inheritDoc}
1084 */
1085 public int estimateLength() {
1086 return 1;
1087 }
1088
1089 /**
1090 * {@inheritDoc}
1091 */
1092 public void appendTo(StringBuffer buffer, Calendar calendar) {
1093 buffer.append(mValue);
1094 }
1095 }
1096
1097 /**
1098 * <p>Inner class to output a constant string.</p>
1099 */
1100 private static class StringLiteral implements Rule {
1101 private final String mValue;
1102
1103 /**
1104 * Constructs a new instance of <code>StringLiteral</code>
1105 * to hold the specified value.
1106 *
1107 * @param value the string literal
1108 */
1109 StringLiteral(String value) {
1110 mValue = value;
1111 }
1112
1113 /**
1114 * {@inheritDoc}
1115 */
1116 public int estimateLength() {
1117 return mValue.length();
1118 }
1119
1120 /**
1121 * {@inheritDoc}
1122 */
1123 public void appendTo(StringBuffer buffer, Calendar calendar) {
1124 buffer.append(mValue);
1125 }
1126 }
1127
1128 /**
1129 * <p>Inner class to output one of a set of values.</p>
1130 */
1131 private static class TextField implements Rule {
1132 private final int mField;
1133 private final String[] mValues;
1134
1135 /**
1136 * Constructs an instance of <code>TextField</code>
1137 * with the specified field and values.
1138 *
1139 * @param field the field
1140 * @param values the field values
1141 */
1142 TextField(int field, String[] values) {
1143 mField = field;
1144 mValues = values;
1145 }
1146
1147 /**
1148 * {@inheritDoc}
1149 */
1150 public int estimateLength() {
1151 int max = 0;
1152 for (int i=mValues.length; --i >= 0; ) {
1153 int len = mValues[i].length();
1154 if (len > max) {
1155 max = len;
1156 }
1157 }
1158 return max;
1159 }
1160
1161 /**
1162 * {@inheritDoc}
1163 */
1164 public void appendTo(StringBuffer buffer, Calendar calendar) {
1165 buffer.append(mValues[calendar.get(mField)]);
1166 }
1167 }
1168
1169 /**
1170 * <p>Inner class to output an unpadded number.</p>
1171 */
1172 private static class UnpaddedNumberField implements NumberRule {
1173 private final int mField;
1174
1175 /**
1176 * Constructs an instance of <code>UnpadedNumberField</code> with the specified field.
1177 *
1178 * @param field the field
1179 */
1180 UnpaddedNumberField(int field) {
1181 mField = field;
1182 }
1183
1184 /**
1185 * {@inheritDoc}
1186 */
1187 public int estimateLength() {
1188 return 4;
1189 }
1190
1191 /**
1192 * {@inheritDoc}
1193 */
1194 public void appendTo(StringBuffer buffer, Calendar calendar) {
1195 appendTo(buffer, calendar.get(mField));
1196 }
1197
1198 /**
1199 * {@inheritDoc}
1200 */
1201 public final void appendTo(StringBuffer buffer, int value) {
1202 if (value < 10) {
1203 buffer.append((char)(value + '0'));
1204 } else if (value < 100) {
1205 buffer.append((char)(value / 10 + '0'));
1206 buffer.append((char)(value % 10 + '0'));
1207 } else {
1208 buffer.append(Integer.toString(value));
1209 }
1210 }
1211 }
1212
1213 /**
1214 * <p>Inner class to output an unpadded month.</p>
1215 */
1216 private static class UnpaddedMonthField implements NumberRule {
1217 static final UnpaddedMonthField INSTANCE = new UnpaddedMonthField();
1218
1219 /**
1220 * Constructs an instance of <code>UnpaddedMonthField</code>.
1221 *
1222 */
1223 UnpaddedMonthField() {
1224 super();
1225 }
1226
1227 /**
1228 * {@inheritDoc}
1229 */
1230 public int estimateLength() {
1231 return 2;
1232 }
1233
1234 /**
1235 * {@inheritDoc}
1236 */
1237 public void appendTo(StringBuffer buffer, Calendar calendar) {
1238 appendTo(buffer, calendar.get(Calendar.MONTH) + 1);
1239 }
1240
1241 /**
1242 * {@inheritDoc}
1243 */
1244 public final void appendTo(StringBuffer buffer, int value) {
1245 if (value < 10) {
1246 buffer.append((char)(value + '0'));
1247 } else {
1248 buffer.append((char)(value / 10 + '0'));
1249 buffer.append((char)(value % 10 + '0'));
1250 }
1251 }
1252 }
1253
1254 /**
1255 * <p>Inner class to output a padded number.</p>
1256 */
1257 private static class PaddedNumberField implements NumberRule {
1258 private final int mField;
1259 private final int mSize;
1260
1261 /**
1262 * Constructs an instance of <code>PaddedNumberField</code>.
1263 *
1264 * @param field the field
1265 * @param size size of the output field
1266 */
1267 PaddedNumberField(int field, int size) {
1268 if (size < 3) {
1269 // Should use UnpaddedNumberField or TwoDigitNumberField.
1270 throw new IllegalArgumentException();
1271 }
1272 mField = field;
1273 mSize = size;
1274 }
1275
1276 /**
1277 * {@inheritDoc}
1278 */
1279 public int estimateLength() {
1280 return 4;
1281 }
1282
1283 /**
1284 * {@inheritDoc}
1285 */
1286 public void appendTo(StringBuffer buffer, Calendar calendar) {
1287 appendTo(buffer, calendar.get(mField));
1288 }
1289
1290 /**
1291 * {@inheritDoc}
1292 */
1293 public final void appendTo(StringBuffer buffer, int value) {
1294 if (value < 100) {
1295 for (int i = mSize; --i >= 2; ) {
1296 buffer.append('0');
1297 }
1298 buffer.append((char)(value / 10 + '0'));
1299 buffer.append((char)(value % 10 + '0'));
1300 } else {
1301 int digits;
1302 if (value < 1000) {
1303 digits = 3;
1304 } else {
1305 Validate.isTrue(value > -1, "Negative values should not be possible", value);
1306 digits = Integer.toString(value).length();
1307 }
1308 for (int i = mSize; --i >= digits; ) {
1309 buffer.append('0');
1310 }
1311 buffer.append(Integer.toString(value));
1312 }
1313 }
1314 }
1315
1316 /**
1317 * <p>Inner class to output a two digit number.</p>
1318 */
1319 private static class TwoDigitNumberField implements NumberRule {
1320 private final int mField;
1321
1322 /**
1323 * Constructs an instance of <code>TwoDigitNumberField</code> with the specified field.
1324 *
1325 * @param field the field
1326 */
1327 TwoDigitNumberField(int field) {
1328 mField = field;
1329 }
1330
1331 /**
1332 * {@inheritDoc}
1333 */
1334 public int estimateLength() {
1335 return 2;
1336 }
1337
1338 /**
1339 * {@inheritDoc}
1340 */
1341 public void appendTo(StringBuffer buffer, Calendar calendar) {
1342 appendTo(buffer, calendar.get(mField));
1343 }
1344
1345 /**
1346 * {@inheritDoc}
1347 */
1348 public final void appendTo(StringBuffer buffer, int value) {
1349 if (value < 100) {
1350 buffer.append((char)(value / 10 + '0'));
1351 buffer.append((char)(value % 10 + '0'));
1352 } else {
1353 buffer.append(Integer.toString(value));
1354 }
1355 }
1356 }
1357
1358 /**
1359 * <p>Inner class to output a two digit year.</p>
1360 */
1361 private static class TwoDigitYearField implements NumberRule {
1362 static final TwoDigitYearField INSTANCE = new TwoDigitYearField();
1363
1364 /**
1365 * Constructs an instance of <code>TwoDigitYearField</code>.
1366 */
1367 TwoDigitYearField() {
1368 super();
1369 }
1370
1371 /**
1372 * {@inheritDoc}
1373 */
1374 public int estimateLength() {
1375 return 2;
1376 }
1377
1378 /**
1379 * {@inheritDoc}
1380 */
1381 public void appendTo(StringBuffer buffer, Calendar calendar) {
1382 appendTo(buffer, calendar.get(Calendar.YEAR) % 100);
1383 }
1384
1385 /**
1386 * {@inheritDoc}
1387 */
1388 public final void appendTo(StringBuffer buffer, int value) {
1389 buffer.append((char)(value / 10 + '0'));
1390 buffer.append((char)(value % 10 + '0'));
1391 }
1392 }
1393
1394 /**
1395 * <p>Inner class to output a two digit month.</p>
1396 */
1397 private static class TwoDigitMonthField implements NumberRule {
1398 static final TwoDigitMonthField INSTANCE = new TwoDigitMonthField();
1399
1400 /**
1401 * Constructs an instance of <code>TwoDigitMonthField</code>.
1402 */
1403 TwoDigitMonthField() {
1404 super();
1405 }
1406
1407 /**
1408 * {@inheritDoc}
1409 */
1410 public int estimateLength() {
1411 return 2;
1412 }
1413
1414 /**
1415 * {@inheritDoc}
1416 */
1417 public void appendTo(StringBuffer buffer, Calendar calendar) {
1418 appendTo(buffer, calendar.get(Calendar.MONTH) + 1);
1419 }
1420
1421 /**
1422 * {@inheritDoc}
1423 */
1424 public final void appendTo(StringBuffer buffer, int value) {
1425 buffer.append((char)(value / 10 + '0'));
1426 buffer.append((char)(value % 10 + '0'));
1427 }
1428 }
1429
1430 /**
1431 * <p>Inner class to output the twelve hour field.</p>
1432 */
1433 private static class TwelveHourField implements NumberRule {
1434 private final NumberRule mRule;
1435
1436 /**
1437 * Constructs an instance of <code>TwelveHourField</code> with the specified
1438 * <code>NumberRule</code>.
1439 *
1440 * @param rule the rule
1441 */
1442 TwelveHourField(NumberRule rule) {
1443 mRule = rule;
1444 }
1445
1446 /**
1447 * {@inheritDoc}
1448 */
1449 public int estimateLength() {
1450 return mRule.estimateLength();
1451 }
1452
1453 /**
1454 * {@inheritDoc}
1455 */
1456 public void appendTo(StringBuffer buffer, Calendar calendar) {
1457 int value = calendar.get(Calendar.HOUR);
1458 if (value == 0) {
1459 value = calendar.getLeastMaximum(Calendar.HOUR) + 1;
1460 }
1461 mRule.appendTo(buffer, value);
1462 }
1463
1464 /**
1465 * {@inheritDoc}
1466 */
1467 public void appendTo(StringBuffer buffer, int value) {
1468 mRule.appendTo(buffer, value);
1469 }
1470 }
1471
1472 /**
1473 * <p>Inner class to output the twenty four hour field.</p>
1474 */
1475 private static class TwentyFourHourField implements NumberRule {
1476 private final NumberRule mRule;
1477
1478 /**
1479 * Constructs an instance of <code>TwentyFourHourField</code> with the specified
1480 * <code>NumberRule</code>.
1481 *
1482 * @param rule the rule
1483 */
1484 TwentyFourHourField(NumberRule rule) {
1485 mRule = rule;
1486 }
1487
1488 /**
1489 * {@inheritDoc}
1490 */
1491 public int estimateLength() {
1492 return mRule.estimateLength();
1493 }
1494
1495 /**
1496 * {@inheritDoc}
1497 */
1498 public void appendTo(StringBuffer buffer, Calendar calendar) {
1499 int value = calendar.get(Calendar.HOUR_OF_DAY);
1500 if (value == 0) {
1501 value = calendar.getMaximum(Calendar.HOUR_OF_DAY) + 1;
1502 }
1503 mRule.appendTo(buffer, value);
1504 }
1505
1506 /**
1507 * {@inheritDoc}
1508 */
1509 public void appendTo(StringBuffer buffer, int value) {
1510 mRule.appendTo(buffer, value);
1511 }
1512 }
1513
1514 /**
1515 * <p>Inner class to output a time zone name.</p>
1516 */
1517 private static class TimeZoneNameRule implements Rule {
1518 private final TimeZone mTimeZone;
1519 private final boolean mTimeZoneForced;
1520 private final Locale mLocale;
1521 private final int mStyle;
1522 private final String mStandard;
1523 private final String mDaylight;
1524
1525 /**
1526 * Constructs an instance of <code>TimeZoneNameRule</code> with the specified properties.
1527 *
1528 * @param timeZone the time zone
1529 * @param timeZoneForced if <code>true</code> the time zone is forced into standard and daylight
1530 * @param locale the locale
1531 * @param style the style
1532 */
1533 TimeZoneNameRule(TimeZone timeZone, boolean timeZoneForced, Locale locale, int style) {
1534 mTimeZone = timeZone;
1535 mTimeZoneForced = timeZoneForced;
1536 mLocale = locale;
1537 mStyle = style;
1538
1539 if (timeZoneForced) {
1540 mStandard = getTimeZoneDisplay(timeZone, false, style, locale);
1541 mDaylight = getTimeZoneDisplay(timeZone, true, style, locale);
1542 } else {
1543 mStandard = null;
1544 mDaylight = null;
1545 }
1546 }
1547
1548 /**
1549 * {@inheritDoc}
1550 */
1551 public int estimateLength() {
1552 if (mTimeZoneForced) {
1553 return Math.max(mStandard.length(), mDaylight.length());
1554 } else if (mStyle == TimeZone.SHORT) {
1555 return 4;
1556 } else {
1557 return 40;
1558 }
1559 }
1560
1561 /**
1562 * {@inheritDoc}
1563 */
1564 public void appendTo(StringBuffer buffer, Calendar calendar) {
1565 if (mTimeZoneForced) {
1566 if (mTimeZone.useDaylightTime() && calendar.get(Calendar.DST_OFFSET) != 0) {
1567 buffer.append(mDaylight);
1568 } else {
1569 buffer.append(mStandard);
1570 }
1571 } else {
1572 TimeZone timeZone = calendar.getTimeZone();
1573 if (timeZone.useDaylightTime() && calendar.get(Calendar.DST_OFFSET) != 0) {
1574 buffer.append(getTimeZoneDisplay(timeZone, true, mStyle, mLocale));
1575 } else {
1576 buffer.append(getTimeZoneDisplay(timeZone, false, mStyle, mLocale));
1577 }
1578 }
1579 }
1580 }
1581
1582 /**
1583 * <p>Inner class to output a time zone as a number <code>+/-HHMM</code>
1584 * or <code>+/-HH:MM</code>.</p>
1585 */
1586 private static class TimeZoneNumberRule implements Rule {
1587 static final TimeZoneNumberRule INSTANCE_COLON = new TimeZoneNumberRule(true);
1588 static final TimeZoneNumberRule INSTANCE_NO_COLON = new TimeZoneNumberRule(false);
1589
1590 final boolean mColon;
1591
1592 /**
1593 * Constructs an instance of <code>TimeZoneNumberRule</code> with the specified properties.
1594 *
1595 * @param colon add colon between HH and MM in the output if <code>true</code>
1596 */
1597 TimeZoneNumberRule(boolean colon) {
1598 mColon = colon;
1599 }
1600
1601 /**
1602 * {@inheritDoc}
1603 */
1604 public int estimateLength() {
1605 return 5;
1606 }
1607
1608 /**
1609 * {@inheritDoc}
1610 */
1611 public void appendTo(StringBuffer buffer, Calendar calendar) {
1612 int offset = calendar.get(Calendar.ZONE_OFFSET) + calendar.get(Calendar.DST_OFFSET);
1613
1614 if (offset < 0) {
1615 buffer.append('-');
1616 offset = -offset;
1617 } else {
1618 buffer.append('+');
1619 }
1620
1621 int hours = offset / (60 * 60 * 1000);
1622 buffer.append((char)(hours / 10 + '0'));
1623 buffer.append((char)(hours % 10 + '0'));
1624
1625 if (mColon) {
1626 buffer.append(':');
1627 }
1628
1629 int minutes = offset / (60 * 1000) - 60 * hours;
1630 buffer.append((char)(minutes / 10 + '0'));
1631 buffer.append((char)(minutes % 10 + '0'));
1632 }
1633 }
1634
1635 // ----------------------------------------------------------------------
1636 /**
1637 * <p>Inner class that acts as a compound key for time zone names.</p>
1638 */
1639 private static class TimeZoneDisplayKey {
1640 private final TimeZone mTimeZone;
1641 private final int mStyle;
1642 private final Locale mLocale;
1643
1644 /**
1645 * Constructs an instance of <code>TimeZoneDisplayKey</code> with the specified properties.
1646 *
1647 * @param timeZone the time zone
1648 * @param daylight adjust the style for daylight saving time if <code>true</code>
1649 * @param style the timezone style
1650 * @param locale the timezone locale
1651 */
1652 TimeZoneDisplayKey(TimeZone timeZone,
1653 boolean daylight, int style, Locale locale) {
1654 mTimeZone = timeZone;
1655 if (daylight) {
1656 style |= 0x80000000;
1657 }
1658 mStyle = style;
1659 mLocale = locale;
1660 }
1661
1662 /**
1663 * {@inheritDoc}
1664 */
1665 public int hashCode() {
1666 return mStyle * 31 + mLocale.hashCode();
1667 }
1668
1669 /**
1670 * {@inheritDoc}
1671 */
1672 public boolean equals(Object obj) {
1673 if (this == obj) {
1674 return true;
1675 }
1676 if (obj instanceof TimeZoneDisplayKey) {
1677 TimeZoneDisplayKey other = (TimeZoneDisplayKey)obj;
1678 return
1679 mTimeZone.equals(other.mTimeZone) &&
1680 mStyle == other.mStyle &&
1681 mLocale.equals(other.mLocale);
1682 }
1683 return false;
1684 }
1685 }
1686
1687 // ----------------------------------------------------------------------
1688 /**
1689 * <p>Helper class for creating compound objects.</p>
1690 *
1691 * <p>One use for this class is to create a hashtable key
1692 * out of multiple objects.</p>
1693 */
1694 private static class Pair {
1695 private final Object mObj1;
1696 private final Object mObj2;
1697
1698 /**
1699 * Constructs an instance of <code>Pair</code> to hold the specified objects.
1700 * @param obj1 one object in the pair
1701 * @param obj2 second object in the pair
1702 */
1703 public Pair(Object obj1, Object obj2) {
1704 mObj1 = obj1;
1705 mObj2 = obj2;
1706 }
1707
1708 /**
1709 * {@inheritDoc}
1710 */
1711 public boolean equals(Object obj) {
1712 if (this == obj) {
1713 return true;
1714 }
1715
1716 if (!(obj instanceof Pair)) {
1717 return false;
1718 }
1719
1720 Pair key = (Pair)obj;
1721
1722 return
1723 (mObj1 == null ?
1724 key.mObj1 == null : mObj1.equals(key.mObj1)) &&
1725 (mObj2 == null ?
1726 key.mObj2 == null : mObj2.equals(key.mObj2));
1727 }
1728
1729 /**
1730 * {@inheritDoc}
1731 */
1732 public int hashCode() {
1733 return
1734 (mObj1 == null ? 0 : mObj1.hashCode()) +
1735 (mObj2 == null ? 0 : mObj2.hashCode());
1736 }
1737
1738 /**
1739 * {@inheritDoc}
1740 */
1741 public String toString() {
1742 return "[" + mObj1 + ':' + mObj2 + ']';
1743 }
1744 }
1745
1746 }