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