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