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