1 /*
2 * Licensed to the Apache Software Foundation (ASF) under one or more
3 * contributor license agreements. See the NOTICE file distributed with
4 * this work for additional information regarding copyright ownership.
5 * The ASF licenses this file to You under the Apache License, Version 2.0
6 * (the "License"); you may not use this file except in compliance with
7 * the License. You may obtain a copy of the License at
8 *
9 * https://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17
18 package org.apache.commons.lang3.time;
19
20 import java.text.DateFormat;
21 import java.text.FieldPosition;
22 import java.text.Format;
23 import java.text.ParseException;
24 import java.text.ParsePosition;
25 import java.text.SimpleDateFormat;
26 import java.util.Calendar;
27 import java.util.Date;
28 import java.util.GregorianCalendar;
29 import java.util.Locale;
30 import java.util.TimeZone;
31
32 /**
33 * FastDateFormat is a fast and thread-safe version of {@link java.text.SimpleDateFormat}.
34 *
35 * <p>
36 * To obtain an instance of FastDateFormat, use one of the static factory methods: {@link #getInstance(String, TimeZone, Locale)},
37 * {@link #getDateInstance(int, TimeZone, Locale)}, {@link #getTimeInstance(int, TimeZone, Locale)}, or {@link #getDateTimeInstance(int, int, TimeZone, Locale)}
38 * </p>
39 *
40 * <p>
41 * Since FastDateFormat is thread safe, you can use a static member instance:
42 * </p>
43 * {@code
44 * private static final FastDateFormat DATE_FORMATTER = FastDateFormat.getDateTimeInstance(FastDateFormat.LONG, FastDateFormat.SHORT);
45 * }
46 *
47 * <p>
48 * This class can be used as a direct replacement to {@link SimpleDateFormat} in most formatting and parsing situations. This class is especially useful in
49 * 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.
50 * </p>
51 *
52 * <p>
53 * Note on memory retention: unlike {@code new SimpleDateFormat(pattern)}, instances obtained from the static factory methods are held in a static cache keyed
54 * by (pattern, time zone, locale). The cache is bounded (it is flushed when it exceeds an internal limit) and can be flushed explicitly with
55 * {@link #clear()}, but each distinct key retains its instance for the lifetime of the JVM until then. Prefer fixed, application-defined patterns; do not
56 * pass unvalidated caller-supplied pattern, time zone, or locale values to the factory methods.
57 * </p>
58 *
59 * <p>
60 * All patterns are compatible with SimpleDateFormat (except time zones and some year patterns - see below).
61 * </p>
62 *
63 * <p>
64 * Since 3.2, FastDateFormat supports parsing as well as printing.
65 * </p>
66 *
67 * <p>
68 * 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
69 * be used here (on all JDK versions).
70 * </p>
71 *
72 * <p>
73 * 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
74 * a minor incompatibility with Java 1.4, but at a gain of useful functionality.
75 * </p>
76 *
77 * <p>
78 * 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
79 * 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
80 * implements the behavior of Java 7.
81 * </p>
82 *
83 * @since 2.0
84 */
85 public class FastDateFormat extends Format implements DateParser, DatePrinter {
86
87 /**
88 * Required for serialization support.
89 *
90 * @see java.io.Serializable
91 */
92 private static final long serialVersionUID = 2L;
93
94 /**
95 * FULL locale dependent date or time style.
96 */
97 public static final int FULL = DateFormat.FULL;
98
99 /**
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 }