| 1 16 package org.apache.commons.lang.time; 17 18 import java.text.DateFormat ; 19 import java.text.DateFormatSymbols ; 20 import java.text.FieldPosition ; 21 import java.text.Format ; 22 import java.text.ParsePosition ; 23 import java.text.SimpleDateFormat ; 24 import java.util.ArrayList ; 25 import java.util.Calendar ; 26 import java.util.Date ; 27 import java.util.GregorianCalendar ; 28 import java.util.HashMap ; 29 import java.util.List ; 30 import java.util.Locale ; 31 import java.util.Map ; 32 import java.util.TimeZone ; 33 34 66 public class FastDateFormat extends Format { 67 79 82 public static final int FULL = DateFormat.FULL; 83 86 public static final int LONG = DateFormat.LONG; 87 90 public static final int MEDIUM = DateFormat.MEDIUM; 91 94 public static final int SHORT = DateFormat.SHORT; 95 96 static final double LOG_10 = Math.log(10); 98 99 private static String cDefaultPattern; 100 101 private static Map cInstanceCache = new HashMap (7); 102 private static Map cDateInstanceCache = new HashMap (7); 103 private static Map cTimeInstanceCache = new HashMap (7); 104 private static Map cDateTimeInstanceCache = new HashMap (7); 105 private static Map cTimeZoneDisplayCache = new HashMap (7); 106 107 110 private final String mPattern; 111 114 private final TimeZone mTimeZone; 115 118 private final boolean mTimeZoneForced; 119 122 private final Locale mLocale; 123 126 private final boolean mLocaleForced; 127 130 private Rule[] mRules; 131 134 private int mMaxLengthEstimate; 135 136 143 public static FastDateFormat getInstance() { 144 return getInstance(getDefaultPattern(), null, null); 145 } 146 147 156 public static FastDateFormat getInstance(String pattern) { 157 return getInstance(pattern, null, null); 158 } 159 160 171 public static FastDateFormat getInstance(String pattern, TimeZone timeZone) { 172 return getInstance(pattern, timeZone, null); 173 } 174 175 185 public static FastDateFormat getInstance(String pattern, Locale locale) { 186 return getInstance(pattern, null, locale); 187 } 188 189 202 public static synchronized FastDateFormat getInstance(String pattern, TimeZone timeZone, Locale locale) { 203 FastDateFormat emptyFormat = new FastDateFormat(pattern, timeZone, locale); 204 FastDateFormat format = (FastDateFormat) cInstanceCache.get(emptyFormat); 205 if (format == null) { 206 format = emptyFormat; 207 format.init(); cInstanceCache.put(format, format); } 210 return format; 211 } 212 213 224 public static FastDateFormat getDateInstance(int style) { 225 return getDateInstance(style, null, null); 226 } 227 228 239 public static FastDateFormat getDateInstance(int style, Locale locale) { 240 return getDateInstance(style, null, locale); 241 } 242 243 255 public static FastDateFormat getDateInstance(int style, TimeZone timeZone) { 256 return getDateInstance(style, timeZone, null); 257 } 258 270 public static synchronized FastDateFormat getDateInstance(int style, TimeZone timeZone, Locale locale) { 271 Object key = new Integer (style); 272 if (timeZone != null) { 273 key = new Pair(key, timeZone); 274 } 275 if (locale != null) { 276 key = new Pair(key, locale); 277 } 278 279 FastDateFormat format = (FastDateFormat) cDateInstanceCache.get(key); 280 if (format == null) { 281 if (locale == null) { 282 locale = Locale.getDefault(); 283 } 284 285 try { 286 SimpleDateFormat formatter = (SimpleDateFormat ) DateFormat.getDateInstance(style, locale); 287 String pattern = formatter.toPattern(); 288 format = getInstance(pattern, timeZone, locale); 289 cDateInstanceCache.put(key, format); 290 291 } catch (ClassCastException ex) { 292 throw new IllegalArgumentException ("No date pattern for locale: " + locale); 293 } 294 } 295 return format; 296 } 297 298 309 public static FastDateFormat getTimeInstance(int style) { 310 return getTimeInstance(style, null, null); 311 } 312 313 324 public static FastDateFormat getTimeInstance(int style, Locale locale) { 325 return getTimeInstance(style, null, locale); 326 } 327 328 340 public static FastDateFormat getTimeInstance(int style, TimeZone timeZone) { 341 return getTimeInstance(style, timeZone, null); 342 } 343 344 356 public static synchronized FastDateFormat getTimeInstance(int style, TimeZone timeZone, Locale locale) { 357 Object key = new Integer (style); 358 if (timeZone != null) { 359 key = new Pair(key, timeZone); 360 } 361 if (locale != null) { 362 key = new Pair(key, locale); 363 } 364 365 FastDateFormat format = (FastDateFormat) cTimeInstanceCache.get(key); 366 if (format == null) { 367 if (locale == null) { 368 locale = Locale.getDefault(); 369 } 370 371 try { 372 SimpleDateFormat formatter = (SimpleDateFormat ) DateFormat.getTimeInstance(style, locale); 373 String pattern = formatter.toPattern(); 374 format = getInstance(pattern, timeZone, locale); 375 cTimeInstanceCache.put(key, format); 376 377 } catch (ClassCastException ex) { 378 throw new IllegalArgumentException ("No date pattern for locale: " + locale); 379 } 380 } 381 return format; 382 } 383 384 396 public static FastDateFormat getDateTimeInstance( 397 int dateStyle, int timeStyle) { 398 return getDateTimeInstance(dateStyle, timeStyle, null, null); 399 } 400 401 413 public static FastDateFormat getDateTimeInstance( 414 int dateStyle, int timeStyle, Locale locale) { 415 return getDateTimeInstance(dateStyle, timeStyle, null, locale); 416 } 417 418 431 public static FastDateFormat getDateTimeInstance( 432 int dateStyle, int timeStyle, TimeZone timeZone) { 433 return getDateTimeInstance(dateStyle, timeStyle, timeZone, null); 434 } 435 448 public static synchronized FastDateFormat getDateTimeInstance(int dateStyle, int timeStyle, TimeZone timeZone, 449 Locale locale) { 450 451 Object key = new Pair(new Integer (dateStyle), new Integer (timeStyle)); 452 if (timeZone != null) { 453 key = new Pair(key, timeZone); 454 } 455 if (locale != null) { 456 key = new Pair(key, locale); 457 } 458 459 FastDateFormat format = (FastDateFormat) cDateTimeInstanceCache.get(key); 460 if (format == null) { 461 if (locale == null) { 462 locale = Locale.getDefault(); 463 } 464 465 try { 466 SimpleDateFormat formatter = (SimpleDateFormat ) DateFormat.getDateTimeInstance(dateStyle, timeStyle, 467 locale); 468 String pattern = formatter.toPattern(); 469 format = getInstance(pattern, timeZone, locale); 470 cDateTimeInstanceCache.put(key, format); 471 472 } catch (ClassCastException ex) { 473 throw new IllegalArgumentException ("No date time pattern for locale: " + locale); 474 } 475 } 476 return format; 477 } 478 479 490 static synchronized String getTimeZoneDisplay(TimeZone tz, boolean daylight, int style, Locale locale) { 491 Object key = new TimeZoneDisplayKey(tz, daylight, style, locale); 492 String value = (String ) cTimeZoneDisplayCache.get(key); 493 if (value == null) { 494 value = tz.getDisplayName(daylight, style, locale); 496 cTimeZoneDisplayCache.put(key, value); 497 } 498 return value; 499 } 500 501 506 private static synchronized String getDefaultPattern() { 507 if (cDefaultPattern == null) { 508 cDefaultPattern = new SimpleDateFormat ().toPattern(); 509 } 510 return cDefaultPattern; 511 } 512 513 528 protected FastDateFormat(String pattern, TimeZone timeZone, Locale locale) { 529 super(); 530 if (pattern == null) { 531 throw new IllegalArgumentException ("The pattern must not be null"); 532 } 533 mPattern = pattern; 534 535 mTimeZoneForced = (timeZone != null); 536 if (timeZone == null) { 537 timeZone = TimeZone.getDefault(); 538 } 539 mTimeZone = timeZone; 540 541 mLocaleForced = (locale != null); 542 if (locale == null) { 543 locale = Locale.getDefault(); 544 } 545 mLocale = locale; 546 } 547 548 551 protected void init() { 552 List rulesList = parsePattern(); 553 mRules = (Rule[]) rulesList.toArray(new Rule[rulesList.size()]); 554 555 int len = 0; 556 for (int i=mRules.length; --i >= 0; ) { 557 len += mRules[i].estimateLength(); 558 } 559 560 mMaxLengthEstimate = len; 561 } 562 563 571 protected List parsePattern() { 572 DateFormatSymbols symbols = new DateFormatSymbols (mLocale); 573 List rules = new ArrayList (); 574 575 String [] ERAs = symbols.getEras(); 576 String [] months = symbols.getMonths(); 577 String [] shortMonths = symbols.getShortMonths(); 578 String [] weekdays = symbols.getWeekdays(); 579 String [] shortWeekdays = symbols.getShortWeekdays(); 580 String [] AmPmStrings = symbols.getAmPmStrings(); 581 582 int length = mPattern.length(); 583 int[] indexRef = new int[1]; 584 585 for (int i = 0; i < length; i++) { 586 indexRef[0] = i; 587 String token = parseToken(mPattern, indexRef); 588 i = indexRef[0]; 589 590 int tokenLen = token.length(); 591 if (tokenLen == 0) { 592 break; 593 } 594 595 Rule rule; 596 char c = token.charAt(0); 597 598 switch (c) { 599 case 'G': rule = new TextField(Calendar.ERA, ERAs); 601 break; 602 case 'y': if (tokenLen >= 4) { 604 rule = selectNumberRule(Calendar.YEAR, tokenLen); 605 } else { 606 rule = TwoDigitYearField.INSTANCE; 607 } 608 break; 609 case 'M': if (tokenLen >= 4) { 611 rule = new TextField(Calendar.MONTH, months); 612 } else if (tokenLen == 3) { 613 rule = new TextField(Calendar.MONTH, shortMonths); 614 } else if (tokenLen == 2) { 615 rule = TwoDigitMonthField.INSTANCE; 616 } else { 617 rule = UnpaddedMonthField.INSTANCE; 618 } 619 break; 620 case 'd': rule = selectNumberRule(Calendar.DAY_OF_MONTH, tokenLen); 622 break; 623 case 'h': rule = new TwelveHourField(selectNumberRule(Calendar.HOUR, tokenLen)); 625 break; 626 case 'H': rule = selectNumberRule(Calendar.HOUR_OF_DAY, tokenLen); 628 break; 629 case 'm': rule = selectNumberRule(Calendar.MINUTE, tokenLen); 631 break; 632 case 's': rule = selectNumberRule(Calendar.SECOND, tokenLen); 634 break; 635 case 'S': rule = selectNumberRule(Calendar.MILLISECOND, tokenLen); 637 break; 638 case 'E': rule = new TextField(Calendar.DAY_OF_WEEK, tokenLen < 4 ? shortWeekdays : weekdays); 640 break; 641 case 'D': rule = selectNumberRule(Calendar.DAY_OF_YEAR, tokenLen); 643 break; 644 case 'F': rule = selectNumberRule(Calendar.DAY_OF_WEEK_IN_MONTH, tokenLen); 646 break; 647 case 'w': rule = selectNumberRule(Calendar.WEEK_OF_YEAR, tokenLen); 649 break; 650 case 'W': rule = selectNumberRule(Calendar.WEEK_OF_MONTH, tokenLen); 652 break; 653 case 'a': rule = new TextField(Calendar.AM_PM, AmPmStrings); 655 break; 656 case 'k': rule = new TwentyFourHourField(selectNumberRule(Calendar.HOUR_OF_DAY, tokenLen)); 658 break; 659 case 'K': rule = selectNumberRule(Calendar.HOUR, tokenLen); 661 break; 662 case 'z': if (tokenLen >= 4) { 664 rule = new TimeZoneNameRule(mTimeZone, mTimeZoneForced, mLocale, TimeZone.LONG); 665 } else { 666 rule = new TimeZoneNameRule(mTimeZone, mTimeZoneForced, mLocale, TimeZone.SHORT); 667 } 668 break; 669 case 'Z': if (tokenLen == 1) { 671 rule = TimeZoneNumberRule.INSTANCE_NO_COLON; 672 } else { 673 rule = TimeZoneNumberRule.INSTANCE_COLON; 674 } 675 break; 676 case '\'': String sub = token.substring(1); 678 if (sub.length() == 1) { 679 rule = new CharacterLiteral(sub.charAt(0)); 680 } else { 681 rule = new StringLiteral(sub); 682 } 683 break; 684 default: 685 throw new IllegalArgumentException ("Illegal pattern component: " + token); 686 } 687 688 rules.add(rule); 689 } 690 691 return rules; 692 } 693 694 701 protected String parseToken(String pattern, int[] indexRef) { 702 StringBuffer buf = new StringBuffer (); 703 704 int i = indexRef[0]; 705 int length = pattern.length(); 706 707 char c = pattern.charAt(i); 708 if (c >= 'A' && c <= 'Z' || c >= 'a' && c <= 'z') { 709 buf.append(c); 712 713 while (i + 1 < length) { 714 char peek = pattern.charAt(i + 1); 715 if (peek == c) { 716 buf.append(c); 717 i++; 718 } else { 719 break; 720 } 721 } 722 } else { 723 buf.append('\''); 725 726 boolean inLiteral = false; 727 728 for (; i < length; i++) { 729 c = pattern.charAt(i); 730 731 if (c == '\'') { 732 if (i + 1 < length && pattern.charAt(i + 1) == '\'') { 733 i++; 735 buf.append(c); 736 } else { 737 inLiteral = !inLiteral; 738 } 739 } else if (!inLiteral && 740 (c >= 'A' && c <= 'Z' || c >= 'a' && c <= 'z')) { 741 i--; 742 break; 743 } else { 744 buf.append(c); 745 } 746 } 747 } 748 749 indexRef[0] = i; 750 return buf.toString(); 751 } 752 753 760 protected NumberRule selectNumberRule(int field, int padding) { 761 switch (padding) { 762 case 1: 763 return new UnpaddedNumberField(field); 764 case 2: 765 return new TwoDigitNumberField(field); 766 default: 767 return new PaddedNumberField(field, padding); 768 } 769 } 770 771 782 public StringBuffer format(Object obj, StringBuffer toAppendTo, FieldPosition pos) { 783 if (obj instanceof Date ) { 784 return format((Date ) obj, toAppendTo); 785 } else if (obj instanceof Calendar ) { 786 return format((Calendar ) obj, toAppendTo); 787 } else if (obj instanceof Long ) { 788 return format(((Long ) obj).longValue(), toAppendTo); 789 } else { 790 throw new IllegalArgumentException ("Unknown class: " + 791 (obj == null ? "<null>" : obj.getClass().getName())); 792 } 793 } 794 795 |