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