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 * }
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}