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 }