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