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.util.Calendar;
025import java.util.Date;
026import java.util.Locale;
027import java.util.TimeZone;
028
029/**
030 * <p>FastDateFormat is a fast and thread-safe version of
031 * {@link java.text.SimpleDateFormat}.</p>
032 *
033 * <p>To obtain an instance of FastDateFormat, use one of the static factory methods: 
034 * {@link #getInstance(String, TimeZone, Locale)}, {@link #getDateInstance(int, TimeZone, Locale)}, 
035 * {@link #getTimeInstance(int, TimeZone, Locale)}, or {@link #getDateTimeInstance(int, int, TimeZone, Locale)} 
036 * </p>
037 * 
038 * <p>Since FastDateFormat is thread safe, you can use a static member instance:</p>
039 * <code>
040 *   private static final FastDateFormat DATE_FORMATTER = FastDateFormat.getDateTimeInstance(FastDateFormat.LONG, FastDateFormat.SHORT);
041 * </code>
042 * 
043 * <p>This class can be used as a direct replacement to
044 * {@code SimpleDateFormat} in most formatting and parsing situations.
045 * This class is especially useful in multi-threaded server environments.
046 * {@code SimpleDateFormat} is not thread-safe in any JDK version,
047 * nor will it be as Sun have closed the bug/RFE.
048 * </p>
049 *
050 * <p>All patterns are compatible with
051 * SimpleDateFormat (except time zones and some year patterns - see below).</p>
052 *
053 * <p>Since 3.2, FastDateFormat supports parsing as well as printing.</p>
054 *
055 * <p>Java 1.4 introduced a new pattern letter, {@code 'Z'}, to represent
056 * time zones in RFC822 format (eg. {@code +0800} or {@code -1100}).
057 * This pattern letter can be used here (on all JDK versions).</p>
058 *
059 * <p>In addition, the pattern {@code 'ZZ'} has been made to represent
060 * ISO 8601 full format time zones (eg. {@code +08:00} or {@code -11:00}).
061 * This introduces a minor incompatibility with Java 1.4, but at a gain of
062 * useful functionality.</p>
063 *
064 * <p>Javadoc cites for the year pattern: <i>For formatting, if the number of
065 * pattern letters is 2, the year is truncated to 2 digits; otherwise it is
066 * interpreted as a number.</i> Starting with Java 1.7 a pattern of 'Y' or
067 * 'YYY' will be formatted as '2003', while it was '03' in former Java
068 * versions. FastDateFormat implements the behavior of Java 7.</p>
069 *
070 * @since 2.0
071 * @version $Id: FastDateFormat.java 1591488 2014-04-30 21:49:35Z ggregory $
072 */
073public class FastDateFormat extends Format implements DateParser, DatePrinter {
074    /**
075     * Required for serialization support.
076     *
077     * @see java.io.Serializable
078     */
079    private static final long serialVersionUID = 2L;
080
081    /**
082     * FULL locale dependent date or time style.
083     */
084    public static final int FULL = DateFormat.FULL;
085    /**
086     * LONG locale dependent date or time style.
087     */
088    public static final int LONG = DateFormat.LONG;
089    /**
090     * MEDIUM locale dependent date or time style.
091     */
092    public static final int MEDIUM = DateFormat.MEDIUM;
093    /**
094     * SHORT locale dependent date or time style.
095     */
096    public static final int SHORT = DateFormat.SHORT;
097
098    private static final FormatCache<FastDateFormat> cache= new FormatCache<FastDateFormat>() {
099        @Override
100        protected FastDateFormat createInstance(final String pattern, final TimeZone timeZone, final Locale locale) {
101            return new FastDateFormat(pattern, timeZone, locale);
102        }
103    };
104
105    private final FastDatePrinter printer;
106    private final FastDateParser parser;
107
108    //-----------------------------------------------------------------------
109    /**
110     * <p>Gets a formatter instance using the default pattern in the
111     * default locale.</p>
112     *
113     * @return a date/time formatter
114     */
115    public static FastDateFormat getInstance() {
116        return cache.getInstance();
117    }
118
119    /**
120     * <p>Gets a formatter instance using the specified pattern in the
121     * default locale.</p>
122     *
123     * @param pattern  {@link java.text.SimpleDateFormat} compatible
124     *  pattern
125     * @return a pattern based date/time formatter
126     * @throws IllegalArgumentException if pattern is invalid
127     */
128    public static FastDateFormat getInstance(final String pattern) {
129        return cache.getInstance(pattern, null, null);
130    }
131
132    /**
133     * <p>Gets a formatter instance using the specified pattern and
134     * time zone.</p>
135     *
136     * @param pattern  {@link java.text.SimpleDateFormat} compatible
137     *  pattern
138     * @param timeZone  optional time zone, overrides time zone of
139     *  formatted date
140     * @return a pattern based date/time formatter
141     * @throws IllegalArgumentException if pattern is invalid
142     */
143    public static FastDateFormat getInstance(final String pattern, final TimeZone timeZone) {
144        return cache.getInstance(pattern, timeZone, null);
145    }
146
147    /**
148     * <p>Gets a formatter instance using the specified pattern and
149     * locale.</p>
150     *
151     * @param pattern  {@link java.text.SimpleDateFormat} compatible
152     *  pattern
153     * @param locale  optional locale, overrides system locale
154     * @return a pattern based date/time formatter
155     * @throws IllegalArgumentException if pattern is invalid
156     */
157    public static FastDateFormat getInstance(final String pattern, final Locale locale) {
158        return cache.getInstance(pattern, null, locale);
159    }
160
161    /**
162     * <p>Gets a formatter instance using the specified pattern, time zone
163     * and locale.</p>
164     *
165     * @param pattern  {@link java.text.SimpleDateFormat} compatible
166     *  pattern
167     * @param timeZone  optional time zone, overrides time zone of
168     *  formatted date
169     * @param locale  optional locale, overrides system locale
170     * @return a pattern based date/time formatter
171     * @throws IllegalArgumentException if pattern is invalid
172     *  or {@code null}
173     */
174    public static FastDateFormat getInstance(final String pattern, final TimeZone timeZone, final Locale locale) {
175        return cache.getInstance(pattern, timeZone, locale);
176    }
177
178    //-----------------------------------------------------------------------
179    /**
180     * <p>Gets a date formatter instance using the specified style in the
181     * default time zone and locale.</p>
182     *
183     * @param style  date style: FULL, LONG, MEDIUM, or SHORT
184     * @return a localized standard date formatter
185     * @throws IllegalArgumentException if the Locale has no date
186     *  pattern defined
187     * @since 2.1
188     */
189    public static FastDateFormat getDateInstance(final int style) {
190        return cache.getDateInstance(style, null, null);
191    }
192
193    /**
194     * <p>Gets a date formatter instance using the specified style and
195     * locale in the default time zone.</p>
196     *
197     * @param style  date style: FULL, LONG, MEDIUM, or SHORT
198     * @param locale  optional locale, overrides system locale
199     * @return a localized standard date formatter
200     * @throws IllegalArgumentException if the Locale has no date
201     *  pattern defined
202     * @since 2.1
203     */
204    public static FastDateFormat getDateInstance(final int style, final Locale locale) {
205        return cache.getDateInstance(style, null, locale);
206    }
207
208    /**
209     * <p>Gets a date formatter instance using the specified style and
210     * time zone in the default locale.</p>
211     *
212     * @param style  date style: FULL, LONG, MEDIUM, or SHORT
213     * @param timeZone  optional time zone, overrides time zone of
214     *  formatted date
215     * @return a localized standard date formatter
216     * @throws IllegalArgumentException if the Locale has no date
217     *  pattern defined
218     * @since 2.1
219     */
220    public static FastDateFormat getDateInstance(final int style, final TimeZone timeZone) {
221        return cache.getDateInstance(style, timeZone, null);
222    }
223
224    /**
225     * <p>Gets a date formatter instance using the specified style, time
226     * zone and locale.</p>
227     *
228     * @param style  date style: FULL, LONG, MEDIUM, or SHORT
229     * @param timeZone  optional time zone, overrides time zone of
230     *  formatted date
231     * @param locale  optional locale, overrides system locale
232     * @return a localized standard date formatter
233     * @throws IllegalArgumentException if the Locale has no date
234     *  pattern defined
235     */
236    public static FastDateFormat getDateInstance(final int style, final TimeZone timeZone, final Locale locale) {
237        return cache.getDateInstance(style, timeZone, locale);
238    }
239
240    //-----------------------------------------------------------------------
241    /**
242     * <p>Gets a time formatter instance using the specified style in the
243     * default time zone and locale.</p>
244     *
245     * @param style  time style: FULL, LONG, MEDIUM, or SHORT
246     * @return a localized standard time formatter
247     * @throws IllegalArgumentException if the Locale has no time
248     *  pattern defined
249     * @since 2.1
250     */
251    public static FastDateFormat getTimeInstance(final int style) {
252        return cache.getTimeInstance(style, null, null);
253    }
254
255    /**
256     * <p>Gets a time formatter instance using the specified style and
257     * locale in the default time zone.</p>
258     *
259     * @param style  time style: FULL, LONG, MEDIUM, or SHORT
260     * @param locale  optional locale, overrides system locale
261     * @return a localized standard time formatter
262     * @throws IllegalArgumentException if the Locale has no time
263     *  pattern defined
264     * @since 2.1
265     */
266    public static FastDateFormat getTimeInstance(final int style, final Locale locale) {
267        return cache.getTimeInstance(style, null, locale);
268    }
269
270    /**
271     * <p>Gets a time formatter instance using the specified style and
272     * time zone in the default locale.</p>
273     *
274     * @param style  time style: FULL, LONG, MEDIUM, or SHORT
275     * @param timeZone  optional time zone, overrides time zone of
276     *  formatted time
277     * @return a localized standard time formatter
278     * @throws IllegalArgumentException if the Locale has no time
279     *  pattern defined
280     * @since 2.1
281     */
282    public static FastDateFormat getTimeInstance(final int style, final TimeZone timeZone) {
283        return cache.getTimeInstance(style, timeZone, null);
284    }
285
286    /**
287     * <p>Gets a time formatter instance using the specified style, time
288     * zone and locale.</p>
289     *
290     * @param style  time style: FULL, LONG, MEDIUM, or SHORT
291     * @param timeZone  optional time zone, overrides time zone of
292     *  formatted time
293     * @param locale  optional locale, overrides system locale
294     * @return a localized standard time formatter
295     * @throws IllegalArgumentException if the Locale has no time
296     *  pattern defined
297     */
298    public static FastDateFormat getTimeInstance(final int style, final TimeZone timeZone, final Locale locale) {
299        return cache.getTimeInstance(style, timeZone, locale);
300    }
301
302    //-----------------------------------------------------------------------
303    /**
304     * <p>Gets a date/time formatter instance using the specified style
305     * in the default time zone and locale.</p>
306     *
307     * @param dateStyle  date style: FULL, LONG, MEDIUM, or SHORT
308     * @param timeStyle  time style: FULL, LONG, MEDIUM, or SHORT
309     * @return a localized standard date/time formatter
310     * @throws IllegalArgumentException if the Locale has no date/time
311     *  pattern defined
312     * @since 2.1
313     */
314    public static FastDateFormat getDateTimeInstance(final int dateStyle, final int timeStyle) {
315        return cache.getDateTimeInstance(dateStyle, timeStyle, null, null);
316    }
317
318    /**
319     * <p>Gets a date/time formatter instance using the specified style and
320     * locale in the default time zone.</p>
321     *
322     * @param dateStyle  date style: FULL, LONG, MEDIUM, or SHORT
323     * @param timeStyle  time style: FULL, LONG, MEDIUM, or SHORT
324     * @param locale  optional locale, overrides system locale
325     * @return a localized standard date/time formatter
326     * @throws IllegalArgumentException if the Locale has no date/time
327     *  pattern defined
328     * @since 2.1
329     */
330    public static FastDateFormat getDateTimeInstance(final int dateStyle, final int timeStyle, final Locale locale) {
331        return cache.getDateTimeInstance(dateStyle, timeStyle, null, locale);
332    }
333
334    /**
335     * <p>Gets a date/time formatter instance using the specified style and
336     * time zone in the default locale.</p>
337     *
338     * @param dateStyle  date style: FULL, LONG, MEDIUM, or SHORT
339     * @param timeStyle  time style: FULL, LONG, MEDIUM, or SHORT
340     * @param timeZone  optional time zone, overrides time zone of
341     *  formatted date
342     * @return a localized standard date/time formatter
343     * @throws IllegalArgumentException if the Locale has no date/time
344     *  pattern defined
345     * @since 2.1
346     */
347    public static FastDateFormat getDateTimeInstance(final int dateStyle, final int timeStyle, final TimeZone timeZone) {
348        return getDateTimeInstance(dateStyle, timeStyle, timeZone, null);
349    }
350    /**
351     * <p>Gets a date/time formatter instance using the specified style,
352     * time zone and locale.</p>
353     *
354     * @param dateStyle  date style: FULL, LONG, MEDIUM, or SHORT
355     * @param timeStyle  time style: FULL, LONG, MEDIUM, or SHORT
356     * @param timeZone  optional time zone, overrides time zone of
357     *  formatted date
358     * @param locale  optional locale, overrides system locale
359     * @return a localized standard date/time formatter
360     * @throws IllegalArgumentException if the Locale has no date/time
361     *  pattern defined
362     */
363    public static FastDateFormat getDateTimeInstance(
364            final int dateStyle, final int timeStyle, final TimeZone timeZone, final Locale locale) {
365        return cache.getDateTimeInstance(dateStyle, timeStyle, timeZone, locale);
366    }
367
368    // Constructor
369    //-----------------------------------------------------------------------
370    /**
371     * <p>Constructs a new FastDateFormat.</p>
372     *
373     * @param pattern  {@link java.text.SimpleDateFormat} compatible pattern
374     * @param timeZone  non-null time zone to use
375     * @param locale  non-null locale to use
376     * @throws NullPointerException if pattern, timeZone, or locale is null.
377     */
378    protected FastDateFormat(final String pattern, final TimeZone timeZone, final Locale locale) {
379        this(pattern, timeZone, locale, null);
380    }
381
382    // Constructor
383    //-----------------------------------------------------------------------
384    /**
385     * <p>Constructs a new FastDateFormat.</p>
386     *
387     * @param pattern  {@link java.text.SimpleDateFormat} compatible pattern
388     * @param timeZone  non-null time zone to use
389     * @param locale  non-null locale to use
390     * @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
391     * @throws NullPointerException if pattern, timeZone, or locale is null.
392     */
393    protected FastDateFormat(final String pattern, final TimeZone timeZone, final Locale locale, final Date centuryStart) {
394        printer= new FastDatePrinter(pattern, timeZone, locale);
395        parser= new FastDateParser(pattern, timeZone, locale, centuryStart);
396    }
397
398    // Format methods
399    //-----------------------------------------------------------------------
400    /**
401     * <p>Formats a {@code Date}, {@code Calendar} or
402     * {@code Long} (milliseconds) object.</p>
403     *
404     * @param obj  the object to format
405     * @param toAppendTo  the buffer to append to
406     * @param pos  the position - ignored
407     * @return the buffer passed in
408     */
409    @Override
410    public StringBuffer format(final Object obj, final StringBuffer toAppendTo, final FieldPosition pos) {
411        return printer.format(obj, toAppendTo, pos);
412    }
413
414    /**
415     * <p>Formats a millisecond {@code long} value.</p>
416     *
417     * @param millis  the millisecond value to format
418     * @return the formatted string
419     * @since 2.1
420     */
421    @Override
422    public String format(final long millis) {
423        return printer.format(millis);
424    }
425
426    /**
427     * <p>Formats a {@code Date} object using a {@code GregorianCalendar}.</p>
428     *
429     * @param date  the date to format
430     * @return the formatted string
431     */
432    @Override
433    public String format(final Date date) {
434        return printer.format(date);
435    }
436
437    /**
438     * <p>Formats a {@code Calendar} object.</p>
439     *
440     * @param calendar  the calendar to format
441     * @return the formatted string
442     */
443    @Override
444    public String format(final Calendar calendar) {
445        return printer.format(calendar);
446    }
447
448    /**
449     * <p>Formats a millisecond {@code long} value into the
450     * supplied {@code StringBuffer}.</p>
451     *
452     * @param millis  the millisecond value to format
453     * @param buf  the buffer to format into
454     * @return the specified string buffer
455     * @since 2.1
456     */
457    @Override
458    public StringBuffer format(final long millis, final StringBuffer buf) {
459        return printer.format(millis, buf);
460    }
461
462    /**
463     * <p>Formats a {@code Date} object into the
464     * supplied {@code StringBuffer} using a {@code GregorianCalendar}.</p>
465     *
466     * @param date  the date to format
467     * @param buf  the buffer to format into
468     * @return the specified string buffer
469     */
470    @Override
471    public StringBuffer format(final Date date, final StringBuffer buf) {
472        return printer.format(date, buf);
473    }
474
475    /**
476     * <p>Formats a {@code Calendar} object into the
477     * supplied {@code StringBuffer}.</p>
478     *
479     * @param calendar  the calendar to format
480     * @param buf  the buffer to format into
481     * @return the specified string buffer
482     */
483    @Override
484    public StringBuffer format(final Calendar calendar, final StringBuffer buf) {
485        return printer.format(calendar, buf);
486    }
487
488    // Parsing
489    //-----------------------------------------------------------------------
490
491
492    /* (non-Javadoc)
493     * @see DateParser#parse(java.lang.String)
494     */
495    @Override
496    public Date parse(final String source) throws ParseException {
497        return parser.parse(source);
498    }
499
500    /* (non-Javadoc)
501     * @see DateParser#parse(java.lang.String, java.text.ParsePosition)
502     */
503    @Override
504    public Date parse(final String source, final ParsePosition pos) {
505            return parser.parse(source, pos);
506    }
507
508    /* (non-Javadoc)
509     * @see java.text.Format#parseObject(java.lang.String, java.text.ParsePosition)
510     */
511    @Override
512    public Object parseObject(final String source, final ParsePosition pos) {
513        return parser.parseObject(source, pos);
514    }
515
516    // Accessors
517    //-----------------------------------------------------------------------
518    /**
519     * <p>Gets the pattern used by this formatter.</p>
520     *
521     * @return the pattern, {@link java.text.SimpleDateFormat} compatible
522     */
523    @Override
524    public String getPattern() {
525        return printer.getPattern();
526    }
527
528    /**
529     * <p>Gets the time zone used by this formatter.</p>
530     *
531     * <p>This zone is always used for {@code Date} formatting. </p>
532     *
533     * @return the time zone
534     */
535    @Override
536    public TimeZone getTimeZone() {
537        return printer.getTimeZone();
538    }
539
540    /**
541     * <p>Gets the locale used by this formatter.</p>
542     *
543     * @return the locale
544     */
545    @Override
546    public Locale getLocale() {
547        return printer.getLocale();
548    }
549
550    /**
551     * <p>Gets an estimate for the maximum string length that the
552     * formatter will produce.</p>
553     *
554     * <p>The actual formatted length will almost always be less than or
555     * equal to this amount.</p>
556     *
557     * @return the maximum formatted length
558     */
559    public int getMaxLengthEstimate() {
560        return printer.getMaxLengthEstimate();
561    }
562
563    // Basics
564    //-----------------------------------------------------------------------
565    /**
566     * <p>Compares two objects for equality.</p>
567     *
568     * @param obj  the object to compare to
569     * @return {@code true} if equal
570     */
571    @Override
572    public boolean equals(final Object obj) {
573        if (obj instanceof FastDateFormat == false) {
574            return false;
575        }
576        final FastDateFormat other = (FastDateFormat) obj;
577        // no need to check parser, as it has same invariants as printer
578        return printer.equals(other.printer);
579    }
580
581    /**
582     * <p>Returns a hashcode compatible with equals.</p>
583     *
584     * @return a hashcode compatible with equals
585     */
586    @Override
587    public int hashCode() {
588        return printer.hashCode();
589    }
590
591    /**
592     * <p>Gets a debugging string version of this formatter.</p>
593     *
594     * @return a debugging string
595     */
596    @Override
597    public String toString() {
598        return "FastDateFormat[" + printer.getPattern() + "," + printer.getLocale() + "," + printer.getTimeZone().getID() + "]";
599    }
600
601
602    /**
603     * <p>Performs the formatting by applying the rules to the
604     * specified calendar.</p>
605     *
606     * @param calendar  the calendar to format
607     * @param buf  the buffer to format into
608     * @return the specified string buffer
609     */
610    protected StringBuffer applyRules(final Calendar calendar, final StringBuffer buf) {
611        return printer.applyRules(calendar, buf);
612    }
613
614
615}