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