    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 FormatCache<FastDateFormat> cache = new FormatCache<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    /** Our fast printer. */
    112    private final FastDatePrinter printer;
    113
    114    /** Our fast parser. */
    115    private final FastDateParser parser;
    116
    117    /**
    118     * Gets a formatter instance using the default pattern in the
    119     * default locale.
    120     *
    121     * @return a date/time formatter
    122     */
    123    public static FastDateFormat getInstance() {
    124        return cache.getInstance();
    125    }
    126
    127    /**
    128     * Gets a formatter instance using the specified pattern in the
    129     * default locale.
    130     *
    131     * @param pattern  {@link java.text.SimpleDateFormat} compatible
    132     *  pattern
    133     * @return a pattern based date/time formatter
    134     * @throws IllegalArgumentException if pattern is invalid
    135     */
    136    public static FastDateFormat getInstance(final String pattern) {
    137        return cache.getInstance(pattern, null, null);
    138    }
    139
    140    /**
    141     * Gets a formatter instance using the specified pattern and
    142     * time zone.
    143     *
    144     * @param pattern  {@link java.text.SimpleDateFormat} compatible
    145     *  pattern
    146     * @param timeZone  optional time zone, overrides time zone of
    147     *  formatted date
    148     * @return a pattern based date/time formatter
    149     * @throws IllegalArgumentException if pattern is invalid
    150     */
    151    public static FastDateFormat getInstance(final String pattern, final TimeZone timeZone) {
    152        return cache.getInstance(pattern, timeZone, null);
    153    }
    154
    155    /**
    156     * Gets a formatter instance using the specified pattern and
    157     * locale.
    158     *
    159     * @param pattern  {@link java.text.SimpleDateFormat} compatible
    160     *  pattern
    161     * @param locale  optional locale, overrides system locale
    162     * @return a pattern based date/time formatter
    163     * @throws IllegalArgumentException if pattern is invalid
    164     */
    165    public static FastDateFormat getInstance(final String pattern, final Locale locale) {
    166        return cache.getInstance(pattern, null, locale);
    167    }
    168
    169    /**
    170     * Gets a formatter instance using the specified pattern, time zone
    171     * and locale.
    172     *
    173     * @param pattern  {@link java.text.SimpleDateFormat} compatible
    174     *  pattern
    175     * @param timeZone  optional time zone, overrides time zone of
    176     *  formatted date
    177     * @param locale  optional locale, overrides system locale
    178     * @return a pattern based date/time formatter
    179     * @throws IllegalArgumentException if pattern is invalid
    180     *  or {@code null}
    181     */
    182    public static FastDateFormat getInstance(final String pattern, final TimeZone timeZone, final Locale locale) {
    183        return cache.getInstance(pattern, timeZone, locale);
    184    }
    185
    186    /**
    187     * Gets a date formatter instance using the specified style in the
    188     * default time zone and locale.
    189     *
    190     * @param style  date style: FULL, LONG, MEDIUM, or SHORT
    191     * @return a localized standard date formatter
    192     * @throws IllegalArgumentException if the Locale has no date
    193     *  pattern defined
    194     * @since 2.1
    195     */
    196    public static FastDateFormat getDateInstance(final int style) {
    197        return cache.getDateInstance(style, null, null);
    198    }
    199
    200    /**
    201     * Gets a date formatter instance using the specified style and
    202     * locale in the default time zone.
    203     *
    204     * @param style  date style: FULL, LONG, MEDIUM, or SHORT
    205     * @param locale  optional locale, overrides system locale
    206     * @return a localized standard date formatter
    207     * @throws IllegalArgumentException if the Locale has no date
    208     *  pattern defined
    209     * @since 2.1
    210     */
    211    public static FastDateFormat getDateInstance(final int style, final Locale locale) {
    212        return cache.getDateInstance(style, null, locale);
    213    }
    214
    215    /**
    216     * Gets a date formatter instance using the specified style and
    217     * time zone in the default locale.
    218     *
    219     * @param style  date style: FULL, LONG, MEDIUM, or SHORT
    220     * @param timeZone  optional time zone, overrides time zone of
    221     *  formatted date
    222     * @return a localized standard date formatter
    223     * @throws IllegalArgumentException if the Locale has no date
    224     *  pattern defined
    225     * @since 2.1
    226     */
    227    public static FastDateFormat getDateInstance(final int style, final TimeZone timeZone) {
    228        return cache.getDateInstance(style, timeZone, null);
    229    }
    230
    231    /**
    232     * Gets a date formatter instance using the specified style, time
    233     * zone and locale.
    234     *
    235     * @param style  date style: FULL, LONG, MEDIUM, or SHORT
    236     * @param timeZone  optional time zone, overrides time zone of
    237     *  formatted date
    238     * @param locale  optional locale, overrides system locale
    239     * @return a localized standard date formatter
    240     * @throws IllegalArgumentException if the Locale has no date
    241     *  pattern defined
    242     */
    243    public static FastDateFormat getDateInstance(final int style, final TimeZone timeZone, final Locale locale) {
    244        return cache.getDateInstance(style, timeZone, locale);
    245    }
    246
    247    /**
    248     * Gets a time formatter instance using the specified style in the
    249     * default time zone and locale.
    250     *
    251     * @param style  time style: FULL, LONG, MEDIUM, or SHORT
    252     * @return a localized standard time formatter
    253     * @throws IllegalArgumentException if the Locale has no time
    254     *  pattern defined
    255     * @since 2.1
    256     */
    257    public static FastDateFormat getTimeInstance(final int style) {
    258        return cache.getTimeInstance(style, null, null);
    259    }
    260
    261    /**
    262     * Gets a time formatter instance using the specified style and
    263     * locale in the default time zone.
    264     *
    265     * @param style  time style: FULL, LONG, MEDIUM, or SHORT
    266     * @param locale  optional locale, overrides system locale
    267     * @return a localized standard time formatter
    268     * @throws IllegalArgumentException if the Locale has no time
    269     *  pattern defined
    270     * @since 2.1
    271     */
    272    public static FastDateFormat getTimeInstance(final int style, final Locale locale) {
    273        return cache.getTimeInstance(style, null, locale);
    274    }
    275
    276    /**
    277     * Gets a time formatter instance using the specified style and
    278     * time zone in the default locale.
    279     *
    280     * @param style  time style: FULL, LONG, MEDIUM, or SHORT
    281     * @param timeZone  optional time zone, overrides time zone of
    282     *  formatted time
    283     * @return a localized standard time formatter
    284     * @throws IllegalArgumentException if the Locale has no time
    285     *  pattern defined
    286     * @since 2.1
    287     */
    288    public static FastDateFormat getTimeInstance(final int style, final TimeZone timeZone) {
    289        return cache.getTimeInstance(style, timeZone, null);
    290    }
    291
    292    /**
    293     * Gets a time formatter instance using the specified style, time
    294     * zone and locale.
    295     *
    296     * @param style  time style: FULL, LONG, MEDIUM, or SHORT
    297     * @param timeZone  optional time zone, overrides time zone of
    298     *  formatted time
    299     * @param locale  optional locale, overrides system locale
    300     * @return a localized standard time formatter
    301     * @throws IllegalArgumentException if the Locale has no time
    302     *  pattern defined
    303     */
    304    public static FastDateFormat getTimeInstance(final int style, final TimeZone timeZone, final Locale locale) {
    305        return cache.getTimeInstance(style, timeZone, locale);
    306    }
    307
    308    /**
    309     * Gets a date/time formatter instance using the specified style
    310     * in the default time zone and locale.
    311     *
    312     * @param dateStyle  date style: FULL, LONG, MEDIUM, or SHORT
    313     * @param timeStyle  time style: FULL, LONG, MEDIUM, or SHORT
    314     * @return a localized standard date/time formatter
    315     * @throws IllegalArgumentException if the Locale has no date/time
    316     *  pattern defined
    317     * @since 2.1
    318     */
    319    public static FastDateFormat getDateTimeInstance(final int dateStyle, final int timeStyle) {
    320        return cache.getDateTimeInstance(dateStyle, timeStyle, null, null);
    321    }
    322
    323    /**
    324     * Gets a date/time formatter instance using the specified style and
    325     * locale in the default time zone.
    326     *
    327     * @param dateStyle  date style: FULL, LONG, MEDIUM, or SHORT
    328     * @param timeStyle  time style: FULL, LONG, MEDIUM, or SHORT
    329     * @param locale  optional locale, overrides system locale
    330     * @return a localized standard date/time formatter
    331     * @throws IllegalArgumentException if the Locale has no date/time
    332     *  pattern defined
    333     * @since 2.1
    334     */
    335    public static FastDateFormat getDateTimeInstance(final int dateStyle, final int timeStyle, final Locale locale) {
    336        return cache.getDateTimeInstance(dateStyle, timeStyle, null, locale);
    337    }
    338
    339    /**
    340     * Gets a date/time formatter instance using the specified style and
    341     * time zone in the default locale.
    342     *
    343     * @param dateStyle  date style: FULL, LONG, MEDIUM, or SHORT
    344     * @param timeStyle  time style: FULL, LONG, MEDIUM, or SHORT
    345     * @param timeZone  optional time zone, overrides time zone of
    346     *  formatted date
    347     * @return a localized standard date/time formatter
    348     * @throws IllegalArgumentException if the Locale has no date/time
    349     *  pattern defined
    350     * @since 2.1
    351     */
    352    public static FastDateFormat getDateTimeInstance(final int dateStyle, final int timeStyle, final TimeZone timeZone) {
    353        return getDateTimeInstance(dateStyle, timeStyle, timeZone, null);
    354    }
    355    /**
    356     * Gets a date/time formatter instance using the specified style,
    357     * time zone and locale.
    358     *
    359     * @param dateStyle  date style: FULL, LONG, MEDIUM, or SHORT
    360     * @param timeStyle  time style: FULL, LONG, MEDIUM, or SHORT
    361     * @param timeZone  optional time zone, overrides time zone of
    362     *  formatted date
    363     * @param locale  optional locale, overrides system locale
    364     * @return a localized standard date/time formatter
    365     * @throws IllegalArgumentException if the Locale has no date/time
    366     *  pattern defined
    367     */
    368    public static FastDateFormat getDateTimeInstance(
    369            final int dateStyle, final int timeStyle, final TimeZone timeZone, final Locale locale) {
    370        return cache.getDateTimeInstance(dateStyle, timeStyle, timeZone, locale);
    371    }
    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    // Format methods
    402    /**
    403     * Formats a {@link Date}, {@link Calendar} or
    404     * {@link Long} (milliseconds) object.
    405     * This method is an implementation of {@link Format#format(Object, StringBuffer, FieldPosition)}
    406     *
    407     * @param obj  the object to format
    408     * @param toAppendTo  the buffer to append to
    409     * @param pos  the position - ignored
    410     * @return the buffer passed in
    411     */
    412    @Override
    413    public StringBuffer format(final Object obj, final StringBuffer toAppendTo, final FieldPosition pos) {
    414        return toAppendTo.append(printer.format(obj));
    415    }
    416
    417    /**
    418     * Formats a millisecond {@code long} value.
    419     *
    420     * @param millis  the millisecond value to format
    421     * @return the formatted string
    422     * @since 2.1
    423     */
    424    @Override
    425    public String format(final long millis) {
    426        return printer.format(millis);
    427    }
    428
    429    /**
    430     * Formats a {@link Date} object using a {@link GregorianCalendar}.
    431     *
    432     * @param date  the date to format
    433     * @return the formatted string
    434     */
    435    @Override
    436    public String format(final Date date) {
    437        return printer.format(date);
    438    }
    439
    440    /**
    441     * Formats a {@link Calendar} object.
    442     *
    443     * @param calendar  the calendar to format
    444     * @return the formatted string
    445     */
    446    @Override
    447    public String format(final Calendar calendar) {
    448        return printer.format(calendar);
    449    }
    450
    451    /**
    452     * Formats a millisecond {@code long} value into the
    453     * supplied {@link StringBuffer}.
    454     *
    455     * @param millis  the millisecond value to format
    456     * @param buf  the buffer to format into
    457     * @return the specified string buffer
    458     * @since 2.1
    459     * @deprecated Use {{@link #format(long, Appendable)}.
    460     */
    461    @Deprecated
    462    @Override
    463    public StringBuffer format(final long millis, final StringBuffer buf) {
    464        return printer.format(millis, buf);
    465    }
    466
    467    /**
    468     * Formats a {@link Date} object into the
    469     * supplied {@link StringBuffer} using a {@link GregorianCalendar}.
    470     *
    471     * @param date  the date to format
    472     * @param buf  the buffer to format into
    473     * @return the specified string buffer
    474     * @deprecated Use {{@link #format(Date, Appendable)}.
    475     */
    476    @Deprecated
    477    @Override
    478    public StringBuffer format(final Date date, final StringBuffer buf) {
    479        return printer.format(date, buf);
    480    }
    481
    482    /**
    483     * Formats a {@link Calendar} object into the
    484     * supplied {@link StringBuffer}.
    485     *
    486     * @param calendar  the calendar to format
    487     * @param buf  the buffer to format into
    488     * @return the specified string buffer
    489     * @deprecated Use {{@link #format(Calendar, Appendable)}.
    490     */
    491    @Deprecated
    492    @Override
    493    public StringBuffer format(final Calendar calendar, final StringBuffer buf) {
    494        return printer.format(calendar, buf);
    495    }
    496
    497    /**
    498     * Formats a millisecond {@code long} value into the
    499     * supplied {@link StringBuffer}.
    500     *
    501     * @param millis  the millisecond value to format
    502     * @param buf  the buffer to format into
    503     * @return the specified string buffer
    504     * @since 3.5
    505     */
    506    @Override
    507    public <B extends Appendable> B format(final long millis, final B buf) {
    508        return printer.format(millis, buf);
    509    }
    510
    511    /**
    512     * Formats a {@link Date} object into the
    513     * supplied {@link StringBuffer} using a {@link GregorianCalendar}.
    514     *
    515     * @param date  the date to format
    516     * @param buf  the buffer to format into
    517     * @return the specified string buffer
    518     * @since 3.5
    519     */
    520    @Override
    521    public <B extends Appendable> B format(final Date date, final B buf) {
    522        return printer.format(date, buf);
    523    }
    524
    525    /**
    526     * Formats a {@link Calendar} object into the
    527     * supplied {@link StringBuffer}.
    528     *
    529     * @param calendar  the calendar to format
    530     * @param buf  the buffer to format into
    531     * @return the specified string buffer
    532     * @since 3.5
    533    */
    534    @Override
    535    public <B extends Appendable> B format(final Calendar calendar, final B buf) {
    536        return printer.format(calendar, buf);
    537    }
    538
    539    // Parsing
    540
    541
    542    /* (non-Javadoc)
    543     * @see DateParser#parse(String)
    544     */
    545    @Override
    546    public Date parse(final String source) throws ParseException {
    547        return parser.parse(source);
    548    }
    549
    550    /* (non-Javadoc)
    551     * @see DateParser#parse(String, java.text.ParsePosition)
    552     */
    553    @Override
    554    public Date parse(final String source, final ParsePosition pos) {
    555        return parser.parse(source, pos);
    556    }
    557
    558    /*
    559     * (non-Javadoc)
    560     * @see org.apache.commons.lang3.time.DateParser#parse(String, java.text.ParsePosition, java.util.Calendar)
    561     */
    562    @Override
    563    public boolean parse(final String source, final ParsePosition pos, final Calendar calendar) {
    564        return parser.parse(source, pos, calendar);
    565    }
    566
    567    /* (non-Javadoc)
    568     * @see java.text.Format#parseObject(String, java.text.ParsePosition)
    569     */
    570    @Override
    571    public Object parseObject(final String source, final ParsePosition pos) {
    572        return parser.parseObject(source, pos);
    573    }
    574
    575    // Accessors
    576    /**
    577     * Gets the pattern used by this formatter.
    578     *
    579     * @return the pattern, {@link java.text.SimpleDateFormat} compatible
    580     */
    581    @Override
    582    public String getPattern() {
    583        return printer.getPattern();
    584    }
    585
    586    /**
    587     * Gets the time zone used by this formatter.
    588     *
    589     * <p>This zone is always used for {@link Date} formatting.</p>
    590     *
    591     * @return the time zone
    592     */
    593    @Override
    594    public TimeZone getTimeZone() {
    595        return printer.getTimeZone();
    596    }
    597
    598    /**
    599     * Gets the locale used by this formatter.
    600     *
    601     * @return the locale
    602     */
    603    @Override
    604    public Locale getLocale() {
    605        return printer.getLocale();
    606    }
    607
    608    /**
    609     * Gets an estimate for the maximum string length that the
    610     * formatter will produce.
    611     *
    612     * <p>The actual formatted length will almost always be less than or
    613     * equal to this amount.</p>
    614     *
    615     * @return the maximum formatted length
    616     */
    617    public int getMaxLengthEstimate() {
    618        return printer.getMaxLengthEstimate();
    619    }
    620
    621    // Basics
    622    /**
    623     * Compares two objects for equality.
    624     *
    625     * @param obj  the object to compare to
    626     * @return {@code true} if equal
    627     */
    628    @Override
    629    public boolean equals(final Object obj) {
    630        if (!(obj instanceof FastDateFormat)) {
    631            return false;
    632        }
    633        final FastDateFormat other = (FastDateFormat) obj;
    634        // no need to check parser, as it has same invariants as printer
    635        return printer.equals(other.printer);
    636    }
    637
    638    /**
    639     * Returns a hash code compatible with equals.
    640     *
    641     * @return a hash code compatible with equals
    642     */
    643    @Override
    644    public int hashCode() {
    645        return printer.hashCode();
    646    }
    647
    648    /**
    649     * Gets a debugging string version of this formatter.
    650     *
    651     * @return a debugging string
    652     */
    653    @Override
    654    public String toString() {
    655        return "FastDateFormat[" + printer.getPattern() + "," + printer.getLocale() + "," + printer.getTimeZone().getID() + "]";
    656    }
    657
    658    /**
    659     * Performs the formatting by applying the rules to the
    660     * specified calendar.
    661     *
    662     * @param calendar the calendar to format
    663     * @param buf  the buffer to format into
    664     * @return the specified string buffer
    665     * @deprecated Use {@link #format(Calendar, Appendable)}
    666     */
    667    @Deprecated
    668    protected StringBuffer applyRules(final Calendar calendar, final StringBuffer buf) {
    669        return printer.applyRules(calendar, buf);
    670    }
    671}