    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     * Formats a millisecond {@code long} value into the
    542     * supplied {@link StringBuffer}.
    543     *
    544     * @param millis  the millisecond value to format
    545     * @param buf  the buffer to format into
    546     * @return the specified string buffer
    547     * @since 2.1
    548     * @deprecated Use {{@link #format(long, Appendable)}.
    549     */
    550    @Deprecated
    551    @Override
    552    public StringBuffer format(final long millis, final StringBuffer buf) {
    553        return printer.format(millis, buf);
    554    }
    555
    556    // Format methods
    557    /**
    558     * Formats a {@link Date}, {@link Calendar} or
    559     * {@link Long} (milliseconds) object.
    560     * This method is an implementation of {@link Format#format(Object, StringBuffer, FieldPosition)}
    561     *
    562     * @param obj  the object to format
    563     * @param toAppendTo  the buffer to append to
    564     * @param pos  the position - ignored
    565     * @return the buffer passed in
    566     */
    567    @Override
    568    public StringBuffer format(final Object obj, final StringBuffer toAppendTo, final FieldPosition pos) {
    569        return toAppendTo.append(printer.format(obj));
    570    }
    571
    572    /**
    573     * Gets the locale used by this formatter.
    574     *
    575     * @return the locale
    576     */
    577    @Override
    578    public Locale getLocale() {
    579        return printer.getLocale();
    580    }
    581
    582    /**
    583     * Gets an estimate for the maximum string length that the
    584     * formatter will produce.
    585     *
    586     * <p>The actual formatted length will almost always be less than or
    587     * equal to this amount.</p>
    588     *
    589     * @return the maximum formatted length
    590     */
    591    public int getMaxLengthEstimate() {
    592        return printer.getMaxLengthEstimate();
    593    }
    594
    595    // Accessors
    596    /**
    597     * Gets the pattern used by this formatter.
    598     *
    599     * @return the pattern, {@link java.text.SimpleDateFormat} compatible
    600     */
    601    @Override
    602    public String getPattern() {
    603        return printer.getPattern();
    604    }
    605
    606    /**
    607     * Gets the time zone used by this formatter.
    608     *
    609     * <p>This zone is always used for {@link Date} formatting.</p>
    610     *
    611     * @return the time zone
    612     */
    613    @Override
    614    public TimeZone getTimeZone() {
    615        return printer.getTimeZone();
    616    }
    617
    618    /**
    619     * Returns a hash code compatible with equals.
    620     *
    621     * @return a hash code compatible with equals
    622     */
    623    @Override
    624    public int hashCode() {
    625        return printer.hashCode();
    626    }
    627
    628    /* (non-Javadoc)
    629     * @see DateParser#parse(String)
    630     */
    631    @Override
    632    public Date parse(final String source) throws ParseException {
    633        return parser.parse(source);
    634    }
    635
    636    /* (non-Javadoc)
    637     * @see DateParser#parse(String, java.text.ParsePosition)
    638     */
    639    @Override
    640    public Date parse(final String source, final ParsePosition pos) {
    641        return parser.parse(source, pos);
    642    }
    643
    644    /*
    645     * (non-Javadoc)
    646     * @see org.apache.commons.lang3.time.DateParser#parse(String, java.text.ParsePosition, java.util.Calendar)
    647     */
    648    @Override
    649    public boolean parse(final String source, final ParsePosition pos, final Calendar calendar) {
    650        return parser.parse(source, pos, calendar);
    651    }
    652
    653    /* (non-Javadoc)
    654     * @see java.text.Format#parseObject(String, java.text.ParsePosition)
    655     */
    656    @Override
    657    public Object parseObject(final String source, final ParsePosition pos) {
    658        return parser.parseObject(source, pos);
    659    }
    660
    661    /**
    662     * Gets a debugging string version of this formatter.
    663     *
    664     * @return a debugging string
    665     */
    666    @Override
    667    public String toString() {
    668        return "FastDateFormat[" + printer.getPattern() + "," + printer.getLocale() + "," + printer.getTimeZone().getID() + "]";
    669    }
    670}