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