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}