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