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}