FastDatePrinter.java

  1. /*
  2.  * Licensed to the Apache Software Foundation (ASF) under one or more
  3.  * contributor license agreements.  See the NOTICE file distributed with
  4.  * this work for additional information regarding copyright ownership.
  5.  * The ASF licenses this file to You under the Apache License, Version 2.0
  6.  * (the "License"); you may not use this file except in compliance with
  7.  * the License.  You may obtain a copy of the License at
  8.  *
  9.  *      https://www.apache.org/licenses/LICENSE-2.0
  10.  *
  11.  * Unless required by applicable law or agreed to in writing, software
  12.  * distributed under the License is distributed on an "AS IS" BASIS,
  13.  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14.  * See the License for the specific language governing permissions and
  15.  * limitations under the License.
  16.  */
  17. package org.apache.commons.lang3.time;

  18. import java.io.IOException;
  19. import java.io.ObjectInputStream;
  20. import java.io.Serializable;
  21. import java.text.DateFormat;
  22. import java.text.DateFormatSymbols;
  23. import java.text.FieldPosition;
  24. import java.text.SimpleDateFormat;
  25. import java.util.ArrayList;
  26. import java.util.Calendar;
  27. import java.util.Date;
  28. import java.util.List;
  29. import java.util.Locale;
  30. import java.util.TimeZone;
  31. import java.util.concurrent.ConcurrentHashMap;
  32. import java.util.concurrent.ConcurrentMap;

  33. import org.apache.commons.lang3.CharUtils;
  34. import org.apache.commons.lang3.ClassUtils;
  35. import org.apache.commons.lang3.LocaleUtils;
  36. import org.apache.commons.lang3.exception.ExceptionUtils;

  37. /**
  38.  * FastDatePrinter is a fast and thread-safe version of
  39.  * {@link java.text.SimpleDateFormat}.
  40.  *
  41.  * <p>To obtain a FastDatePrinter, use {@link FastDateFormat#getInstance(String, TimeZone, Locale)}
  42.  * or another variation of the factory methods of {@link FastDateFormat}.</p>
  43.  *
  44.  * <p>Since FastDatePrinter is thread safe, you can use a static member instance:</p>
  45.  * {@code
  46.  *     private static final DatePrinter DATE_PRINTER = FastDateFormat.getInstance("yyyy-MM-dd");
  47.  * }
  48.  *
  49.  * <p>This class can be used as a direct replacement to
  50.  * {@link SimpleDateFormat} in most formatting situations.
  51.  * This class is especially useful in multi-threaded server environments.
  52.  * {@link SimpleDateFormat} is not thread-safe in any JDK version,
  53.  * nor will it be as Sun have closed the bug/RFE.
  54.  * </p>
  55.  *
  56.  * <p>Only formatting is supported by this class, but all patterns are compatible with
  57.  * SimpleDateFormat (except time zones and some year patterns - see below).</p>
  58.  *
  59.  * <p>Java 1.4 introduced a new pattern letter, {@code 'Z'}, to represent
  60.  * time zones in RFC822 format (eg. {@code +0800} or {@code -1100}).
  61.  * This pattern letter can be used here (on all JDK versions).</p>
  62.  *
  63.  * <p>In addition, the pattern {@code 'ZZ'} has been made to represent
  64.  * ISO 8601 extended format time zones (eg. {@code +08:00} or {@code -11:00}).
  65.  * This introduces a minor incompatibility with Java 1.4, but at a gain of
  66.  * useful functionality.</p>
  67.  *
  68.  * <p>Starting with JDK7, ISO 8601 support was added using the pattern {@code 'X'}.
  69.  * To maintain compatibility, {@code 'ZZ'} will continue to be supported, but using
  70.  * one of the {@code 'X'} formats is recommended.
  71.  *
  72.  * <p>Javadoc cites for the year pattern: <i>For formatting, if the number of
  73.  * pattern letters is 2, the year is truncated to 2 digits; otherwise it is
  74.  * interpreted as a number.</i> Starting with Java 1.7 a pattern of 'Y' or
  75.  * 'YYY' will be formatted as '2003', while it was '03' in former Java
  76.  * versions. FastDatePrinter implements the behavior of Java 7.</p>
  77.  *
  78.  * @since 3.2
  79.  * @see FastDateParser
  80.  */
  81. public class FastDatePrinter implements DatePrinter, Serializable {
  82.     // A lot of the speed in this class comes from caching, but some comes
  83.     // from the special int to StringBuffer conversion.
  84.     //
  85.     // The following produces a padded 2-digit number:
  86.     //   buffer.append((char)(value / 10 + '0'));
  87.     //   buffer.append((char)(value % 10 + '0'));
  88.     //
  89.     // Note that the fastest append to StringBuffer is a single char (used here).
  90.     // Note that Integer.toString() is not called, the conversion is simply
  91.     // taking the value and adding (mathematically) the ASCII value for '0'.
  92.     // So, don't change this code! It works and is very fast.

  93.     /**
  94.      * Inner class to output a constant single character.
  95.      */
  96.     private static final class CharacterLiteral implements Rule {
  97.         private final char value;

  98.         /**
  99.          * Constructs a new instance of {@link CharacterLiteral}
  100.          * to hold the specified value.
  101.          *
  102.          * @param value the character literal
  103.          */
  104.         CharacterLiteral(final char value) {
  105.             this.value = value;
  106.         }

  107.         /**
  108.          * {@inheritDoc}
  109.          */
  110.         @Override
  111.         public void appendTo(final Appendable buffer, final Calendar calendar) throws IOException {
  112.             buffer.append(value);
  113.         }

  114.         /**
  115.          * {@inheritDoc}
  116.          */
  117.         @Override
  118.         public int estimateLength() {
  119.             return 1;
  120.         }
  121.     }

  122.     /**
  123.      * Inner class to output the numeric day in week.
  124.      */
  125.     private static final class DayInWeekField implements NumberRule {
  126.         private final NumberRule rule;

  127.         DayInWeekField(final NumberRule rule) {
  128.             this.rule = rule;
  129.         }

  130.         @Override
  131.         public void appendTo(final Appendable buffer, final Calendar calendar) throws IOException {
  132.             final int value = calendar.get(Calendar.DAY_OF_WEEK);
  133.             rule.appendTo(buffer, value == Calendar.SUNDAY ? 7 : value - 1);
  134.         }

  135.         @Override
  136.         public void appendTo(final Appendable buffer, final int value) throws IOException {
  137.             rule.appendTo(buffer, value);
  138.         }

  139.         @Override
  140.         public int estimateLength() {
  141.             return rule.estimateLength();
  142.         }
  143.     }

  144.     /**
  145.      * Inner class to output a time zone as a number {@code +/-HHMM}
  146.      * or {@code +/-HH:MM}.
  147.      */
  148.     private static final class Iso8601_Rule implements Rule {

  149.         // Sign TwoDigitHours or Z
  150.         static final Iso8601_Rule ISO8601_HOURS = new Iso8601_Rule(3);
  151.         // Sign TwoDigitHours Minutes or Z
  152.         static final Iso8601_Rule ISO8601_HOURS_MINUTES = new Iso8601_Rule(5);
  153.         // Sign TwoDigitHours : Minutes or Z
  154.         static final Iso8601_Rule ISO8601_HOURS_COLON_MINUTES = new Iso8601_Rule(6);

  155.         /**
  156.          * Factory method for Iso8601_Rules.
  157.          *
  158.          * @param tokenLen a token indicating the length of the TimeZone String to be formatted.
  159.          * @return an Iso8601_Rule that can format TimeZone String of length {@code tokenLen}. If no such
  160.          *          rule exists, an IllegalArgumentException will be thrown.
  161.          */
  162.         static Iso8601_Rule getRule(final int tokenLen) {
  163.             switch (tokenLen) {
  164.             case 1:
  165.                 return ISO8601_HOURS;
  166.             case 2:
  167.                 return ISO8601_HOURS_MINUTES;
  168.             case 3:
  169.                 return ISO8601_HOURS_COLON_MINUTES;
  170.             default:
  171.                 throw new IllegalArgumentException("invalid number of X");
  172.             }
  173.         }

  174.         private final int length;

  175.         /**
  176.          * Constructs an instance of {@code Iso8601_Rule} with the specified properties.
  177.          *
  178.          * @param length The number of characters in output (unless Z is output)
  179.          */
  180.         Iso8601_Rule(final int length) {
  181.             this.length = length;
  182.         }

  183.         /**
  184.          * {@inheritDoc}
  185.          */
  186.         @Override
  187.         public void appendTo(final Appendable buffer, final Calendar calendar) throws IOException {
  188.             int offset = calendar.get(Calendar.ZONE_OFFSET) + calendar.get(Calendar.DST_OFFSET);
  189.             if (offset == 0) {
  190.                 buffer.append("Z");
  191.                 return;
  192.             }

  193.             if (offset < 0) {
  194.                 buffer.append('-');
  195.                 offset = -offset;
  196.             } else {
  197.                 buffer.append('+');
  198.             }

  199.             final int hours = offset / (60 * 60 * 1000);
  200.             appendDigits(buffer, hours);

  201.             if (length < 5) {
  202.                 return;
  203.             }

  204.             if (length == 6) {
  205.                 buffer.append(':');
  206.             }

  207.             final int minutes = offset / (60 * 1000) - 60 * hours;
  208.             appendDigits(buffer, minutes);
  209.         }

  210.         /**
  211.          * {@inheritDoc}
  212.          */
  213.         @Override
  214.         public int estimateLength() {
  215.             return length;
  216.         }
  217.     }

  218.     /**
  219.      * Inner class defining a numeric rule.
  220.      */
  221.     private interface NumberRule extends Rule {
  222.         /**
  223.          * Appends the specified value to the output buffer based on the rule implementation.
  224.          *
  225.          * @param buffer the output buffer
  226.          * @param value the value to be appended
  227.          * @throws IOException if an I/O error occurs.
  228.          */
  229.         void appendTo(Appendable buffer, int value) throws IOException;
  230.     }

  231.     /**
  232.      * Inner class to output a padded number.
  233.      */
  234.     private static final class PaddedNumberField implements NumberRule {
  235.         // Note: This is final to avoid Spotbugs CT_CONSTRUCTOR_THROW
  236.         private final int field;
  237.         private final int size;

  238.         /**
  239.          * Constructs an instance of {@link PaddedNumberField}.
  240.          *
  241.          * @param field the field
  242.          * @param size size of the output field
  243.          */
  244.         PaddedNumberField(final int field, final int size) {
  245.             if (size < 3) {
  246.                 // Should use UnpaddedNumberField or TwoDigitNumberField.
  247.                 throw new IllegalArgumentException();
  248.             }
  249.             this.field = field;
  250.             this.size = size;
  251.         }

  252.         /**
  253.          * {@inheritDoc}
  254.          */
  255.         @Override
  256.         public void appendTo(final Appendable buffer, final Calendar calendar) throws IOException {
  257.             appendTo(buffer, calendar.get(field));
  258.         }

  259.         /**
  260.          * {@inheritDoc}
  261.          */
  262.         @Override
  263.         public /* final */ void appendTo(final Appendable buffer, final int value) throws IOException {
  264.             // Checkstyle complains about redundant qualifier
  265.             appendFullDigits(buffer, value, size);
  266.         }

  267.         /**
  268.          * {@inheritDoc}
  269.          */
  270.         @Override
  271.         public int estimateLength() {
  272.             return size;
  273.         }
  274.     }
  275.     // Rules
  276.     /**
  277.      * Inner class defining a rule.
  278.      */
  279.     private interface Rule {
  280.         /**
  281.          * Appends the value of the specified calendar to the output buffer based on the rule implementation.
  282.          *
  283.          * @param buf the output buffer
  284.          * @param calendar calendar to be appended
  285.          * @throws IOException if an I/O error occurs.
  286.          */
  287.         void appendTo(Appendable buf, Calendar calendar) throws IOException;

  288.         /**
  289.          * Returns the estimated length of the result.
  290.          *
  291.          * @return the estimated length
  292.          */
  293.         int estimateLength();
  294.     }

  295.     /**
  296.      * Inner class to output a constant string.
  297.      */
  298.     private static final class StringLiteral implements Rule {
  299.         private final String value;

  300.         /**
  301.          * Constructs a new instance of {@link StringLiteral}
  302.          * to hold the specified value.
  303.          *
  304.          * @param value the string literal
  305.          */
  306.         StringLiteral(final String value) {
  307.             this.value = value;
  308.         }

  309.         /**
  310.          * {@inheritDoc}
  311.          */
  312.         @Override
  313.         public void appendTo(final Appendable buffer, final Calendar calendar) throws IOException {
  314.             buffer.append(value);
  315.         }

  316.         /**
  317.          * {@inheritDoc}
  318.          */
  319.         @Override
  320.         public int estimateLength() {
  321.             return value.length();
  322.         }
  323.     }
  324.     /**
  325.      * Inner class to output one of a set of values.
  326.      */
  327.     private static final class TextField implements Rule {
  328.         private final int field;
  329.         private final String[] values;

  330.         /**
  331.          * Constructs an instance of {@link TextField}
  332.          * with the specified field and values.
  333.          *
  334.          * @param field the field
  335.          * @param values the field values
  336.          */
  337.         TextField(final int field, final String[] values) {
  338.             this.field = field;
  339.             this.values = values;
  340.         }

  341.         /**
  342.          * {@inheritDoc}
  343.          */
  344.         @Override
  345.         public void appendTo(final Appendable buffer, final Calendar calendar) throws IOException {
  346.             buffer.append(values[calendar.get(field)]);
  347.         }

  348.         /**
  349.          * {@inheritDoc}
  350.          */
  351.         @Override
  352.         public int estimateLength() {
  353.             int max = 0;
  354.             for (int i = values.length; --i >= 0;) {
  355.                 final int len = values[i].length();
  356.                 if (len > max) {
  357.                     max = len;
  358.                 }
  359.             }
  360.             return max;
  361.         }
  362.     }
  363.     /**
  364.      * Inner class that acts as a compound key for time zone names.
  365.      */
  366.     private static final class TimeZoneDisplayKey {
  367.         private final TimeZone timeZone;
  368.         private final int style;
  369.         private final Locale locale;

  370.         /**
  371.          * Constructs an instance of {@link TimeZoneDisplayKey} with the specified properties.
  372.          *
  373.          * @param timeZone the time zone
  374.          * @param daylight adjust the style for daylight saving time if {@code true}
  375.          * @param style the time zone style
  376.          * @param locale the time zone locale
  377.          */
  378.         TimeZoneDisplayKey(final TimeZone timeZone,
  379.                            final boolean daylight, final int style, final Locale locale) {
  380.             this.timeZone = timeZone;
  381.             if (daylight) {
  382.                 this.style = style | 0x80000000;
  383.             } else {
  384.                 this.style = style;
  385.             }
  386.             this.locale = LocaleUtils.toLocale(locale);
  387.         }

  388.         /**
  389.          * {@inheritDoc}
  390.          */
  391.         @Override
  392.         public boolean equals(final Object obj) {
  393.             if (this == obj) {
  394.                 return true;
  395.             }
  396.             if (obj instanceof TimeZoneDisplayKey) {
  397.                 final TimeZoneDisplayKey other = (TimeZoneDisplayKey) obj;
  398.                 return
  399.                     timeZone.equals(other.timeZone) &&
  400.                     style == other.style &&
  401.                     locale.equals(other.locale);
  402.             }
  403.             return false;
  404.         }

  405.         /**
  406.          * {@inheritDoc}
  407.          */
  408.         @Override
  409.         public int hashCode() {
  410.             return (style * 31 + locale.hashCode()) * 31 + timeZone.hashCode();
  411.         }
  412.     }
  413.     /**
  414.      * Inner class to output a time zone name.
  415.      */
  416.     private static final class TimeZoneNameRule implements Rule {
  417.         private final Locale locale;
  418.         private final int style;
  419.         private final String standard;
  420.         private final String daylight;

  421.         /**
  422.          * Constructs an instance of {@link TimeZoneNameRule} with the specified properties.
  423.          *
  424.          * @param timeZone the time zone
  425.          * @param locale the locale
  426.          * @param style the style
  427.          */
  428.         TimeZoneNameRule(final TimeZone timeZone, final Locale locale, final int style) {
  429.             this.locale = LocaleUtils.toLocale(locale);
  430.             this.style = style;
  431.             this.standard = getTimeZoneDisplay(timeZone, false, style, locale);
  432.             this.daylight = getTimeZoneDisplay(timeZone, true, style, locale);
  433.         }

  434.         /**
  435.          * {@inheritDoc}
  436.          */
  437.         @Override
  438.         public void appendTo(final Appendable buffer, final Calendar calendar) throws IOException {
  439.             final TimeZone zone = calendar.getTimeZone();
  440.             final boolean daylight = calendar.get(Calendar.DST_OFFSET) != 0;
  441.             buffer.append(getTimeZoneDisplay(zone, daylight, style, locale));
  442.         }

  443.         /**
  444.          * {@inheritDoc}
  445.          */
  446.         @Override
  447.         public int estimateLength() {
  448.             // We have no access to the Calendar object that will be passed to
  449.             // appendTo so base estimate on the TimeZone passed to the
  450.             // constructor
  451.             return Math.max(standard.length(), daylight.length());
  452.         }
  453.     }
  454.     /**
  455.      * Inner class to output a time zone as a number {@code +/-HHMM}
  456.      * or {@code +/-HH:MM}.
  457.      */
  458.     private static final class TimeZoneNumberRule implements Rule {
  459.         static final TimeZoneNumberRule INSTANCE_COLON = new TimeZoneNumberRule(true);
  460.         static final TimeZoneNumberRule INSTANCE_NO_COLON = new TimeZoneNumberRule(false);

  461.         private final boolean colon;

  462.         /**
  463.          * Constructs an instance of {@link TimeZoneNumberRule} with the specified properties.
  464.          *
  465.          * @param colon add colon between HH and MM in the output if {@code true}
  466.          */
  467.         TimeZoneNumberRule(final boolean colon) {
  468.             this.colon = colon;
  469.         }

  470.         /**
  471.          * {@inheritDoc}
  472.          */
  473.         @Override
  474.         public void appendTo(final Appendable buffer, final Calendar calendar) throws IOException {

  475.             int offset = calendar.get(Calendar.ZONE_OFFSET) + calendar.get(Calendar.DST_OFFSET);

  476.             if (offset < 0) {
  477.                 buffer.append('-');
  478.                 offset = -offset;
  479.             } else {
  480.                 buffer.append('+');
  481.             }

  482.             final int hours = offset / (60 * 60 * 1000);
  483.             appendDigits(buffer, hours);

  484.             if (colon) {
  485.                 buffer.append(':');
  486.             }

  487.             final int minutes = offset / (60 * 1000) - 60 * hours;
  488.             appendDigits(buffer, minutes);
  489.         }

  490.         /**
  491.          * {@inheritDoc}
  492.          */
  493.         @Override
  494.         public int estimateLength() {
  495.             return 5;
  496.         }
  497.     }

  498.     /**
  499.      * Inner class to output the twelve hour field.
  500.      */
  501.     private static final class TwelveHourField implements NumberRule {
  502.         private final NumberRule rule;

  503.         /**
  504.          * Constructs an instance of {@link TwelveHourField} with the specified
  505.          * {@link NumberRule}.
  506.          *
  507.          * @param rule the rule
  508.          */
  509.         TwelveHourField(final NumberRule rule) {
  510.             this.rule = rule;
  511.         }

  512.         /**
  513.          * {@inheritDoc}
  514.          */
  515.         @Override
  516.         public void appendTo(final Appendable buffer, final Calendar calendar) throws IOException {
  517.             int value = calendar.get(Calendar.HOUR);
  518.             if (value == 0) {
  519.                 value = calendar.getLeastMaximum(Calendar.HOUR) + 1;
  520.             }
  521.             rule.appendTo(buffer, value);
  522.         }

  523.         /**
  524.          * {@inheritDoc}
  525.          */
  526.         @Override
  527.         public void appendTo(final Appendable buffer, final int value) throws IOException {
  528.             rule.appendTo(buffer, value);
  529.         }

  530.         /**
  531.          * {@inheritDoc}
  532.          */
  533.         @Override
  534.         public int estimateLength() {
  535.             return rule.estimateLength();
  536.         }
  537.     }

  538.     /**
  539.      * Inner class to output the twenty four hour field.
  540.      */
  541.     private static final class TwentyFourHourField implements NumberRule {
  542.         private final NumberRule rule;

  543.         /**
  544.          * Constructs an instance of {@link TwentyFourHourField} with the specified
  545.          * {@link NumberRule}.
  546.          *
  547.          * @param rule the rule
  548.          */
  549.         TwentyFourHourField(final NumberRule rule) {
  550.             this.rule = rule;
  551.         }

  552.         /**
  553.          * {@inheritDoc}
  554.          */
  555.         @Override
  556.         public void appendTo(final Appendable buffer, final Calendar calendar) throws IOException {
  557.             int value = calendar.get(Calendar.HOUR_OF_DAY);
  558.             if (value == 0) {
  559.                 value = calendar.getMaximum(Calendar.HOUR_OF_DAY) + 1;
  560.             }
  561.             rule.appendTo(buffer, value);
  562.         }

  563.         /**
  564.          * {@inheritDoc}
  565.          */
  566.         @Override
  567.         public void appendTo(final Appendable buffer, final int value) throws IOException {
  568.             rule.appendTo(buffer, value);
  569.         }

  570.         /**
  571.          * {@inheritDoc}
  572.          */
  573.         @Override
  574.         public int estimateLength() {
  575.             return rule.estimateLength();
  576.         }
  577.     }

  578.     /**
  579.      * Inner class to output a two digit month.
  580.      */
  581.     private static final class TwoDigitMonthField implements NumberRule {
  582.         static final TwoDigitMonthField INSTANCE = new TwoDigitMonthField();

  583.         /**
  584.          * Constructs an instance of {@link TwoDigitMonthField}.
  585.          */
  586.         TwoDigitMonthField() {
  587.         }

  588.         /**
  589.          * {@inheritDoc}
  590.          */
  591.         @Override
  592.         public void appendTo(final Appendable buffer, final Calendar calendar) throws IOException {
  593.             appendTo(buffer, calendar.get(Calendar.MONTH) + 1);
  594.         }

  595.         /**
  596.          * {@inheritDoc}
  597.          */
  598.         @Override
  599.         public void appendTo(final Appendable buffer, final int value) throws IOException {
  600.             appendDigits(buffer, value);
  601.         }

  602.         /**
  603.          * {@inheritDoc}
  604.          */
  605.         @Override
  606.         public int estimateLength() {
  607.             return 2;
  608.         }
  609.     }

  610.     /**
  611.      * Inner class to output a two digit number.
  612.      */
  613.     private static final class TwoDigitNumberField implements NumberRule {
  614.         private final int field;

  615.         /**
  616.          * Constructs an instance of {@link TwoDigitNumberField} with the specified field.
  617.          *
  618.          * @param field the field
  619.          */
  620.         TwoDigitNumberField(final int field) {
  621.             this.field = field;
  622.         }

  623.         /**
  624.          * {@inheritDoc}
  625.          */
  626.         @Override
  627.         public void appendTo(final Appendable buffer, final Calendar calendar) throws IOException {
  628.             appendTo(buffer, calendar.get(field));
  629.         }

  630.         /**
  631.          * {@inheritDoc}
  632.          */
  633.         @Override
  634.         public void appendTo(final Appendable buffer, final int value) throws IOException {
  635.             if (value < 100) {
  636.                 appendDigits(buffer, value);
  637.             } else {
  638.                 appendFullDigits(buffer, value, 2);
  639.             }
  640.         }

  641.         /**
  642.          * {@inheritDoc}
  643.          */
  644.         @Override
  645.         public int estimateLength() {
  646.             return 2;
  647.         }
  648.     }

  649.     /**
  650.      * Inner class to output a two digit year.
  651.      */
  652.     private static final class TwoDigitYearField implements NumberRule {
  653.         static final TwoDigitYearField INSTANCE = new TwoDigitYearField();

  654.         /**
  655.          * Constructs an instance of {@link TwoDigitYearField}.
  656.          */
  657.         TwoDigitYearField() {
  658.         }

  659.         /**
  660.          * {@inheritDoc}
  661.          */
  662.         @Override
  663.         public void appendTo(final Appendable buffer, final Calendar calendar) throws IOException {
  664.             appendTo(buffer, calendar.get(Calendar.YEAR) % 100);
  665.         }

  666.         /**
  667.          * {@inheritDoc}
  668.          */
  669.         @Override
  670.         public void appendTo(final Appendable buffer, final int value) throws IOException {
  671.             appendDigits(buffer, value % 100);
  672.         }

  673.         /**
  674.          * {@inheritDoc}
  675.          */
  676.         @Override
  677.         public int estimateLength() {
  678.             return 2;
  679.         }
  680.     }

  681.     /**
  682.      * Inner class to output an unpadded month.
  683.      */
  684.     private static final class UnpaddedMonthField implements NumberRule {
  685.         static final UnpaddedMonthField INSTANCE = new UnpaddedMonthField();

  686.         /**
  687.          * Constructs an instance of {@link UnpaddedMonthField}.
  688.          */
  689.         UnpaddedMonthField() {
  690.         }

  691.         /**
  692.          * {@inheritDoc}
  693.          */
  694.         @Override
  695.         public void appendTo(final Appendable buffer, final Calendar calendar) throws IOException {
  696.             appendTo(buffer, calendar.get(Calendar.MONTH) + 1);
  697.         }

  698.         /**
  699.          * {@inheritDoc}
  700.          */
  701.         @Override
  702.         public void appendTo(final Appendable buffer, final int value) throws IOException {
  703.             if (value < 10) {
  704.                 buffer.append((char) (value + '0'));
  705.             } else {
  706.                 appendDigits(buffer, value);
  707.             }
  708.         }

  709.         /**
  710.          * {@inheritDoc}
  711.          */
  712.         @Override
  713.         public int estimateLength() {
  714.             return 2;
  715.         }
  716.     }

  717.     /**
  718.      * Inner class to output an unpadded number.
  719.      */
  720.     private static final class UnpaddedNumberField implements NumberRule {
  721.         private final int field;

  722.         /**
  723.          * Constructs an instance of {@link UnpaddedNumberField} with the specified field.
  724.          *
  725.          * @param field the field
  726.          */
  727.         UnpaddedNumberField(final int field) {
  728.             this.field = field;
  729.         }

  730.         /**
  731.          * {@inheritDoc}
  732.          */
  733.         @Override
  734.         public void appendTo(final Appendable buffer, final Calendar calendar) throws IOException {
  735.             appendTo(buffer, calendar.get(field));
  736.         }

  737.         /**
  738.          * {@inheritDoc}
  739.          */
  740.         @Override
  741.         public void appendTo(final Appendable buffer, final int value) throws IOException {
  742.             if (value < 10) {
  743.                 buffer.append((char) (value + '0'));
  744.             } else if (value < 100) {
  745.                 appendDigits(buffer, value);
  746.             } else {
  747.                appendFullDigits(buffer, value, 1);
  748.             }
  749.         }

  750.         /**
  751.          * {@inheritDoc}
  752.          */
  753.         @Override
  754.         public int estimateLength() {
  755.             return 4;
  756.         }
  757.     }

  758.     /**
  759.      * Inner class to output the numeric day in week.
  760.      */
  761.     private static final class WeekYear implements NumberRule {
  762.         private final NumberRule rule;

  763.         WeekYear(final NumberRule rule) {
  764.             this.rule = rule;
  765.         }

  766.         @Override
  767.         public void appendTo(final Appendable buffer, final Calendar calendar) throws IOException {
  768.             rule.appendTo(buffer, calendar.getWeekYear());
  769.         }

  770.         @Override
  771.         public void appendTo(final Appendable buffer, final int value) throws IOException {
  772.             rule.appendTo(buffer, value);
  773.         }

  774.         @Override
  775.         public int estimateLength() {
  776.             return rule.estimateLength();
  777.         }
  778.     }

  779.     /** Empty array. */
  780.     private static final Rule[] EMPTY_RULE_ARRAY = {};

  781.     /**
  782.      * Required for serialization support.
  783.      *
  784.      * @see java.io.Serializable
  785.      */
  786.     private static final long serialVersionUID = 1L;

  787.     /**
  788.      * FULL locale dependent date or time style.
  789.      */
  790.     public static final int FULL = DateFormat.FULL;

  791.     /**
  792.      * LONG locale dependent date or time style.
  793.      */
  794.     public static final int LONG = DateFormat.LONG;

  795.     /**
  796.      * MEDIUM locale dependent date or time style.
  797.      */
  798.     public static final int MEDIUM = DateFormat.MEDIUM;

  799.     /**
  800.      * SHORT locale dependent date or time style.
  801.      */
  802.     public static final int SHORT = DateFormat.SHORT;

  803.     private static final int MAX_DIGITS = 10; // log10(Integer.MAX_VALUE) ~= 9.3

  804.     private static final ConcurrentMap<TimeZoneDisplayKey, String> timeZoneDisplayCache = new ConcurrentHashMap<>(7);

  805.     /**
  806.      * Appends two digits to the given buffer.
  807.      *
  808.      * @param buffer the buffer to append to.
  809.      * @param value the value to append digits from.
  810.      * @throws IOException If an I/O error occurs
  811.      */
  812.     private static void appendDigits(final Appendable buffer, final int value) throws IOException {
  813.         buffer.append((char) (value / 10 + '0'));
  814.         buffer.append((char) (value % 10 + '0'));
  815.     }

  816.     /**
  817.      * Appends all digits to the given buffer.
  818.      *
  819.      * @param buffer the buffer to append to.
  820.      * @param value the value to append digits from.
  821.      * @param minFieldWidth Minimum field width.
  822.      * @throws IOException If an I/O error occurs
  823.      */
  824.     private static void appendFullDigits(final Appendable buffer, int value, int minFieldWidth) throws IOException {
  825.         // specialized paths for 1 to 4 digits -> avoid the memory allocation from the temporary work array
  826.         // see LANG-1248
  827.         if (value < 10000) {
  828.             // less memory allocation path works for four digits or less

  829.             int nDigits = 4;
  830.             if (value < 1000) {
  831.                 --nDigits;
  832.                 if (value < 100) {
  833.                     --nDigits;
  834.                     if (value < 10) {
  835.                         --nDigits;
  836.                     }
  837.                 }
  838.             }
  839.             // left zero pad
  840.             for (int i = minFieldWidth - nDigits; i > 0; --i) {
  841.                 buffer.append('0');
  842.             }

  843.             switch (nDigits) {
  844.             case 4:
  845.                 buffer.append((char) (value / 1000 + '0'));
  846.                 value %= 1000;
  847.                 // falls-through
  848.             case 3:
  849.                 if (value >= 100) {
  850.                     buffer.append((char) (value / 100 + '0'));
  851.                     value %= 100;
  852.                 } else {
  853.                     buffer.append('0');
  854.                 }
  855.                 // falls-through
  856.             case 2:
  857.                 if (value >= 10) {
  858.                     buffer.append((char) (value / 10 + '0'));
  859.                     value %= 10;
  860.                 } else {
  861.                     buffer.append('0');
  862.                 }
  863.                 // falls-through
  864.             case 1:
  865.                 buffer.append((char) (value + '0'));
  866.             }
  867.         } else {
  868.             // more memory allocation path works for any digits

  869.             // build up decimal representation in reverse
  870.             final char[] work = new char[MAX_DIGITS];
  871.             int digit = 0;
  872.             while (value != 0) {
  873.                 work[digit++] = (char) (value % 10 + '0');
  874.                 value /= 10;
  875.             }

  876.             // pad with zeros
  877.             while (digit < minFieldWidth) {
  878.                 buffer.append('0');
  879.                 --minFieldWidth;
  880.             }

  881.             // reverse
  882.             while (--digit >= 0) {
  883.                 buffer.append(work[digit]);
  884.             }
  885.         }
  886.     }

  887.     static void clear() {
  888.         timeZoneDisplayCache.clear();
  889.     }

  890.     /**
  891.      * Gets the time zone display name, using a cache for performance.
  892.      *
  893.      * @param tz  the zone to query
  894.      * @param daylight  true if daylight savings
  895.      * @param style  the style to use {@code TimeZone.LONG} or {@code TimeZone.SHORT}
  896.      * @param locale  the locale to use
  897.      * @return the textual name of the time zone
  898.      */
  899.     static String getTimeZoneDisplay(final TimeZone tz, final boolean daylight, final int style, final Locale locale) {
  900.         final TimeZoneDisplayKey key = new TimeZoneDisplayKey(tz, daylight, style, locale);
  901.         // This is a very slow call, so cache the results.
  902.         return timeZoneDisplayCache.computeIfAbsent(key, k -> tz.getDisplayName(daylight, style, locale));
  903.     }

  904.     /**
  905.      * The pattern.
  906.      */
  907.     private final String pattern;

  908.     /**
  909.      * The time zone.
  910.      */
  911.     private final TimeZone timeZone;

  912.     /**
  913.      * The locale.
  914.      */
  915.     private final Locale locale;

  916.     /**
  917.      * The parsed rules.
  918.      */
  919.     private transient Rule[] rules;

  920.     /**
  921.      * The estimated maximum length.
  922.      */
  923.     private transient int maxLengthEstimate;

  924.     // Constructor
  925.     /**
  926.      * Constructs a new FastDatePrinter.
  927.      * Use {@link FastDateFormat#getInstance(String, TimeZone, Locale)}  or another variation of the
  928.      * factory methods of {@link FastDateFormat} to get a cached FastDatePrinter instance.
  929.      *
  930.      * @param pattern  {@link java.text.SimpleDateFormat} compatible pattern
  931.      * @param timeZone  non-null time zone to use
  932.      * @param locale  non-null locale to use
  933.      * @throws NullPointerException if pattern, timeZone, or locale is null.
  934.      */
  935.     protected FastDatePrinter(final String pattern, final TimeZone timeZone, final Locale locale) {
  936.         this.pattern = pattern;
  937.         this.timeZone = timeZone;
  938.         this.locale = LocaleUtils.toLocale(locale);
  939.         init();
  940.     }

  941.     /**
  942.      * Performs the formatting by applying the rules to the
  943.      * specified calendar.
  944.      *
  945.      * @param calendar  the calendar to format
  946.      * @param buf  the buffer to format into
  947.      * @param <B> the Appendable class type, usually StringBuilder or StringBuffer.
  948.      * @return the specified string buffer
  949.      */
  950.     private <B extends Appendable> B applyRules(final Calendar calendar, final B buf) {
  951.         try {
  952.             for (final Rule rule : rules) {
  953.                 rule.appendTo(buf, calendar);
  954.             }
  955.         } catch (final IOException ioe) {
  956.             ExceptionUtils.asRuntimeException(ioe);
  957.         }
  958.         return buf;
  959.     }

  960.     /**
  961.      * Performs the formatting by applying the rules to the
  962.      * specified calendar.
  963.      *
  964.      * @param calendar the calendar to format
  965.      * @param buf the buffer to format into
  966.      * @return the specified string buffer
  967.      * @deprecated use {@link #format(Calendar)} or {@link #format(Calendar, Appendable)}
  968.      */
  969.     @Deprecated
  970.     protected StringBuffer applyRules(final Calendar calendar, final StringBuffer buf) {
  971.         return (StringBuffer) applyRules(calendar, (Appendable) buf);
  972.     }

  973.     /**
  974.      * Creates a String representation of the given Calendar by applying the rules of this printer to it.
  975.      * @param c the Calendar to apply the rules to.
  976.      * @return a String representation of the given Calendar.
  977.      */
  978.     private String applyRulesToString(final Calendar c) {
  979.         return applyRules(c, new StringBuilder(maxLengthEstimate)).toString();
  980.     }

  981.     // Basics
  982.     /**
  983.      * Compares two objects for equality.
  984.      *
  985.      * @param obj  the object to compare to
  986.      * @return {@code true} if equal
  987.      */
  988.     @Override
  989.     public boolean equals(final Object obj) {
  990.         if (!(obj instanceof FastDatePrinter)) {
  991.             return false;
  992.         }
  993.         final FastDatePrinter other = (FastDatePrinter) obj;
  994.         return pattern.equals(other.pattern)
  995.             && timeZone.equals(other.timeZone)
  996.             && locale.equals(other.locale);
  997.     }

  998.     /* (non-Javadoc)
  999.      * @see org.apache.commons.lang3.time.DatePrinter#format(java.util.Calendar)
  1000.      */
  1001.     @Override
  1002.     public String format(final Calendar calendar) {
  1003.         return format(calendar, new StringBuilder(maxLengthEstimate)).toString();
  1004.     }

  1005.     /* (non-Javadoc)
  1006.      * @see org.apache.commons.lang3.time.DatePrinter#format(java.util.Calendar, Appendable)
  1007.      */
  1008.     @Override
  1009.     public <B extends Appendable> B format(Calendar calendar, final B buf) {
  1010.         // do not pass in calendar directly, this will cause TimeZone of FastDatePrinter to be ignored
  1011.         if (!calendar.getTimeZone().equals(timeZone)) {
  1012.             calendar = (Calendar) calendar.clone();
  1013.             calendar.setTimeZone(timeZone);
  1014.         }
  1015.         return applyRules(calendar, buf);
  1016.     }

  1017.     /* (non-Javadoc)
  1018.      * @see org.apache.commons.lang3.time.DatePrinter#format(java.util.Calendar, StringBuffer)
  1019.      */
  1020.     @Override
  1021.     public StringBuffer format(final Calendar calendar, final StringBuffer buf) {
  1022.         // do not pass in calendar directly, this will cause TimeZone of FastDatePrinter to be ignored
  1023.         return format(calendar.getTime(), buf);
  1024.     }

  1025.     /* (non-Javadoc)
  1026.      * @see org.apache.commons.lang3.time.DatePrinter#format(java.util.Date)
  1027.      */
  1028.     @Override
  1029.     public String format(final Date date) {
  1030.         final Calendar c = newCalendar();
  1031.         c.setTime(date);
  1032.         return applyRulesToString(c);
  1033.     }

  1034.     /* (non-Javadoc)
  1035.      * @see org.apache.commons.lang3.time.DatePrinter#format(java.util.Date, Appendable)
  1036.      */
  1037.     @Override
  1038.     public <B extends Appendable> B format(final Date date, final B buf) {
  1039.         final Calendar c = newCalendar();
  1040.         c.setTime(date);
  1041.         return applyRules(c, buf);
  1042.     }

  1043.     /* (non-Javadoc)
  1044.      * @see org.apache.commons.lang3.time.DatePrinter#format(java.util.Date, StringBuffer)
  1045.      */
  1046.     @Override
  1047.     public StringBuffer format(final Date date, final StringBuffer buf) {
  1048.         final Calendar c = newCalendar();
  1049.         c.setTime(date);
  1050.         return (StringBuffer) applyRules(c, (Appendable) buf);
  1051.     }

  1052.     /* (non-Javadoc)
  1053.      * @see org.apache.commons.lang3.time.DatePrinter#format(long)
  1054.      */
  1055.     @Override
  1056.     public String format(final long millis) {
  1057.         final Calendar c = newCalendar();
  1058.         c.setTimeInMillis(millis);
  1059.         return applyRulesToString(c);
  1060.     }

  1061.     /* (non-Javadoc)
  1062.      * @see org.apache.commons.lang3.time.DatePrinter#format(long, Appendable)
  1063.      */
  1064.     @Override
  1065.     public <B extends Appendable> B format(final long millis, final B buf) {
  1066.         final Calendar c = newCalendar();
  1067.         c.setTimeInMillis(millis);
  1068.         return applyRules(c, buf);
  1069.     }

  1070.     /* (non-Javadoc)
  1071.      * @see org.apache.commons.lang3.time.DatePrinter#format(long, StringBuffer)
  1072.      */
  1073.     @Override
  1074.     public StringBuffer format(final long millis, final StringBuffer buf) {
  1075.         final Calendar c = newCalendar();
  1076.         c.setTimeInMillis(millis);
  1077.         return (StringBuffer) applyRules(c, (Appendable) buf);
  1078.     }

  1079.     /**
  1080.      * Formats a {@link Date}, {@link Calendar} or
  1081.      * {@link Long} (milliseconds) object.
  1082.      * @param obj  the object to format
  1083.      * @return The formatted value.
  1084.      * @since 3.5
  1085.      */
  1086.     String format(final Object obj) {
  1087.         if (obj instanceof Date) {
  1088.             return format((Date) obj);
  1089.         }
  1090.         if (obj instanceof Calendar) {
  1091.             return format((Calendar) obj);
  1092.         }
  1093.         if (obj instanceof Long) {
  1094.             return format(((Long) obj).longValue());
  1095.         }
  1096.         throw new IllegalArgumentException("Unknown class: " + ClassUtils.getName(obj, "<null>"));
  1097.     }

  1098.     // Format methods
  1099.     /**
  1100.      * Formats a {@link Date}, {@link Calendar} or
  1101.      * {@link Long} (milliseconds) object.
  1102.      * @deprecated Use {{@link #format(Date)}, {{@link #format(Calendar)}, {{@link #format(long)}.
  1103.      * @param obj  the object to format
  1104.      * @param toAppendTo  the buffer to append to
  1105.      * @param pos  the position - ignored
  1106.      * @return the buffer passed in
  1107.      */
  1108.     @Deprecated
  1109.     @Override
  1110.     public StringBuffer format(final Object obj, final StringBuffer toAppendTo, final FieldPosition pos) {
  1111.         if (obj instanceof Date) {
  1112.             return format((Date) obj, toAppendTo);
  1113.         }
  1114.         if (obj instanceof Calendar) {
  1115.             return format((Calendar) obj, toAppendTo);
  1116.         }
  1117.         if (obj instanceof Long) {
  1118.             return format(((Long) obj).longValue(), toAppendTo);
  1119.         }
  1120.         throw new IllegalArgumentException("Unknown class: " + ClassUtils.getName(obj, "<null>"));
  1121.     }

  1122.     /* (non-Javadoc)
  1123.      * @see org.apache.commons.lang3.time.DatePrinter#getLocale()
  1124.      */
  1125.     @Override
  1126.     public Locale getLocale() {
  1127.         return locale;
  1128.     }

  1129.     /**
  1130.      * Gets an estimate for the maximum string length that the
  1131.      * formatter will produce.
  1132.      *
  1133.      * <p>The actual formatted length will almost always be less than or
  1134.      * equal to this amount.</p>
  1135.      *
  1136.      * @return the maximum formatted length
  1137.      */
  1138.     public int getMaxLengthEstimate() {
  1139.         return maxLengthEstimate;
  1140.     }

  1141.     /* (non-Javadoc)
  1142.      * @see org.apache.commons.lang3.time.DatePrinter#getPattern()
  1143.      */
  1144.     @Override
  1145.     public String getPattern() {
  1146.         return pattern;
  1147.     }

  1148.     /* (non-Javadoc)
  1149.      * @see org.apache.commons.lang3.time.DatePrinter#getTimeZone()
  1150.      */
  1151.     @Override
  1152.     public TimeZone getTimeZone() {
  1153.         return timeZone;
  1154.     }

  1155.     /**
  1156.      * Returns a hash code compatible with equals.
  1157.      *
  1158.      * @return a hash code compatible with equals
  1159.      */
  1160.     @Override
  1161.     public int hashCode() {
  1162.         return pattern.hashCode() + 13 * (timeZone.hashCode() + 13 * locale.hashCode());
  1163.     }

  1164.     /**
  1165.      * Initializes the instance for first use.
  1166.      */
  1167.     private void init() {
  1168.         final List<Rule> rulesList = parsePattern();
  1169.         rules = rulesList.toArray(EMPTY_RULE_ARRAY);

  1170.         int len = 0;
  1171.         for (int i = rules.length; --i >= 0;) {
  1172.             len += rules[i].estimateLength();
  1173.         }

  1174.         maxLengthEstimate = len;
  1175.     }

  1176.     /**
  1177.      * Creates a new Calendar instance.
  1178.      * @return a new Calendar instance.
  1179.      */
  1180.     private Calendar newCalendar() {
  1181.         return Calendar.getInstance(timeZone, locale);
  1182.     }

  1183.     // Parse the pattern
  1184.     /**
  1185.      * Returns a list of Rules given a pattern.
  1186.      *
  1187.      * @return a {@link List} of Rule objects
  1188.      * @throws IllegalArgumentException if pattern is invalid
  1189.      */
  1190.     protected List<Rule> parsePattern() {
  1191.         final DateFormatSymbols symbols = new DateFormatSymbols(locale);
  1192.         final List<Rule> rules = new ArrayList<>();

  1193.         final String[] ERAs = symbols.getEras();
  1194.         final String[] months = symbols.getMonths();
  1195.         final String[] shortMonths = symbols.getShortMonths();
  1196.         final String[] weekdays = symbols.getWeekdays();
  1197.         final String[] shortWeekdays = symbols.getShortWeekdays();
  1198.         final String[] AmPmStrings = symbols.getAmPmStrings();

  1199.         final int length = pattern.length();
  1200.         final int[] indexRef = new int[1];

  1201.         for (int i = 0; i < length; i++) {
  1202.             indexRef[0] = i;
  1203.             final String token = parseToken(pattern, indexRef);
  1204.             i = indexRef[0];

  1205.             final int tokenLen = token.length();
  1206.             if (tokenLen == 0) {
  1207.                 break;
  1208.             }

  1209.             Rule rule;
  1210.             final char c = token.charAt(0);

  1211.             switch (c) {
  1212.             case 'G': // era designator (text)
  1213.                 rule = new TextField(Calendar.ERA, ERAs);
  1214.                 break;
  1215.             case 'y': // year (number)
  1216.             case 'Y': // week year
  1217.                 if (tokenLen == 2) {
  1218.                     rule = TwoDigitYearField.INSTANCE;
  1219.                 } else {
  1220.                     rule = selectNumberRule(Calendar.YEAR, Math.max(tokenLen, 4));
  1221.                 }
  1222.                 if (c == 'Y') {
  1223.                     rule = new WeekYear((NumberRule) rule);
  1224.                 }
  1225.                 break;
  1226.             case 'M': // month in year (text and number)
  1227.                 if (tokenLen >= 4) {
  1228.                     rule = new TextField(Calendar.MONTH, months);
  1229.                 } else if (tokenLen == 3) {
  1230.                     rule = new TextField(Calendar.MONTH, shortMonths);
  1231.                 } else if (tokenLen == 2) {
  1232.                     rule = TwoDigitMonthField.INSTANCE;
  1233.                 } else {
  1234.                     rule = UnpaddedMonthField.INSTANCE;
  1235.                 }
  1236.                 break;
  1237.             case 'L': // month in year (text and number)
  1238.                 if (tokenLen >= 4) {
  1239.                     rule = new TextField(Calendar.MONTH, CalendarUtils.getInstance(locale).getStandaloneLongMonthNames());
  1240.                 } else if (tokenLen == 3) {
  1241.                     rule = new TextField(Calendar.MONTH, CalendarUtils.getInstance(locale).getStandaloneShortMonthNames());
  1242.                 } else if (tokenLen == 2) {
  1243.                     rule = TwoDigitMonthField.INSTANCE;
  1244.                 } else {
  1245.                     rule = UnpaddedMonthField.INSTANCE;
  1246.                 }
  1247.                 break;
  1248.             case 'd': // day in month (number)
  1249.                 rule = selectNumberRule(Calendar.DAY_OF_MONTH, tokenLen);
  1250.                 break;
  1251.             case 'h': // hour in am/pm (number, 1..12)
  1252.                 rule = new TwelveHourField(selectNumberRule(Calendar.HOUR, tokenLen));
  1253.                 break;
  1254.             case 'H': // hour in day (number, 0..23)
  1255.                 rule = selectNumberRule(Calendar.HOUR_OF_DAY, tokenLen);
  1256.                 break;
  1257.             case 'm': // minute in hour (number)
  1258.                 rule = selectNumberRule(Calendar.MINUTE, tokenLen);
  1259.                 break;
  1260.             case 's': // second in minute (number)
  1261.                 rule = selectNumberRule(Calendar.SECOND, tokenLen);
  1262.                 break;
  1263.             case 'S': // millisecond (number)
  1264.                 rule = selectNumberRule(Calendar.MILLISECOND, tokenLen);
  1265.                 break;
  1266.             case 'E': // day in week (text)
  1267.                 rule = new TextField(Calendar.DAY_OF_WEEK, tokenLen < 4 ? shortWeekdays : weekdays);
  1268.                 break;
  1269.             case 'u': // day in week (number)
  1270.                 rule = new DayInWeekField(selectNumberRule(Calendar.DAY_OF_WEEK, tokenLen));
  1271.                 break;
  1272.             case 'D': // day in year (number)
  1273.                 rule = selectNumberRule(Calendar.DAY_OF_YEAR, tokenLen);
  1274.                 break;
  1275.             case 'F': // day of week in month (number)
  1276.                 rule = selectNumberRule(Calendar.DAY_OF_WEEK_IN_MONTH, tokenLen);
  1277.                 break;
  1278.             case 'w': // week in year (number)
  1279.                 rule = selectNumberRule(Calendar.WEEK_OF_YEAR, tokenLen);
  1280.                 break;
  1281.             case 'W': // week in month (number)
  1282.                 rule = selectNumberRule(Calendar.WEEK_OF_MONTH, tokenLen);
  1283.                 break;
  1284.             case 'a': // am/pm marker (text)
  1285.                 rule = new TextField(Calendar.AM_PM, AmPmStrings);
  1286.                 break;
  1287.             case 'k': // hour in day (1..24)
  1288.                 rule = new TwentyFourHourField(selectNumberRule(Calendar.HOUR_OF_DAY, tokenLen));
  1289.                 break;
  1290.             case 'K': // hour in am/pm (0..11)
  1291.                 rule = selectNumberRule(Calendar.HOUR, tokenLen);
  1292.                 break;
  1293.             case 'X': // ISO 8601
  1294.                 rule = Iso8601_Rule.getRule(tokenLen);
  1295.                 break;
  1296.             case 'z': // time zone (text)
  1297.                 rule = new TimeZoneNameRule(timeZone, locale, tokenLen >= 4 ? TimeZone.LONG : TimeZone.SHORT);
  1298.                 break;
  1299.             case 'Z': // time zone (value)
  1300.                 if (tokenLen == 1) {
  1301.                     rule = TimeZoneNumberRule.INSTANCE_NO_COLON;
  1302.                 } else if (tokenLen == 2) {
  1303.                     rule = Iso8601_Rule.ISO8601_HOURS_COLON_MINUTES;
  1304.                 } else {
  1305.                     rule = TimeZoneNumberRule.INSTANCE_COLON;
  1306.                 }
  1307.                 break;
  1308.             case '\'': // literal text
  1309.                 final String sub = token.substring(1);
  1310.                 if (sub.length() == 1) {
  1311.                     rule = new CharacterLiteral(sub.charAt(0));
  1312.                 } else {
  1313.                     rule = new StringLiteral(sub);
  1314.                 }
  1315.                 break;
  1316.             default:
  1317.                 throw new IllegalArgumentException("Illegal pattern component: " + token);
  1318.             }

  1319.             rules.add(rule);
  1320.         }

  1321.         return rules;
  1322.     }

  1323.     /**
  1324.      * Performs the parsing of tokens.
  1325.      *
  1326.      * @param pattern  the pattern
  1327.      * @param indexRef  index references
  1328.      * @return parsed token
  1329.      */
  1330.     protected String parseToken(final String pattern, final int[] indexRef) {
  1331.         final StringBuilder buf = new StringBuilder();
  1332.         int i = indexRef[0];
  1333.         final int length = pattern.length();
  1334.         char c = pattern.charAt(i);
  1335.         final char c1 = c;
  1336.         if (CharUtils.isAsciiAlpha(c1)) {
  1337.             // Scan a run of the same character, which indicates a time
  1338.             // pattern.
  1339.             buf.append(c);
  1340.             while (i + 1 < length) {
  1341.                 final char peek = pattern.charAt(i + 1);
  1342.                 if (peek != c) {
  1343.                     break;
  1344.                 }
  1345.                 buf.append(c);
  1346.                 i++;
  1347.             }
  1348.         } else {
  1349.             // This will identify token as text.
  1350.             buf.append('\'');
  1351.             boolean inLiteral = false;
  1352.             for (; i < length; i++) {
  1353.                 c = pattern.charAt(i);
  1354.                 if (c == '\'') {
  1355.                     if (i + 1 < length && pattern.charAt(i + 1) == '\'') {
  1356.                         // '' is treated as escaped '
  1357.                         i++;
  1358.                         buf.append(c);
  1359.                     } else {
  1360.                         inLiteral = !inLiteral;
  1361.                     }
  1362.                 } else {
  1363.                     final char c2 = c;
  1364.                     if (!inLiteral && CharUtils.isAsciiAlpha(c2)) {
  1365.                         i--;
  1366.                         break;
  1367.                     }
  1368.                     buf.append(c);
  1369.                 }
  1370.             }
  1371.         }
  1372.         indexRef[0] = i;
  1373.         return buf.toString();
  1374.     }

  1375.     // Serializing
  1376.     /**
  1377.      * Create the object after serialization. This implementation reinitializes the
  1378.      * transient properties.
  1379.      *
  1380.      * @param in ObjectInputStream from which the object is being deserialized.
  1381.      * @throws IOException if there is an IO issue.
  1382.      * @throws ClassNotFoundException if a class cannot be found.
  1383.      */
  1384.     private void readObject(final ObjectInputStream in) throws IOException, ClassNotFoundException {
  1385.         in.defaultReadObject();
  1386.         init();
  1387.     }

  1388.     /**
  1389.      * Gets an appropriate rule for the padding required.
  1390.      *
  1391.      * @param field  the field to get a rule for
  1392.      * @param padding  the padding required
  1393.      * @return a new rule with the correct padding
  1394.      */
  1395.     protected NumberRule selectNumberRule(final int field, final int padding) {
  1396.         switch (padding) {
  1397.         case 1:
  1398.             return new UnpaddedNumberField(field);
  1399.         case 2:
  1400.             return new TwoDigitNumberField(field);
  1401.         default:
  1402.             return new PaddedNumberField(field, padding);
  1403.         }
  1404.     }

  1405.     /**
  1406.      * Gets a debugging string version of this formatter.
  1407.      *
  1408.      * @return a debugging string
  1409.      */
  1410.     @Override
  1411.     public String toString() {
  1412.         return "FastDatePrinter[" + pattern + "," + locale + "," + timeZone.getID() + "]";
  1413.     }
  1414. }