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