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