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