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 */
017 package org.apache.commons.lang3.time;
018
019 import java.io.IOException;
020 import java.io.ObjectInputStream;
021 import java.text.DateFormat;
022 import java.text.DateFormatSymbols;
023 import java.text.FieldPosition;
024 import java.text.Format;
025 import java.text.ParsePosition;
026 import java.util.ArrayList;
027 import java.util.Calendar;
028 import java.util.Date;
029 import java.util.GregorianCalendar;
030 import java.util.List;
031 import java.util.Locale;
032 import java.util.TimeZone;
033 import java.util.concurrent.ConcurrentHashMap;
034 import java.util.concurrent.ConcurrentMap;
035
036 import org.apache.commons.lang3.Validate;
037
038 /**
039 * <p>FastDateFormat is a fast and thread-safe version of
040 * {@link java.text.SimpleDateFormat}.</p>
041 *
042 * <p>This class can be used as a direct replacement to
043 * {@code SimpleDateFormat} in most formatting situations.
044 * This class is especially useful in multi-threaded server environments.
045 * {@code SimpleDateFormat} is not thread-safe in any JDK version,
046 * nor will it be as Sun have closed the bug/RFE.
047 * </p>
048 *
049 * <p>Only formatting is supported, but all patterns are compatible with
050 * SimpleDateFormat (except time zones and some year patterns - see below).</p>
051 *
052 * <p>Java 1.4 introduced a new pattern letter, {@code 'Z'}, to represent
053 * time zones in RFC822 format (eg. {@code +0800} or {@code -1100}).
054 * This pattern letter can be used here (on all JDK versions).</p>
055 *
056 * <p>In addition, the pattern {@code 'ZZ'} has been made to represent
057 * ISO8601 full format time zones (eg. {@code +08:00} or {@code -11:00}).
058 * This introduces a minor incompatibility with Java 1.4, but at a gain of
059 * useful functionality.</p>
060 *
061 * <p>Javadoc cites for the year pattern: <i>For formatting, if the number of
062 * pattern letters is 2, the year is truncated to 2 digits; otherwise it is
063 * interpreted as a number.</i> Starting with Java 1.7 a pattern of 'Y' or
064 * 'YYY' will be formatted as '2003', while it was '03' in former Java
065 * versions. FastDateFormat implements the behavior of Java 7.</p>
066 *
067 * @since 2.0
068 * @version $Id: FastDateFormat.java 1146138 2011-07-13 17:01:37Z joehni $
069 */
070 public class FastDateFormat extends Format {
071 // A lot of the speed in this class comes from caching, but some comes
072 // from the special int to StringBuffer conversion.
073 //
074 // The following produces a padded 2 digit number:
075 // buffer.append((char)(value / 10 + '0'));
076 // buffer.append((char)(value % 10 + '0'));
077 //
078 // Note that the fastest append to StringBuffer is a single char (used here).
079 // Note that Integer.toString() is not called, the conversion is simply
080 // taking the value and adding (mathematically) the ASCII value for '0'.
081 // So, don't change this code! It works and is very fast.
082
083 /**
084 * Required for serialization support.
085 *
086 * @see java.io.Serializable
087 */
088 private static final long serialVersionUID = 1L;
089
090 /**
091 * FULL locale dependent date or time style.
092 */
093 public static final int FULL = DateFormat.FULL;
094 /**
095 * LONG locale dependent date or time style.
096 */
097 public static final int LONG = DateFormat.LONG;
098 /**
099 * MEDIUM locale dependent date or time style.
100 */
101 public static final int MEDIUM = DateFormat.MEDIUM;
102 /**
103 * SHORT locale dependent date or time style.
104 */
105 public static final int SHORT = DateFormat.SHORT;
106
107 private static final FormatCache<FastDateFormat> cache= new FormatCache<FastDateFormat>() {
108 @Override
109 protected FastDateFormat createInstance(String pattern, TimeZone timeZone, Locale locale) {
110 return new FastDateFormat(pattern, timeZone, locale);
111 }
112 };
113
114 private static ConcurrentMap<TimeZoneDisplayKey, String> cTimeZoneDisplayCache =
115 new ConcurrentHashMap<TimeZoneDisplayKey, String>(7);
116
117 /**
118 * The pattern.
119 */
120 private final String mPattern;
121 /**
122 * The time zone.
123 */
124 private final TimeZone mTimeZone;
125 /**
126 * The locale.
127 */
128 private final Locale mLocale;
129 /**
130 * The parsed rules.
131 */
132 private transient Rule[] mRules;
133 /**
134 * The estimated maximum length.
135 */
136 private transient int mMaxLengthEstimate;
137
138 //-----------------------------------------------------------------------
139 /**
140 * <p>Gets a formatter instance using the default pattern in the
141 * default locale.</p>
142 *
143 * @return a date/time formatter
144 */
145 public static FastDateFormat getInstance() {
146 return cache.getDateTimeInstance(SHORT, SHORT, null, null);
147 }
148
149 /**
150 * <p>Gets a formatter instance using the specified pattern in the
151 * default locale.</p>
152 *
153 * @param pattern {@link java.text.SimpleDateFormat} compatible
154 * pattern
155 * @return a pattern based date/time formatter
156 * @throws IllegalArgumentException if pattern is invalid
157 */
158 public static FastDateFormat getInstance(String pattern) {
159 return cache.getInstance(pattern, null, null);
160 }
161
162 /**
163 * <p>Gets a formatter instance using the specified pattern and
164 * time zone.</p>
165 *
166 * @param pattern {@link java.text.SimpleDateFormat} compatible
167 * pattern
168 * @param timeZone optional time zone, overrides time zone of
169 * formatted date
170 * @return a pattern based date/time formatter
171 * @throws IllegalArgumentException if pattern is invalid
172 */
173 public static FastDateFormat getInstance(String pattern, TimeZone timeZone) {
174 return cache.getInstance(pattern, timeZone, null);
175 }
176
177 /**
178 * <p>Gets a formatter instance using the specified pattern and
179 * locale.</p>
180 *
181 * @param pattern {@link java.text.SimpleDateFormat} compatible
182 * pattern
183 * @param locale optional locale, overrides system locale
184 * @return a pattern based date/time formatter
185 * @throws IllegalArgumentException if pattern is invalid
186 */
187 public static FastDateFormat getInstance(String pattern, Locale locale) {
188 return cache.getInstance(pattern, null, locale);
189 }
190
191 /**
192 * <p>Gets a formatter instance using the specified pattern, time zone
193 * and locale.</p>
194 *
195 * @param pattern {@link java.text.SimpleDateFormat} compatible
196 * pattern
197 * @param timeZone optional time zone, overrides time zone of
198 * formatted date
199 * @param locale optional locale, overrides system locale
200 * @return a pattern based date/time formatter
201 * @throws IllegalArgumentException if pattern is invalid
202 * or {@code null}
203 */
204 public static FastDateFormat getInstance(String pattern, TimeZone timeZone, Locale locale) {
205 return cache.getInstance(pattern, timeZone, locale);
206 }
207
208 //-----------------------------------------------------------------------
209 /**
210 * <p>Gets a date formatter instance using the specified style in the
211 * default time zone and locale.</p>
212 *
213 * @param style date style: FULL, LONG, MEDIUM, or SHORT
214 * @return a localized standard date formatter
215 * @throws IllegalArgumentException if the Locale has no date
216 * pattern defined
217 * @since 2.1
218 */
219 public static FastDateFormat getDateInstance(int style) {
220 return cache.getDateTimeInstance(style, null, null, null);
221 }
222
223 /**
224 * <p>Gets a date formatter instance using the specified style and
225 * locale in the default time zone.</p>
226 *
227 * @param style date style: FULL, LONG, MEDIUM, or SHORT
228 * @param locale optional locale, overrides system locale
229 * @return a localized standard date formatter
230 * @throws IllegalArgumentException if the Locale has no date
231 * pattern defined
232 * @since 2.1
233 */
234 public static FastDateFormat getDateInstance(int style, Locale locale) {
235 return cache.getDateTimeInstance(style, null, null, locale);
236 }
237
238 /**
239 * <p>Gets a date formatter instance using the specified style and
240 * time zone in the default locale.</p>
241 *
242 * @param style date style: FULL, LONG, MEDIUM, or SHORT
243 * @param timeZone optional time zone, overrides time zone of
244 * formatted date
245 * @return a localized standard date formatter
246 * @throws IllegalArgumentException if the Locale has no date
247 * pattern defined
248 * @since 2.1
249 */
250 public static FastDateFormat getDateInstance(int style, TimeZone timeZone) {
251 return cache.getDateTimeInstance(style, null, timeZone, null);
252 }
253
254 /**
255 * <p>Gets a date formatter instance using the specified style, time
256 * zone and locale.</p>
257 *
258 * @param style date style: FULL, LONG, MEDIUM, or SHORT
259 * @param timeZone optional time zone, overrides time zone of
260 * formatted date
261 * @param locale optional locale, overrides system locale
262 * @return a localized standard date formatter
263 * @throws IllegalArgumentException if the Locale has no date
264 * pattern defined
265 */
266 public static FastDateFormat getDateInstance(int style, TimeZone timeZone, Locale locale) {
267 return cache.getDateTimeInstance(style, null, timeZone, locale);
268 }
269
270 //-----------------------------------------------------------------------
271 /**
272 * <p>Gets a time formatter instance using the specified style in the
273 * default time zone and locale.</p>
274 *
275 * @param style time style: FULL, LONG, MEDIUM, or SHORT
276 * @return a localized standard time formatter
277 * @throws IllegalArgumentException if the Locale has no time
278 * pattern defined
279 * @since 2.1
280 */
281 public static FastDateFormat getTimeInstance(int style) {
282 return cache.getDateTimeInstance(null, style, null, null);
283 }
284
285 /**
286 * <p>Gets a time formatter instance using the specified style and
287 * locale in the default time zone.</p>
288 *
289 * @param style time style: FULL, LONG, MEDIUM, or SHORT
290 * @param locale optional locale, overrides system locale
291 * @return a localized standard time formatter
292 * @throws IllegalArgumentException if the Locale has no time
293 * pattern defined
294 * @since 2.1
295 */
296 public static FastDateFormat getTimeInstance(int style, Locale locale) {
297 return cache.getDateTimeInstance(null, style, null, locale);
298 }
299
300 /**
301 * <p>Gets a time formatter instance using the specified style and
302 * time zone in the default locale.</p>
303 *
304 * @param style time style: FULL, LONG, MEDIUM, or SHORT
305 * @param timeZone optional time zone, overrides time zone of
306 * formatted time
307 * @return a localized standard time formatter
308 * @throws IllegalArgumentException if the Locale has no time
309 * pattern defined
310 * @since 2.1
311 */
312 public static FastDateFormat getTimeInstance(int style, TimeZone timeZone) {
313 return cache.getDateTimeInstance(null, style, timeZone, null);
314 }
315
316 /**
317 * <p>Gets a time formatter instance using the specified style, time
318 * zone and locale.</p>
319 *
320 * @param style time style: FULL, LONG, MEDIUM, or SHORT
321 * @param timeZone optional time zone, overrides time zone of
322 * formatted time
323 * @param locale optional locale, overrides system locale
324 * @return a localized standard time formatter
325 * @throws IllegalArgumentException if the Locale has no time
326 * pattern defined
327 */
328 public static FastDateFormat getTimeInstance(int style, TimeZone timeZone, Locale locale) {
329 return cache.getDateTimeInstance(null, style, timeZone, locale);
330 }
331
332 //-----------------------------------------------------------------------
333 /**
334 * <p>Gets a date/time formatter instance using the specified style
335 * in the default time zone and locale.</p>
336 *
337 * @param dateStyle date style: FULL, LONG, MEDIUM, or SHORT
338 * @param timeStyle time style: FULL, LONG, MEDIUM, or SHORT
339 * @return a localized standard date/time formatter
340 * @throws IllegalArgumentException if the Locale has no date/time
341 * pattern defined
342 * @since 2.1
343 */
344 public static FastDateFormat getDateTimeInstance(int dateStyle, int timeStyle) {
345 return cache.getDateTimeInstance(dateStyle, timeStyle, null, null);
346 }
347
348 /**
349 * <p>Gets a date/time formatter instance using the specified style and
350 * locale in the default time zone.</p>
351 *
352 * @param dateStyle date style: FULL, LONG, MEDIUM, or SHORT
353 * @param timeStyle time style: FULL, LONG, MEDIUM, or SHORT
354 * @param locale optional locale, overrides system locale
355 * @return a localized standard date/time formatter
356 * @throws IllegalArgumentException if the Locale has no date/time
357 * pattern defined
358 * @since 2.1
359 */
360 public static FastDateFormat getDateTimeInstance(int dateStyle, int timeStyle, Locale locale) {
361 return cache.getDateTimeInstance(dateStyle, timeStyle, null, locale);
362 }
363
364 /**
365 * <p>Gets a date/time formatter instance using the specified style and
366 * time zone in the default locale.</p>
367 *
368 * @param dateStyle date style: FULL, LONG, MEDIUM, or SHORT
369 * @param timeStyle time style: FULL, LONG, MEDIUM, or SHORT
370 * @param timeZone optional time zone, overrides time zone of
371 * formatted date
372 * @return a localized standard date/time formatter
373 * @throws IllegalArgumentException if the Locale has no date/time
374 * pattern defined
375 * @since 2.1
376 */
377 public static FastDateFormat getDateTimeInstance(int dateStyle, int timeStyle, TimeZone timeZone) {
378 return getDateTimeInstance(dateStyle, timeStyle, timeZone, null);
379 }
380 /**
381 * <p>Gets a date/time formatter instance using the specified style,
382 * time zone and locale.</p>
383 *
384 * @param dateStyle date style: FULL, LONG, MEDIUM, or SHORT
385 * @param timeStyle time style: FULL, LONG, MEDIUM, or SHORT
386 * @param timeZone optional time zone, overrides time zone of
387 * formatted date
388 * @param locale optional locale, overrides system locale
389 * @return a localized standard date/time formatter
390 * @throws IllegalArgumentException if the Locale has no date/time
391 * pattern defined
392 */
393 public static FastDateFormat getDateTimeInstance(
394 int dateStyle, int timeStyle, TimeZone timeZone, Locale locale) {
395 return cache.getDateTimeInstance(dateStyle, timeStyle, timeZone, locale);
396 }
397
398 //-----------------------------------------------------------------------
399 /**
400 * <p>Gets the time zone display name, using a cache for performance.</p>
401 *
402 * @param tz the zone to query
403 * @param daylight true if daylight savings
404 * @param style the style to use {@code TimeZone.LONG} or {@code TimeZone.SHORT}
405 * @param locale the locale to use
406 * @return the textual name of the time zone
407 */
408 static String getTimeZoneDisplay(TimeZone tz, boolean daylight, int style, Locale locale) {
409 TimeZoneDisplayKey key = new TimeZoneDisplayKey(tz, daylight, style, locale);
410 String value = cTimeZoneDisplayCache.get(key);
411 if (value == null) {
412 // This is a very slow call, so cache the results.
413 value = tz.getDisplayName(daylight, style, locale);
414 String prior = cTimeZoneDisplayCache.putIfAbsent(key, value);
415 if (prior != null) {
416 value= prior;
417 }
418 }
419 return value;
420 }
421
422 // Constructor
423 //-----------------------------------------------------------------------
424 /**
425 * <p>Constructs a new FastDateFormat.</p>
426 *
427 * @param pattern {@link java.text.SimpleDateFormat} compatible pattern
428 * @param timeZone non-null time zone to use
429 * @param locale non-null locale to use
430 * @throws NullPointerException if pattern, timeZone, or locale is null.
431 */
432 protected FastDateFormat(String pattern, TimeZone timeZone, Locale locale) {
433 mPattern = pattern;
434 mTimeZone = timeZone;
435 mLocale = locale;
436
437 init();
438 }
439
440 /**
441 * <p>Initializes the instance for first use.</p>
442 */
443 private void init() {
444 List<Rule> rulesList = parsePattern();
445 mRules = rulesList.toArray(new Rule[rulesList.size()]);
446
447 int len = 0;
448 for (int i=mRules.length; --i >= 0; ) {
449 len += mRules[i].estimateLength();
450 }
451
452 mMaxLengthEstimate = len;
453 }
454
455 // Parse the pattern
456 //-----------------------------------------------------------------------
457 /**
458 * <p>Returns a list of Rules given a pattern.</p>
459 *
460 * @return a {@code List} of Rule objects
461 * @throws IllegalArgumentException if pattern is invalid
462 */
463 protected List<Rule> parsePattern() {
464 DateFormatSymbols symbols = new DateFormatSymbols(mLocale);
465 List<Rule> rules = new ArrayList<Rule>();
466
467 String[] ERAs = symbols.getEras();
468 String[] months = symbols.getMonths();
469 String[] shortMonths = symbols.getShortMonths();
470 String[] weekdays = symbols.getWeekdays();
471 String[] shortWeekdays = symbols.getShortWeekdays();
472 String[] AmPmStrings = symbols.getAmPmStrings();
473
474 int length = mPattern.length();
475 int[] indexRef = new int[1];
476
477 for (int i = 0; i < length; i++) {
478 indexRef[0] = i;
479 String token = parseToken(mPattern, indexRef);
480 i = indexRef[0];
481
482 int tokenLen = token.length();
483 if (tokenLen == 0) {
484 break;
485 }
486
487 Rule rule;
488 char c = token.charAt(0);
489
490 switch (c) {
491 case 'G': // era designator (text)
492 rule = new TextField(Calendar.ERA, ERAs);
493 break;
494 case 'y': // year (number)
495 if (tokenLen == 2) {
496 rule = TwoDigitYearField.INSTANCE;
497 } else {
498 rule = selectNumberRule(Calendar.YEAR, tokenLen < 4 ? 4 : tokenLen);
499 }
500 break;
501 case 'M': // month in year (text and number)
502 if (tokenLen >= 4) {
503 rule = new TextField(Calendar.MONTH, months);
504 } else if (tokenLen == 3) {
505 rule = new TextField(Calendar.MONTH, shortMonths);
506 } else if (tokenLen == 2) {
507 rule = TwoDigitMonthField.INSTANCE;
508 } else {
509 rule = UnpaddedMonthField.INSTANCE;
510 }
511 break;
512 case 'd': // day in month (number)
513 rule = selectNumberRule(Calendar.DAY_OF_MONTH, tokenLen);
514 break;
515 case 'h': // hour in am/pm (number, 1..12)
516 rule = new TwelveHourField(selectNumberRule(Calendar.HOUR, tokenLen));
517 break;
518 case 'H': // hour in day (number, 0..23)
519 rule = selectNumberRule(Calendar.HOUR_OF_DAY, tokenLen);
520 break;
521 case 'm': // minute in hour (number)
522 rule = selectNumberRule(Calendar.MINUTE, tokenLen);
523 break;
524 case 's': // second in minute (number)
525 rule = selectNumberRule(Calendar.SECOND, tokenLen);
526 break;
527 case 'S': // millisecond (number)
528 rule = selectNumberRule(Calendar.MILLISECOND, tokenLen);
529 break;
530 case 'E': // day in week (text)
531 rule = new TextField(Calendar.DAY_OF_WEEK, tokenLen < 4 ? shortWeekdays : weekdays);
532 break;
533 case 'D': // day in year (number)
534 rule = selectNumberRule(Calendar.DAY_OF_YEAR, tokenLen);
535 break;
536 case 'F': // day of week in month (number)
537 rule = selectNumberRule(Calendar.DAY_OF_WEEK_IN_MONTH, tokenLen);
538 break;
539 case 'w': // week in year (number)
540 rule = selectNumberRule(Calendar.WEEK_OF_YEAR, tokenLen);
541 break;
542 case 'W': // week in month (number)
543 rule = selectNumberRule(Calendar.WEEK_OF_MONTH, tokenLen);
544 break;
545 case 'a': // am/pm marker (text)
546 rule = new TextField(Calendar.AM_PM, AmPmStrings);
547 break;
548 case 'k': // hour in day (1..24)
549 rule = new TwentyFourHourField(selectNumberRule(Calendar.HOUR_OF_DAY, tokenLen));
550 break;
551 case 'K': // hour in am/pm (0..11)
552 rule = selectNumberRule(Calendar.HOUR, tokenLen);
553 break;
554 case 'z': // time zone (text)
555 if (tokenLen >= 4) {
556 rule = new TimeZoneNameRule(mTimeZone, mLocale, TimeZone.LONG);
557 } else {
558 rule = new TimeZoneNameRule(mTimeZone, mLocale, TimeZone.SHORT);
559 }
560 break;
561 case 'Z': // time zone (value)
562 if (tokenLen == 1) {
563 rule = TimeZoneNumberRule.INSTANCE_NO_COLON;
564 } else {
565 rule = TimeZoneNumberRule.INSTANCE_COLON;
566 }
567 break;
568 case '\'': // literal text
569 String sub = token.substring(1);
570 if (sub.length() == 1) {
571 rule = new CharacterLiteral(sub.charAt(0));
572 } else {
573 rule = new StringLiteral(sub);
574 }
575 break;
576 default:
577 throw new IllegalArgumentException("Illegal pattern component: " + token);
578 }
579
580 rules.add(rule);
581 }
582
583 return rules;
584 }
585
586 /**
587 * <p>Performs the parsing of tokens.</p>
588 *
589 * @param pattern the pattern
590 * @param indexRef index references
591 * @return parsed token
592 */
593 protected String parseToken(String pattern, int[] indexRef) {
594 StringBuilder buf = new StringBuilder();
595
596 int i = indexRef[0];
597 int length = pattern.length();
598
599 char c = pattern.charAt(i);
600 if (c >= 'A' && c <= 'Z' || c >= 'a' && c <= 'z') {
601 // Scan a run of the same character, which indicates a time
602 // pattern.
603 buf.append(c);
604
605 while (i + 1 < length) {
606 char peek = pattern.charAt(i + 1);
607 if (peek == c) {
608 buf.append(c);
609 i++;
610 } else {
611 break;
612 }
613 }
614 } else {
615 // This will identify token as text.
616 buf.append('\'');
617
618 boolean inLiteral = false;
619
620 for (; i < length; i++) {
621 c = pattern.charAt(i);
622
623 if (c == '\'') {
624 if (i + 1 < length && pattern.charAt(i + 1) == '\'') {
625 // '' is treated as escaped '
626 i++;
627 buf.append(c);
628 } else {
629 inLiteral = !inLiteral;
630 }
631 } else if (!inLiteral &&
632 (c >= 'A' && c <= 'Z' || c >= 'a' && c <= 'z')) {
633 i--;
634 break;
635 } else {
636 buf.append(c);
637 }
638 }
639 }
640
641 indexRef[0] = i;
642 return buf.toString();
643 }
644
645 /**
646 * <p>Gets an appropriate rule for the padding required.</p>
647 *
648 * @param field the field to get a rule for
649 * @param padding the padding required
650 * @return a new rule with the correct padding
651 */
652 protected NumberRule selectNumberRule(int field, int padding) {
653 switch (padding) {
654 case 1:
655 return new UnpaddedNumberField(field);
656 case 2:
657 return new TwoDigitNumberField(field);
658 default:
659 return new PaddedNumberField(field, padding);
660 }
661 }
662
663 // Format methods
664 //-----------------------------------------------------------------------
665 /**
666 * <p>Formats a {@code Date}, {@code Calendar} or
667 * {@code Long} (milliseconds) object.</p>
668 *
669 * @param obj the object to format
670 * @param toAppendTo the buffer to append to
671 * @param pos the position - ignored
672 * @return the buffer passed in
673 */
674 @Override
675 public StringBuffer format(Object obj, StringBuffer toAppendTo, FieldPosition pos) {
676 if (obj instanceof Date) {
677 return format((Date) obj, toAppendTo);
678 } else if (obj instanceof Calendar) {
679 return format((Calendar) obj, toAppendTo);
680 } else if (obj instanceof Long) {
681 return format(((Long) obj).longValue(), toAppendTo);
682 } else {
683 throw new IllegalArgumentException("Unknown class: " +
684 (obj == null ? "<null>" : obj.getClass().getName()));
685 }
686 }
687
688 /**
689 * <p>Formats a millisecond {@code long} value.</p>
690 *
691 * @param millis the millisecond value to format
692 * @return the formatted string
693 * @since 2.1
694 */
695 public String format(long millis) {
696 return format(new Date(millis));
697 }
698
699 /**
700 * <p>Formats a {@code Date} object using a {@code GregorianCalendar}.</p>
701 *
702 * @param date the date to format
703 * @return the formatted string
704 */
705 public String format(Date date) {
706 Calendar c = new GregorianCalendar(mTimeZone, mLocale); // hard code GregorianCalendar
707 c.setTime(date);
708 return applyRules(c, new StringBuffer(mMaxLengthEstimate)).toString();
709 }
710
711 /**
712 * <p>Formats a {@code Calendar} object.</p>
713 *
714 * @param calendar the calendar to format
715 * @return the formatted string
716 */
717 public String format(Calendar calendar) {
718 return format(calendar, new StringBuffer(mMaxLengthEstimate)).toString();
719 }
720
721 /**
722 * <p>Formats a milliseond {@code long} value into the
723 * supplied {@code StringBuffer}.</p>
724 *
725 * @param millis the millisecond value to format
726 * @param buf the buffer to format into
727 * @return the specified string buffer
728 * @since 2.1
729 */
730 public StringBuffer format(long millis, StringBuffer buf) {
731 return format(new Date(millis), buf);
732 }
733
734 /**
735 * <p>Formats a {@code Date} object into the
736 * supplied {@code StringBuffer} using a {@code GregorianCalendar}.</p>
737 *
738 * @param date the date to format
739 * @param buf the buffer to format into
740 * @return the specified string buffer
741 */
742 public StringBuffer format(Date date, StringBuffer buf) {
743 Calendar c = new GregorianCalendar(mTimeZone, mLocale); // hard code GregorianCalendar
744 c.setTime(date);
745 return applyRules(c, buf);
746 }
747
748 /**
749 * <p>Formats a {@code Calendar} object into the
750 * supplied {@code StringBuffer}.</p>
751 *
752 * @param calendar the calendar to format
753 * @param buf the buffer to format into
754 * @return the specified string buffer
755 */
756 public StringBuffer format(Calendar calendar, StringBuffer buf) {
757 return applyRules(calendar, buf);
758 }
759
760 /**
761 * <p>Performs the formatting by applying the rules to the
762 * specified calendar.</p>
763 *
764 * @param calendar the calendar to format
765 * @param buf the buffer to format into
766 * @return the specified string buffer
767 */
768 protected StringBuffer applyRules(Calendar calendar, StringBuffer buf) {
769 for (Rule rule : mRules) {
770 rule.appendTo(buf, calendar);
771 }
772 return buf;
773 }
774
775 // Parsing
776 //-----------------------------------------------------------------------
777 /**
778 * <p>Parsing is not supported.</p>
779 *
780 * @param source the string to parse
781 * @param pos the parsing position
782 * @return {@code null} as not supported
783 */
784 @Override
785 public Object parseObject(String source, ParsePosition pos) {
786 pos.setIndex(0);
787 pos.setErrorIndex(0);
788 return null;
789 }
790
791 // Accessors
792 //-----------------------------------------------------------------------
793 /**
794 * <p>Gets the pattern used by this formatter.</p>
795 *
796 * @return the pattern, {@link java.text.SimpleDateFormat} compatible
797 */
798 public String getPattern() {
799 return mPattern;
800 }
801
802 /**
803 * <p>Gets the time zone used by this formatter.</p>
804 *
805 * <p>This zone is always used for {@code Date} formatting. </p>
806 *
807 * @return the time zone
808 */
809 public TimeZone getTimeZone() {
810 return mTimeZone;
811 }
812
813 /**
814 * <p>Gets the locale used by this formatter.</p>
815 *
816 * @return the locale
817 */
818 public Locale getLocale() {
819 return mLocale;
820 }
821
822 /**
823 * <p>Gets an estimate for the maximum string length that the
824 * formatter will produce.</p>
825 *
826 * <p>The actual formatted length will almost always be less than or
827 * equal to this amount.</p>
828 *
829 * @return the maximum formatted length
830 */
831 public int getMaxLengthEstimate() {
832 return mMaxLengthEstimate;
833 }
834
835 // Basics
836 //-----------------------------------------------------------------------
837 /**
838 * <p>Compares two objects for equality.</p>
839 *
840 * @param obj the object to compare to
841 * @return {@code true} if equal
842 */
843 @Override
844 public boolean equals(Object obj) {
845 if (obj instanceof FastDateFormat == false) {
846 return false;
847 }
848 FastDateFormat other = (FastDateFormat) obj;
849 return mPattern.equals(other.mPattern)
850 && mTimeZone.equals(other.mTimeZone)
851 && mLocale.equals(other.mLocale);
852 }
853
854 /**
855 * <p>Returns a hashcode compatible with equals.</p>
856 *
857 * @return a hashcode compatible with equals
858 */
859 @Override
860 public int hashCode() {
861 return mPattern.hashCode() + 13 * (mTimeZone.hashCode() + 13 * mLocale.hashCode());
862 }
863
864 /**
865 * <p>Gets a debugging string version of this formatter.</p>
866 *
867 * @return a debugging string
868 */
869 @Override
870 public String toString() {
871 return "FastDateFormat[" + mPattern + "]";
872 }
873
874 // Serializing
875 //-----------------------------------------------------------------------
876 /**
877 * Create the object after serialization. This implementation reinitializes the
878 * transient properties.
879 *
880 * @param in ObjectInputStream from which the object is being deserialized.
881 * @throws IOException if there is an IO issue.
882 * @throws ClassNotFoundException if a class cannot be found.
883 */
884 private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
885 in.defaultReadObject();
886 init();
887 }
888
889 // Rules
890 //-----------------------------------------------------------------------
891 /**
892 * <p>Inner class defining a rule.</p>
893 */
894 private interface Rule {
895 /**
896 * Returns the estimated lentgh of the result.
897 *
898 * @return the estimated length
899 */
900 int estimateLength();
901
902 /**
903 * Appends the value of the specified calendar to the output buffer based on the rule implementation.
904 *
905 * @param buffer the output buffer
906 * @param calendar calendar to be appended
907 */
908 void appendTo(StringBuffer buffer, Calendar calendar);
909 }
910
911 /**
912 * <p>Inner class defining a numeric rule.</p>
913 */
914 private interface NumberRule extends Rule {
915 /**
916 * Appends the specified value to the output buffer based on the rule implementation.
917 *
918 * @param buffer the output buffer
919 * @param value the value to be appended
920 */
921 void appendTo(StringBuffer buffer, int value);
922 }
923
924 /**
925 * <p>Inner class to output a constant single character.</p>
926 */
927 private static class CharacterLiteral implements Rule {
928 private final char mValue;
929
930 /**
931 * Constructs a new instance of {@code CharacterLiteral}
932 * to hold the specified value.
933 *
934 * @param value the character literal
935 */
936 CharacterLiteral(char value) {
937 mValue = value;
938 }
939
940 /**
941 * {@inheritDoc}
942 */
943 public int estimateLength() {
944 return 1;
945 }
946
947 /**
948 * {@inheritDoc}
949 */
950 public void appendTo(StringBuffer buffer, Calendar calendar) {
951 buffer.append(mValue);
952 }
953 }
954
955 /**
956 * <p>Inner class to output a constant string.</p>
957 */
958 private static class StringLiteral implements Rule {
959 private final String mValue;
960
961 /**
962 * Constructs a new instance of {@code StringLiteral}
963 * to hold the specified value.
964 *
965 * @param value the string literal
966 */
967 StringLiteral(String value) {
968 mValue = value;
969 }
970
971 /**
972 * {@inheritDoc}
973 */
974 public int estimateLength() {
975 return mValue.length();
976 }
977
978 /**
979 * {@inheritDoc}
980 */
981 public void appendTo(StringBuffer buffer, Calendar calendar) {
982 buffer.append(mValue);
983 }
984 }
985
986 /**
987 * <p>Inner class to output one of a set of values.</p>
988 */
989 private static class TextField implements Rule {
990 private final int mField;
991 private final String[] mValues;
992
993 /**
994 * Constructs an instance of {@code TextField}
995 * with the specified field and values.
996 *
997 * @param field the field
998 * @param values the field values
999 */
1000 TextField(int field, String[] values) {
1001 mField = field;
1002 mValues = values;
1003 }
1004
1005 /**
1006 * {@inheritDoc}
1007 */
1008 public int estimateLength() {
1009 int max = 0;
1010 for (int i=mValues.length; --i >= 0; ) {
1011 int len = mValues[i].length();
1012 if (len > max) {
1013 max = len;
1014 }
1015 }
1016 return max;
1017 }
1018
1019 /**
1020 * {@inheritDoc}
1021 */
1022 public void appendTo(StringBuffer buffer, Calendar calendar) {
1023 buffer.append(mValues[calendar.get(mField)]);
1024 }
1025 }
1026
1027 /**
1028 * <p>Inner class to output an unpadded number.</p>
1029 */
1030 private static class UnpaddedNumberField implements NumberRule {
1031 private final int mField;
1032
1033 /**
1034 * Constructs an instance of {@code UnpadedNumberField} with the specified field.
1035 *
1036 * @param field the field
1037 */
1038 UnpaddedNumberField(int field) {
1039 mField = field;
1040 }
1041
1042 /**
1043 * {@inheritDoc}
1044 */
1045 public int estimateLength() {
1046 return 4;
1047 }
1048
1049 /**
1050 * {@inheritDoc}
1051 */
1052 public void appendTo(StringBuffer buffer, Calendar calendar) {
1053 appendTo(buffer, calendar.get(mField));
1054 }
1055
1056 /**
1057 * {@inheritDoc}
1058 */
1059 public final void appendTo(StringBuffer buffer, int value) {
1060 if (value < 10) {
1061 buffer.append((char)(value + '0'));
1062 } else if (value < 100) {
1063 buffer.append((char)(value / 10 + '0'));
1064 buffer.append((char)(value % 10 + '0'));
1065 } else {
1066 buffer.append(Integer.toString(value));
1067 }
1068 }
1069 }
1070
1071 /**
1072 * <p>Inner class to output an unpadded month.</p>
1073 */
1074 private static class UnpaddedMonthField implements NumberRule {
1075 static final UnpaddedMonthField INSTANCE = new UnpaddedMonthField();
1076
1077 /**
1078 * Constructs an instance of {@code UnpaddedMonthField}.
1079 *
1080 */
1081 UnpaddedMonthField() {
1082 super();
1083 }
1084
1085 /**
1086 * {@inheritDoc}
1087 */
1088 public int estimateLength() {
1089 return 2;
1090 }
1091
1092 /**
1093 * {@inheritDoc}
1094 */
1095 public void appendTo(StringBuffer buffer, Calendar calendar) {
1096 appendTo(buffer, calendar.get(Calendar.MONTH) + 1);
1097 }
1098
1099 /**
1100 * {@inheritDoc}
1101 */
1102 public final void appendTo(StringBuffer buffer, int value) {
1103 if (value < 10) {
1104 buffer.append((char)(value + '0'));
1105 } else {
1106 buffer.append((char)(value / 10 + '0'));
1107 buffer.append((char)(value % 10 + '0'));
1108 }
1109 }
1110 }
1111
1112 /**
1113 * <p>Inner class to output a padded number.</p>
1114 */
1115 private static class PaddedNumberField implements NumberRule {
1116 private final int mField;
1117 private final int mSize;
1118
1119 /**
1120 * Constructs an instance of {@code PaddedNumberField}.
1121 *
1122 * @param field the field
1123 * @param size size of the output field
1124 */
1125 PaddedNumberField(int field, int size) {
1126 if (size < 3) {
1127 // Should use UnpaddedNumberField or TwoDigitNumberField.
1128 throw new IllegalArgumentException();
1129 }
1130 mField = field;
1131 mSize = size;
1132 }
1133
1134 /**
1135 * {@inheritDoc}
1136 */
1137 public int estimateLength() {
1138 return 4;
1139 }
1140
1141 /**
1142 * {@inheritDoc}
1143 */
1144 public void appendTo(StringBuffer buffer, Calendar calendar) {
1145 appendTo(buffer, calendar.get(mField));
1146 }
1147
1148 /**
1149 * {@inheritDoc}
1150 */
1151 public final void appendTo(StringBuffer buffer, int value) {
1152 if (value < 100) {
1153 for (int i = mSize; --i >= 2; ) {
1154 buffer.append('0');
1155 }
1156 buffer.append((char)(value / 10 + '0'));
1157 buffer.append((char)(value % 10 + '0'));
1158 } else {
1159 int digits;
1160 if (value < 1000) {
1161 digits = 3;
1162 } else {
1163 Validate.isTrue(value > -1, "Negative values should not be possible", value);
1164 digits = Integer.toString(value).length();
1165 }
1166 for (int i = mSize; --i >= digits; ) {
1167 buffer.append('0');
1168 }
1169 buffer.append(Integer.toString(value));
1170 }
1171 }
1172 }
1173
1174 /**
1175 * <p>Inner class to output a two digit number.</p>
1176 */
1177 private static class TwoDigitNumberField implements NumberRule {
1178 private final int mField;
1179
1180 /**
1181 * Constructs an instance of {@code TwoDigitNumberField} with the specified field.
1182 *
1183 * @param field the field
1184 */
1185 TwoDigitNumberField(int field) {
1186 mField = field;
1187 }
1188
1189 /**
1190 * {@inheritDoc}
1191 */
1192 public int estimateLength() {
1193 return 2;
1194 }
1195
1196 /**
1197 * {@inheritDoc}
1198 */
1199 public void appendTo(StringBuffer buffer, Calendar calendar) {
1200 appendTo(buffer, calendar.get(mField));
1201 }
1202
1203 /**
1204 * {@inheritDoc}
1205 */
1206 public final void appendTo(StringBuffer buffer, int value) {
1207 if (value < 100) {
1208 buffer.append((char)(value / 10 + '0'));
1209 buffer.append((char)(value % 10 + '0'));
1210 } else {
1211 buffer.append(Integer.toString(value));
1212 }
1213 }
1214 }
1215
1216 /**
1217 * <p>Inner class to output a two digit year.</p>
1218 */
1219 private static class TwoDigitYearField implements NumberRule {
1220 static final TwoDigitYearField INSTANCE = new TwoDigitYearField();
1221
1222 /**
1223 * Constructs an instance of {@code TwoDigitYearField}.
1224 */
1225 TwoDigitYearField() {
1226 super();
1227 }
1228
1229 /**
1230 * {@inheritDoc}
1231 */
1232 public int estimateLength() {
1233 return 2;
1234 }
1235
1236 /**
1237 * {@inheritDoc}
1238 */
1239 public void appendTo(StringBuffer buffer, Calendar calendar) {
1240 appendTo(buffer, calendar.get(Calendar.YEAR) % 100);
1241 }
1242
1243 /**
1244 * {@inheritDoc}
1245 */
1246 public final void appendTo(StringBuffer buffer, int value) {
1247 buffer.append((char)(value / 10 + '0'));
1248 buffer.append((char)(value % 10 + '0'));
1249 }
1250 }
1251
1252 /**
1253 * <p>Inner class to output a two digit month.</p>
1254 */
1255 private static class TwoDigitMonthField implements NumberRule {
1256 static final TwoDigitMonthField INSTANCE = new TwoDigitMonthField();
1257
1258 /**
1259 * Constructs an instance of {@code TwoDigitMonthField}.
1260 */
1261 TwoDigitMonthField() {
1262 super();
1263 }
1264
1265 /**
1266 * {@inheritDoc}
1267 */
1268 public int estimateLength() {
1269 return 2;
1270 }
1271
1272 /**
1273 * {@inheritDoc}
1274 */
1275 public void appendTo(StringBuffer buffer, Calendar calendar) {
1276 appendTo(buffer, calendar.get(Calendar.MONTH) + 1);
1277 }
1278
1279 /**
1280 * {@inheritDoc}
1281 */
1282 public final void appendTo(StringBuffer buffer, int value) {
1283 buffer.append((char)(value / 10 + '0'));
1284 buffer.append((char)(value % 10 + '0'));
1285 }
1286 }
1287
1288 /**
1289 * <p>Inner class to output the twelve hour field.</p>
1290 */
1291 private static class TwelveHourField implements NumberRule {
1292 private final NumberRule mRule;
1293
1294 /**
1295 * Constructs an instance of {@code TwelveHourField} with the specified
1296 * {@code NumberRule}.
1297 *
1298 * @param rule the rule
1299 */
1300 TwelveHourField(NumberRule rule) {
1301 mRule = rule;
1302 }
1303
1304 /**
1305 * {@inheritDoc}
1306 */
1307 public int estimateLength() {
1308 return mRule.estimateLength();
1309 }
1310
1311 /**
1312 * {@inheritDoc}
1313 */
1314 public void appendTo(StringBuffer buffer, Calendar calendar) {
1315 int value = calendar.get(Calendar.HOUR);
1316 if (value == 0) {
1317 value = calendar.getLeastMaximum(Calendar.HOUR) + 1;
1318 }
1319 mRule.appendTo(buffer, value);
1320 }
1321
1322 /**
1323 * {@inheritDoc}
1324 */
1325 public void appendTo(StringBuffer buffer, int value) {
1326 mRule.appendTo(buffer, value);
1327 }
1328 }
1329
1330 /**
1331 * <p>Inner class to output the twenty four hour field.</p>
1332 */
1333 private static class TwentyFourHourField implements NumberRule {
1334 private final NumberRule mRule;
1335
1336 /**
1337 * Constructs an instance of {@code TwentyFourHourField} with the specified
1338 * {@code NumberRule}.
1339 *
1340 * @param rule the rule
1341 */
1342 TwentyFourHourField(NumberRule rule) {
1343 mRule = rule;
1344 }
1345
1346 /**
1347 * {@inheritDoc}
1348 */
1349 public int estimateLength() {
1350 return mRule.estimateLength();
1351 }
1352
1353 /**
1354 * {@inheritDoc}
1355 */
1356 public void appendTo(StringBuffer buffer, Calendar calendar) {
1357 int value = calendar.get(Calendar.HOUR_OF_DAY);
1358 if (value == 0) {
1359 value = calendar.getMaximum(Calendar.HOUR_OF_DAY) + 1;
1360 }
1361 mRule.appendTo(buffer, value);
1362 }
1363
1364 /**
1365 * {@inheritDoc}
1366 */
1367 public void appendTo(StringBuffer buffer, int value) {
1368 mRule.appendTo(buffer, value);
1369 }
1370 }
1371
1372 /**
1373 * <p>Inner class to output a time zone name.</p>
1374 */
1375 private static class TimeZoneNameRule implements Rule {
1376 private final TimeZone mTimeZone;
1377 private final String mStandard;
1378 private final String mDaylight;
1379
1380 /**
1381 * Constructs an instance of {@code TimeZoneNameRule} with the specified properties.
1382 *
1383 * @param timeZone the time zone
1384 * @param locale the locale
1385 * @param style the style
1386 */
1387 TimeZoneNameRule(TimeZone timeZone, Locale locale, int style) {
1388 mTimeZone = timeZone;
1389
1390 mStandard = getTimeZoneDisplay(timeZone, false, style, locale);
1391 mDaylight = getTimeZoneDisplay(timeZone, true, style, locale);
1392 }
1393
1394 /**
1395 * {@inheritDoc}
1396 */
1397 public int estimateLength() {
1398 return Math.max(mStandard.length(), mDaylight.length());
1399 }
1400
1401 /**
1402 * {@inheritDoc}
1403 */
1404 public void appendTo(StringBuffer buffer, Calendar calendar) {
1405 if (mTimeZone.useDaylightTime() && calendar.get(Calendar.DST_OFFSET) != 0) {
1406 buffer.append(mDaylight);
1407 } else {
1408 buffer.append(mStandard);
1409 }
1410 }
1411 }
1412
1413 /**
1414 * <p>Inner class to output a time zone as a number {@code +/-HHMM}
1415 * or {@code +/-HH:MM}.</p>
1416 */
1417 private static class TimeZoneNumberRule implements Rule {
1418 static final TimeZoneNumberRule INSTANCE_COLON = new TimeZoneNumberRule(true);
1419 static final TimeZoneNumberRule INSTANCE_NO_COLON = new TimeZoneNumberRule(false);
1420
1421 final boolean mColon;
1422
1423 /**
1424 * Constructs an instance of {@code TimeZoneNumberRule} with the specified properties.
1425 *
1426 * @param colon add colon between HH and MM in the output if {@code true}
1427 */
1428 TimeZoneNumberRule(boolean colon) {
1429 mColon = colon;
1430 }
1431
1432 /**
1433 * {@inheritDoc}
1434 */
1435 public int estimateLength() {
1436 return 5;
1437 }
1438
1439 /**
1440 * {@inheritDoc}
1441 */
1442 public void appendTo(StringBuffer buffer, Calendar calendar) {
1443 int offset = calendar.get(Calendar.ZONE_OFFSET) + calendar.get(Calendar.DST_OFFSET);
1444
1445 if (offset < 0) {
1446 buffer.append('-');
1447 offset = -offset;
1448 } else {
1449 buffer.append('+');
1450 }
1451
1452 int hours = offset / (60 * 60 * 1000);
1453 buffer.append((char)(hours / 10 + '0'));
1454 buffer.append((char)(hours % 10 + '0'));
1455
1456 if (mColon) {
1457 buffer.append(':');
1458 }
1459
1460 int minutes = offset / (60 * 1000) - 60 * hours;
1461 buffer.append((char)(minutes / 10 + '0'));
1462 buffer.append((char)(minutes % 10 + '0'));
1463 }
1464 }
1465
1466 // ----------------------------------------------------------------------
1467 /**
1468 * <p>Inner class that acts as a compound key for time zone names.</p>
1469 */
1470 private static class TimeZoneDisplayKey {
1471 private final TimeZone mTimeZone;
1472 private final int mStyle;
1473 private final Locale mLocale;
1474
1475 /**
1476 * Constructs an instance of {@code TimeZoneDisplayKey} with the specified properties.
1477 *
1478 * @param timeZone the time zone
1479 * @param daylight adjust the style for daylight saving time if {@code true}
1480 * @param style the timezone style
1481 * @param locale the timezone locale
1482 */
1483 TimeZoneDisplayKey(TimeZone timeZone,
1484 boolean daylight, int style, Locale locale) {
1485 mTimeZone = timeZone;
1486 if (daylight) {
1487 style |= 0x80000000;
1488 }
1489 mStyle = style;
1490 mLocale = locale;
1491 }
1492
1493 /**
1494 * {@inheritDoc}
1495 */
1496 @Override
1497 public int hashCode() {
1498 return (mStyle * 31 + mLocale.hashCode() ) * 31 + mTimeZone.hashCode();
1499 }
1500
1501 /**
1502 * {@inheritDoc}
1503 */
1504 @Override
1505 public boolean equals(Object obj) {
1506 if (this == obj) {
1507 return true;
1508 }
1509 if (obj instanceof TimeZoneDisplayKey) {
1510 TimeZoneDisplayKey other = (TimeZoneDisplayKey)obj;
1511 return
1512 mTimeZone.equals(other.mTimeZone) &&
1513 mStyle == other.mStyle &&
1514 mLocale.equals(other.mLocale);
1515 }
1516 return false;
1517 }
1518 }
1519 }