    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.text.DateFormat;
    020import java.text.FieldPosition;
    021import java.text.Format;
    022import java.text.ParseException;
    023import java.text.ParsePosition;
    024import java.text.SimpleDateFormat;
    025import java.util.Calendar;
    026import java.util.Date;
    027import java.util.GregorianCalendar;
    028import java.util.Locale;
    029import java.util.TimeZone;
    030
    031/**
    032 * FastDateFormat is a fast and thread-safe version of
    033 * {@link java.text.SimpleDateFormat}.
    034 *
    035 * <p>To obtain an instance of FastDateFormat, use one of the static factory methods:
    036 * {@link #getInstance(String, TimeZone, Locale)}, {@link #getDateInstance(int, TimeZone, Locale)},
    037 * {@link #getTimeInstance(int, TimeZone, Locale)}, or {@link #getDateTimeInstance(int, int, TimeZone, Locale)}
    038 * </p>
    039 *
    040 * <p>Since FastDateFormat is thread safe, you can use a static member instance:</p>
    041 * <code>
    042 *   private static final FastDateFormat DATE_FORMATTER = FastDateFormat.getDateTimeInstance(FastDateFormat.LONG, FastDateFormat.SHORT);
    043 * </code>
    044 *
    045 * <p>This class can be used as a direct replacement to
    046 * {@link SimpleDateFormat} in most formatting and parsing situations.
    047 * This class is especially useful in multi-threaded server environments.
    048 * {@link SimpleDateFormat} is not thread-safe in any JDK version,
    049 * nor will it be as Sun have closed the bug/RFE.
    050 * </p>
    051 *
    052 * <p>All patterns are compatible with
    053 * SimpleDateFormat (except time zones and some year patterns - see below).</p>
    054 *
    055 * <p>Since 3.2, FastDateFormat supports parsing as well as printing.</p>
    056 *
    057 * <p>Java 1.4 introduced a new pattern letter, {@code 'Z'}, to represent
    058 * time zones in RFC822 format (eg. {@code +0800} or {@code -1100}).
    059 * This pattern letter can be used here (on all JDK versions).</p>
    060 *
    061 * <p>In addition, the pattern {@code 'ZZ'} has been made to represent
    062 * ISO 8601 extended format time zones (eg. {@code +08:00} or {@code -11:00}).
    063 * This introduces a minor incompatibility with Java 1.4, but at a gain of
    064 * useful functionality.</p>
    065 *
    066 * <p>Javadoc cites for the year pattern: <i>For formatting, if the number of
    067 * pattern letters is 2, the year is truncated to 2 digits; otherwise it is
    068 * interpreted as a number.</i> Starting with Java 1.7 a pattern of 'Y' or
    069 * 'YYY' will be formatted as '2003', while it was '03' in former Java
    070 * versions. FastDateFormat implements the behavior of Java 7.</p>
    071 *
    072 * @since 2.0
    073 */
    074public class FastDateFormat extends Format implements DateParser, DatePrinter {
    075
    076    /**
    077     * Required for serialization support.
    078     *
    079     * @see java.io.Serializable
    080     */
    081    private static final long serialVersionUID = 2L;
    082
    083    /**
    084     * FULL locale dependent date or time style.
    085     */
    086
    087    public static final int FULL = DateFormat.FULL;
    088
    089    /**
    090     * LONG locale dependent date or time style.
    091     */
    092    public static final int LONG = DateFormat.LONG;
    093
    094    /**
    095     * MEDIUM locale dependent date or time style.
    096     */
    097    public static final int MEDIUM = DateFormat.MEDIUM;
    098
    099    /**
    100     * SHORT locale dependent date or time style.
    101     */
    102    public static final int SHORT = DateFormat.SHORT;
    103
    104    private static final AbstractFormatCache<FastDateFormat> cache = new AbstractFormatCache<FastDateFormat>() {
    105        @Override
    106        protected FastDateFormat createInstance(final String pattern, final TimeZone timeZone, final Locale locale) {
    107            return new FastDateFormat(pattern, timeZone, locale);
    108        }
    109    };
    110
    111    /**
    112     * Gets a date formatter instance using the specified style in the
    113     * default time zone and locale.
    114     *
    115     * @param style  date style: FULL, LONG, MEDIUM, or SHORT
    116     * @return a localized standard date formatter
    117     * @throws IllegalArgumentException if the Locale has no date
    118     *  pattern defined
    119     * @since 2.1
    120     */
    121    public static FastDateFormat getDateInstance(final int style) {
    122        return cache.getDateInstance(style, null, null);
    123    }
    124
    125    /**
    126     * Gets a date formatter instance using the specified style and
    127     * locale in the default time zone.
    128     *
    129     * @param style  date style: FULL, LONG, MEDIUM, or SHORT
    130     * @param locale  optional locale, overrides system locale
    131     * @return a localized standard date formatter
    132     * @throws IllegalArgumentException if the Locale has no date
    133     *  pattern defined
    134     * @since 2.1
    135     */
    136    public static FastDateFormat getDateInstance(final int style, final Locale locale) {
    137        return cache.getDateInstance(style, null, locale);
    138    }
    139
    140    /**
    141     * Gets a date formatter instance using the specified style and
    142     * time zone in the default locale.
    143     *
    144     * @param style  date style: FULL, LONG, MEDIUM, or SHORT
    145     * @param timeZone  optional time zone, overrides time zone of
    146     *  formatted date
    147     * @return a localized standard date formatter
    148     * @throws IllegalArgumentException if the Locale has no date
    149     *  pattern defined
    150     * @since 2.1
    151     */
    152    public static FastDateFormat getDateInstance(final int style, final TimeZone timeZone) {
    153        return cache.getDateInstance(style, timeZone, null);
    154    }
    155
    156    /**
    157     * Gets a date formatter instance using the specified style, time
    158     * zone and locale.
    159     *
    160     * @param style  date style: FULL, LONG, MEDIUM, or SHORT
    161     * @param timeZone  optional time zone, overrides time zone of
    162     *  formatted date
    163     * @param locale  optional locale, overrides system locale
    164     * @return a localized standard date formatter
    165     * @throws IllegalArgumentException if the Locale has no date
    166     *  pattern defined
    167     */
    168    public static FastDateFormat getDateInstance(final int style, final TimeZone timeZone, final Locale locale) {
    169        return cache.getDateInstance(style, timeZone, locale);
    170    }
    171
    172    /**
    173     * Gets a date/time formatter instance using the specified style
    174     * in the default time zone and locale.
    175     *
    176     * @param dateStyle  date style: FULL, LONG, MEDIUM, or SHORT
    177     * @param timeStyle  time style: FULL, LONG, MEDIUM, or SHORT
    178     * @return a localized standard date/time formatter
    179     * @throws IllegalArgumentException if the Locale has no date/time
    180     *  pattern defined
    181     * @since 2.1
    182     */
    183    public static FastDateFormat getDateTimeInstance(final int dateStyle, final int timeStyle) {
    184        return cache.getDateTimeInstance(dateStyle, timeStyle, null, null);
    185    }
    186
    187    /**
    188     * Gets a date/time formatter instance using the specified style and
    189     * locale in the default time zone.
    190     *
    191     * @param dateStyle  date style: FULL, LONG, MEDIUM, or SHORT
    192     * @param timeStyle  time style: FULL, LONG, MEDIUM, or SHORT
    193     * @param locale  optional locale, overrides system locale
    194     * @return a localized standard date/time formatter
    195     * @throws IllegalArgumentException if the Locale has no date/time
    196     *  pattern defined
    197     * @since 2.1
    198     */
    199    public static FastDateFormat getDateTimeInstance(final int dateStyle, final int timeStyle, final Locale locale) {
    200        return cache.getDateTimeInstance(dateStyle, timeStyle, null, locale);
    201    }
    202
    203    /**
    204     * Gets a date/time formatter instance using the specified style and
    205     * time zone in the default locale.
    206     *
    207     * @param dateStyle  date style: FULL, LONG, MEDIUM, or SHORT
    208     * @param timeStyle  time style: FULL, LONG, MEDIUM, or SHORT
    209     * @param timeZone  optional time zone, overrides time zone of
    210     *  formatted date
    211     * @return a localized standard date/time formatter
    212     * @throws IllegalArgumentException if the Locale has no date/time
    213     *  pattern defined
    214     * @since 2.1
    215     */
    216    public static FastDateFormat getDateTimeInstance(final int dateStyle, final int timeStyle, final TimeZone timeZone) {
    217        return getDateTimeInstance(dateStyle, timeStyle, timeZone, null);
    218    }
    219
    220    /**
    221     * Gets a date/time formatter instance using the specified style,
    222     * time zone and locale.
    223     *
    224     * @param dateStyle  date style: FULL, LONG, MEDIUM, or SHORT
    225     * @param timeStyle  time style: FULL, LONG, MEDIUM, or SHORT
    226     * @param timeZone  optional time zone, overrides time zone of
    227     *  formatted date
    228     * @param locale  optional locale, overrides system locale
    229     * @return a localized standard date/time formatter
    230     * @throws IllegalArgumentException if the Locale has no date/time
    231     *  pattern defined
    232     */
    233    public static FastDateFormat getDateTimeInstance(
    234            final int dateStyle, final int timeStyle, final TimeZone timeZone, final Locale locale) {
    235        return cache.getDateTimeInstance(dateStyle, timeStyle, timeZone, locale);
    236    }
    237
    238    /**
    239     * Gets a formatter instance using the default pattern in the
    240     * default locale.
    241     *
    242     * @return a date/time formatter
    243     */
    244    public static FastDateFormat getInstance() {
    245        return cache.getInstance();
    246    }
    247
    248    /**
    249     * Gets a formatter instance using the specified pattern in the
    250     * default locale.
    251     *
    252     * @param pattern  {@link java.text.SimpleDateFormat} compatible
    253     *  pattern
    254     * @return a pattern based date/time formatter
    255     * @throws IllegalArgumentException if pattern is invalid
    256     */
    257    public static FastDateFormat getInstance(final String pattern) {
    258        return cache.getInstance(pattern, null, null);
    259    }
    260
    261    /**
    262     * Gets a formatter instance using the specified pattern and
    263     * locale.
    264     *
    265     * @param pattern  {@link java.text.SimpleDateFormat} compatible
    266     *  pattern
    267     * @param locale  optional locale, overrides system locale
    268     * @return a pattern based date/time formatter
    269     * @throws IllegalArgumentException if pattern is invalid
    270     */
    271    public static FastDateFormat getInstance(final String pattern, final Locale locale) {
    272        return cache.getInstance(pattern, null, locale);
    273    }
    274
    275    /**
    276     * Gets a formatter instance using the specified pattern and
    277     * time zone.
    278     *
    279     * @param pattern  {@link java.text.SimpleDateFormat} compatible
    280     *  pattern
    281     * @param timeZone  optional time zone, overrides time zone of
    282     *  formatted date
    283     * @return a pattern based date/time formatter
    284     * @throws IllegalArgumentException if pattern is invalid
    285     */
    286    public static FastDateFormat getInstance(final String pattern, final TimeZone timeZone) {
    287        return cache.getInstance(pattern, timeZone, null);
    288    }
    289
    290    /**
    291     * Gets a formatter instance using the specified pattern, time zone
    292     * and locale.
    293     *
    294     * @param pattern  {@link java.text.SimpleDateFormat} compatible
    295     *  pattern
    296     * @param timeZone  optional time zone, overrides time zone of
    297     *  formatted date
    298     * @param locale  optional locale, overrides system locale
    299     * @return a pattern based date/time formatter
    300     * @throws IllegalArgumentException if pattern is invalid
    301     *  or {@code null}
    302     */
    303    public static FastDateFormat getInstance(final String pattern, final TimeZone timeZone, final Locale locale) {
    304        return cache.getInstance(pattern, timeZone, locale);
    305    }
    306
    307    /**
    308     * Gets a time formatter instance using the specified style in the
    309     * default time zone and locale.
    310     *
    311     * @param style  time style: FULL, LONG, MEDIUM, or SHORT
    312     * @return a localized standard time formatter
    313     * @throws IllegalArgumentException if the Locale has no time
    314     *  pattern defined
    315     * @since 2.1
    316     */
    317    public static FastDateFormat getTimeInstance(final int style) {
    318        return cache.getTimeInstance(style, null, null);
    319    }
    320
    321    /**
    322     * Gets a time formatter instance using the specified style and
    323     * locale in the default time zone.
    324     *
    325     * @param style  time style: FULL, LONG, MEDIUM, or SHORT
    326     * @param locale  optional locale, overrides system locale
    327     * @return a localized standard time formatter
    328     * @throws IllegalArgumentException if the Locale has no time
    329     *  pattern defined
    330     * @since 2.1
    331     */
    332    public static FastDateFormat getTimeInstance(final int style, final Locale locale) {
    333        return cache.getTimeInstance(style, null, locale);
    334    }
    335
    336    /**
    337     * Gets a time formatter instance using the specified style and
    338     * time zone in the default locale.
    339     *
    340     * @param style  time style: FULL, LONG, MEDIUM, or SHORT
    341     * @param timeZone  optional time zone, overrides time zone of
    342     *  formatted time
    343     * @return a localized standard time formatter
    344     * @throws IllegalArgumentException if the Locale has no time
    345     *  pattern defined
    346     * @since 2.1
    347     */
    348    public static FastDateFormat getTimeInstance(final int style, final TimeZone timeZone) {
    349        return cache.getTimeInstance(style, timeZone, null);
    350    }
    351
    352    /**
    353     * Gets a time formatter instance using the specified style, time
    354     * zone and locale.
    355     *
    356     * @param style  time style: FULL, LONG, MEDIUM, or SHORT
    357     * @param timeZone  optional time zone, overrides time zone of
    358     *  formatted time
    359     * @param locale  optional locale, overrides system locale
    360     * @return a localized standard time formatter
    361     * @throws IllegalArgumentException if the Locale has no time
    362     *  pattern defined
    363     */
    364    public static FastDateFormat getTimeInstance(final int style, final TimeZone timeZone, final Locale locale) {
    365        return cache.getTimeInstance(style, timeZone, locale);
    366    }
    367
    368    /** Our fast printer. */
    369    private final FastDatePrinter printer;
    370    /** Our fast parser. */
    371    private final FastDateParser parser;
    372
    373    // Constructor
    374    /**
    375     * Constructs a new FastDateFormat.
    376     *
    377     * @param pattern  {@link java.text.SimpleDateFormat} compatible pattern
    378     * @param timeZone  non-null time zone to use
    379     * @param locale  non-null locale to use
    380     * @throws NullPointerException if pattern, timeZone, or locale is null.
    381     */
    382    protected FastDateFormat(final String pattern, final TimeZone timeZone, final Locale locale) {
    383        this(pattern, timeZone, locale, null);
    384    }
    385
    386    // Constructor
    387    /**
    388     * Constructs a new FastDateFormat.
    389     *
    390     * @param pattern  {@link java.text.SimpleDateFormat} compatible pattern
    391     * @param timeZone  non-null time zone to use
    392     * @param locale  non-null locale to use
    393     * @param centuryStart The start of the 100-year period to use as the "default century" for 2 digit year parsing.  If centuryStart is null, defaults to now - 80 years
    394     * @throws NullPointerException if pattern, timeZone, or locale is null.
    395     */
    396    protected FastDateFormat(final String pattern, final TimeZone timeZone, final Locale locale, final Date centuryStart) {
    397        printer = new FastDatePrinter(pattern, timeZone, locale);
    398        parser = new FastDateParser(pattern, timeZone, locale, centuryStart);
    399    }
    400
    401    /**
    402     * Performs the formatting by applying the rules to the
    403     * specified calendar.
    404     *
    405     * @param calendar the calendar to format
    406     * @param buf  the buffer to format into
    407     * @return the specified string buffer
    408     * @deprecated Use {@link #format(Calendar, Appendable)}
    409     */
    410    @Deprecated
    411    protected StringBuffer applyRules(final Calendar calendar, final StringBuffer buf) {
    412        return printer.applyRules(calendar, buf);
    413    }
    414
    415    // Basics
    416    /**
    417     * Compares two objects for equality.
    418     *
    419     * @param obj  the object to compare to
    420     * @return {@code true} if equal
    421     */
    422    @Override
    423    public boolean equals(final Object obj) {
    424        if (!(obj instanceof FastDateFormat)) {
    425            return false;
    426        }
    427        final FastDateFormat other = (FastDateFormat) obj;
    428        // no need to check parser, as it has same invariants as printer
    429        return printer.equals(other.printer);
    430    }
    431
    432    /**
    433     * Formats a {@link Calendar} object.
    434     *
    435     * @param calendar  the calendar to format
    436     * @return the formatted string
    437     */
    438    @Override
    439    public String format(final Calendar calendar) {
    440        return printer.format(calendar);
    441    }
    442
    443    /**
    444     * Formats a {@link Calendar} object into the
    445     * supplied {@link StringBuffer}.
    446     *
    447     * @param calendar  the calendar to format
    448     * @param buf  the buffer to format into
    449     * @return the specified string buffer
    450     * @since 3.5
    451    */
    452    @Override
    453    public <B extends Appendable> B format(final Calendar calendar, final B buf) {
    454        return printer.format(calendar, buf);
    455    }
    456
    457    /**
    458     * Formats a {@link Calendar} object into the
    459     * supplied {@link StringBuffer}.
    460     *
    461     * @param calendar  the calendar to format
    462     * @param buf  the buffer to format into
    463     * @return the specified string buffer
    464     * @deprecated Use {{@link #format(Calendar, Appendable)}.
    465     */
    466    @Deprecated
    467    @Override
    468    public StringBuffer format(final Calendar calendar, final StringBuffer buf) {
    469        return printer.format(calendar, buf);
    470    }
    471
    472    /**
    473     * Formats a {@link Date} object using a {@link GregorianCalendar}.
    474     *
    475     * @param date  the date to format
    476     * @return the formatted string
    477     */
    478    @Override
    479    public String format(final Date date) {
    480        return printer.format(date);
    481    }
    482
    483    /**
    484     * Formats a {@link Date} object into the
    485     * supplied {@link StringBuffer} using a {@link GregorianCalendar}.
    486     *
    487     * @param date  the date to format
    488     * @param buf  the buffer to format into
    489     * @return the specified string buffer
    490     * @since 3.5
    491     */
    492    @Override
    493    public <B extends Appendable> B format(final Date date, final B buf) {
    494        return printer.format(date, buf);
    495    }
    496
    497    /**
    498     * Formats a {@link Date} object into the
    499     * supplied {@link StringBuffer} using a {@link GregorianCalendar}.
    500     *
    501     * @param date  the date to format
    502     * @param buf  the buffer to format into
    503     * @return the specified string buffer
    504     * @deprecated Use {{@link #format(Date, Appendable)}.
    505     */
    506    @Deprecated
    507    @Override
    508    public StringBuffer format(final Date date, final StringBuffer buf) {
    509        return printer.format(date, buf);
    510    }
    511
    512    /**
    513     * Formats a millisecond {@code long} value.
    514     *
    515     * @param millis  the millisecond value to format
    516     * @return the formatted string
    517     * @since 2.1
    518     */
    519    @Override
    520    public String format(final long millis) {
    521        return printer.format(millis);
    522    }
    523
    524    /**
    525     * Formats a millisecond {@code long} value into the
    526     * supplied {@link StringBuffer}.
    527     *
    528     * @param millis  the millisecond value to format
    529     * @param buf  the buffer to format into
    530     * @return the specified string buffer
    531     * @since 3.5
    532     */
    533    @Override
    534    public <B extends Appendable> B format(final long millis, final B buf) {
    535        return printer.format(millis, buf);
    536    }
    537
    538    // Parsing
    539
    540
    541    /**
    542     * Formats a millisecond {@code long} value into the
    543     * supplied {@link StringBuffer}.
    544     *
    545     * @param millis  the millisecond value to format
    546     * @param buf  the buffer to format into
    547     * @return the specified string buffer
    548     * @since 2.1
    549     * @deprecated Use {{@link #format(long, Appendable)}.
    550     */
    551    @Deprecated
    552    @Override
    553    public StringBuffer format(final long millis, final StringBuffer buf) {
    554        return printer.format(millis, buf);
    555    }
    556
    557    // Format methods
    558    /**
    559     * Formats a {@link Date}, {@link Calendar} or
    560     * {@link Long} (milliseconds) object.
    561     * This method is an implementation of {@link Format#format(Object, StringBuffer, FieldPosition)}
    562     *
    563     * @param obj  the object to format
    564     * @param toAppendTo  the buffer to append to
    565     * @param pos  the position - ignored
    566     * @return the buffer passed in
    567     */
    568    @Override
    569    public StringBuffer format(final Object obj, final StringBuffer toAppendTo, final FieldPosition pos) {
    570        return toAppendTo.append(printer.format(obj));
    571    }
    572
    573    /**
    574     * Gets the locale used by this formatter.
    575     *
    576     * @return the locale
    577     */
    578    @Override
    579    public Locale getLocale() {
    580        return printer.getLocale();
    581    }
    582
    583    /**
    584     * Gets an estimate for the maximum string length that the
    585     * formatter will produce.
    586     *
    587     * <p>The actual formatted length will almost always be less than or
    588     * equal to this amount.</p>
    589     *
    590     * @return the maximum formatted length
    591     */
    592    public int getMaxLengthEstimate() {
    593        return printer.getMaxLengthEstimate();
    594    }
    595
    596    // Accessors
    597    /**
    598     * Gets the pattern used by this formatter.
    599     *
    600     * @return the pattern, {@link java.text.SimpleDateFormat} compatible
    601     */
    602    @Override
    603    public String getPattern() {
    604        return printer.getPattern();
    605    }
    606
    607    /**
    608     * Gets the time zone used by this formatter.
    609     *
    610     * <p>This zone is always used for {@link Date} formatting.</p>
    611     *
    612     * @return the time zone
    613     */
    614    @Override
    615    public TimeZone getTimeZone() {
    616        return printer.getTimeZone();
    617    }
    618
    619    /**
    620     * Returns a hash code compatible with equals.
    621     *
    622     * @return a hash code compatible with equals
    623     */
    624    @Override
    625    public int hashCode() {
    626        return printer.hashCode();
    627    }
    628
    629    /* (non-Javadoc)
    630     * @see DateParser#parse(String)
    631     */
    632    @Override
    633    public Date parse(final String source) throws ParseException {
    634        return parser.parse(source);
    635    }
    636
    637    /* (non-Javadoc)
    638     * @see DateParser#parse(String, java.text.ParsePosition)
    639     */
    640    @Override
    641    public Date parse(final String source, final ParsePosition pos) {
    642        return parser.parse(source, pos);
    643    }
    644
    645    /*
    646     * (non-Javadoc)
    647     * @see org.apache.commons.lang3.time.DateParser#parse(String, java.text.ParsePosition, java.util.Calendar)
    648     */
    649    @Override
    650    public boolean parse(final String source, final ParsePosition pos, final Calendar calendar) {
    651        return parser.parse(source, pos, calendar);
    652    }
    653
    654    /* (non-Javadoc)
    655     * @see java.text.Format#parseObject(String, java.text.ParsePosition)
    656     */
    657    @Override
    658    public Object parseObject(final String source, final ParsePosition pos) {
    659        return parser.parseObject(source, pos);
    660    }
    661
    662    /**
    663     * Gets a debugging string version of this formatter.
    664     *
    665     * @return a debugging string
    666     */
    667    @Override
    668    public String toString() {
    669        return "FastDateFormat[" + printer.getPattern() + "," + printer.getLocale() + "," + printer.getTimeZone().getID() + "]";
    670    }
    671}