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