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