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