1 7 8 package com.ibm.icu.text; 9 10 import java.io.IOException ; 11 import java.io.ObjectInputStream ; 12 import java.io.ObjectOutputStream ; 13 import java.text.FieldPosition ; 14 import java.text.MessageFormat ; 15 import java.text.ParsePosition ; 16 import java.util.ArrayList ; 17 import java.util.Date ; 18 import java.util.List ; 19 import java.util.Locale ; 20 import java.util.MissingResourceException ; 21 22 import com.ibm.icu.impl.CalendarData; 23 import com.ibm.icu.impl.DateNumberFormat; 24 import com.ibm.icu.impl.ICUCache; 25 import com.ibm.icu.impl.SimpleCache; 26 import com.ibm.icu.impl.UCharacterProperty; 27 import com.ibm.icu.impl.ZoneMeta; 28 import com.ibm.icu.lang.UCharacter; 29 import com.ibm.icu.util.Calendar; 30 import com.ibm.icu.util.TimeZone; 31 import com.ibm.icu.util.ULocale; 32 33 34 211 public class SimpleDateFormat extends DateFormat { 212 213 private static final long serialVersionUID = 4774881970558875024L; 216 217 static final int currentSerialVersion = 1; 221 222 235 private int serialVersionOnStream = currentSerialVersion; 236 237 242 private String pattern; 243 244 250 private DateFormatSymbols formatData; 251 252 private transient ULocale locale; 253 254 261 private Date defaultCenturyStart; 262 263 private transient int defaultCenturyStartYear; 264 265 private transient long defaultCenturyBase; 268 269 private transient TimeZone parsedTimeZone; 270 271 private static final int millisPerHour = 60 * 60 * 1000; 272 private static final int millisPerMinute = 60 * 1000; 273 274 private static final String GMT_PLUS = "GMT+"; 277 private static final String GMT_MINUS = "GMT-"; 278 private static final String GMT = "GMT"; 279 280 private static final String SUPPRESS_NEGATIVE_PREFIX = "\uAB00"; 284 285 289 private transient boolean useFastFormat; 290 291 299 public SimpleDateFormat() { 300 this(getDefaultPattern(), null, null, null, null, true); 301 } 302 303 309 public SimpleDateFormat(String pattern) 310 { 311 this(pattern, null, null, null, null, true); 312 } 313 314 320 public SimpleDateFormat(String pattern, Locale loc) 321 { 322 this(pattern, null, null, null, ULocale.forLocale(loc), true); 323 } 324 325 332 public SimpleDateFormat(String pattern, ULocale loc) 333 { 334 this(pattern, null, null, null, loc, true); 335 } 336 337 343 public SimpleDateFormat(String pattern, DateFormatSymbols formatData) 344 { 345 this(pattern, (DateFormatSymbols)formatData.clone(), null, null, null, true); 346 } 347 348 352 public SimpleDateFormat(String pattern, DateFormatSymbols formatData, ULocale loc) 353 { 354 this(pattern, (DateFormatSymbols)formatData.clone(), null, null, loc, true); 355 } 356 357 363 SimpleDateFormat(String pattern, DateFormatSymbols formatData, Calendar calendar, ULocale locale, 364 boolean useFastFormat) { 365 this(pattern, (DateFormatSymbols)formatData.clone(), (Calendar)calendar.clone(), null, locale, useFastFormat); 366 } 367 368 371 private SimpleDateFormat(String pattern, DateFormatSymbols formatData, Calendar calendar, 372 NumberFormat numberFormat, ULocale locale, boolean useFastFormat) { 373 this.pattern = pattern; 374 this.formatData = formatData; 375 this.calendar = calendar; 376 this.numberFormat = numberFormat; 377 this.locale = locale; this.useFastFormat = useFastFormat; 379 initialize(); 380 } 381 382 public static SimpleDateFormat getInstance(Calendar.FormatConfiguration formatConfig) { 383 return new SimpleDateFormat(formatConfig.getPatternString(), 384 formatConfig.getDateFormatSymbols(), 385 formatConfig.getCalendar(), 386 null, 387 formatConfig.getLocale(), 388 true); 389 } 390 391 394 private void initialize() { 395 if (locale == null) { 396 locale = ULocale.getDefault(); 397 } 398 if (formatData == null) { 399 formatData = new DateFormatSymbols(locale); 400 } 401 if (calendar == null) { 402 calendar = Calendar.getInstance(locale); 403 } 404 if (numberFormat == null) { 405 numberFormat = new DateNumberFormat(locale); 407 } 408 defaultCenturyBase = System.currentTimeMillis(); 411 412 initLocalZeroPaddingNumberFormat(); 413 } 414 415 private static ULocale cachedDefaultLocale = null; 417 private static String cachedDefaultPattern = null; 418 private static final String FALLBACKPATTERN = "yy/MM/dd HH:mm"; 419 420 424 private static synchronized String getDefaultPattern() { 425 ULocale defaultLocale = ULocale.getDefault(); 426 if (!defaultLocale.equals(cachedDefaultLocale)) { 427 cachedDefaultLocale = defaultLocale; 428 Calendar cal = Calendar.getInstance(cachedDefaultLocale); 429 try { 430 CalendarData calData = new CalendarData(cachedDefaultLocale, cal.getType()); 431 String [] dateTimePatterns = calData.getStringArray("DateTimePatterns"); 432 cachedDefaultPattern = MessageFormat.format(dateTimePatterns[8], 433 new Object [] {dateTimePatterns[SHORT], dateTimePatterns[SHORT + 4]}); 434 } catch (MissingResourceException e) { 435 cachedDefaultPattern = FALLBACKPATTERN; 436 } 437 } 438 return cachedDefaultPattern; 439 } 440 441 444 private void parseAmbiguousDatesAsAfter(Date startDate) { 445 defaultCenturyStart = startDate; 446 calendar.setTime(startDate); 447 defaultCenturyStartYear = calendar.get(Calendar.YEAR); 448 } 449 450 453 private void initializeDefaultCenturyStart(long baseTime) { 454 defaultCenturyBase = baseTime; 455 Calendar tmpCal = (Calendar)calendar.clone(); 458 tmpCal.setTimeInMillis(baseTime); 459 tmpCal.add(Calendar.YEAR, -80); 460 defaultCenturyStart = tmpCal.getTime(); 461 defaultCenturyStartYear = tmpCal.get(Calendar.YEAR); 462 } 463 464 465 private Date getDefaultCenturyStart() { 466 if (defaultCenturyStart == null) { 467 initializeDefaultCenturyStart(defaultCenturyBase); 469 } 470 return defaultCenturyStart; 471 } 472 473 474 private int getDefaultCenturyStartYear() { 475 if (defaultCenturyStart == null) { 476 initializeDefaultCenturyStart(defaultCenturyBase); 478 } 479 return defaultCenturyStartYear; 480 } 481 482 489 public void set2DigitYearStart(Date startDate) { 490 parseAmbiguousDatesAsAfter(startDate); 491 } 492 493 500 public Date get2DigitYearStart() { 501 return getDefaultCenturyStart(); 502 } 503 504 518 public StringBuffer format(Calendar cal, StringBuffer toAppendTo, 519 FieldPosition pos) { 520 pos.setBeginIndex(0); 522 pos.setEndIndex(0); 523 524 528 Object [] items = getPatternItems(); 529 for (int i = 0; i < items.length; i++) { 530 if (items[i] instanceof String ) { 531 toAppendTo.append((String )items[i]); 532 } else { 533 PatternItem item = (PatternItem)items[i]; 534 if (useFastFormat) { 535 subFormat(toAppendTo, item.type, item.length, toAppendTo.length(), pos, cal); 536 } else { 537 toAppendTo.append(subFormat(item.type, item.length, toAppendTo.length(), pos, formatData, cal)); 538 } 539 } 540 } 541 return toAppendTo; 542 } 543 544 private static final int PATTERN_CHAR_BASE = 0x40; 546 private static final int[] PATTERN_CHAR_TO_INDEX = 547 { 548 -1, 22, -1, -1, 10, 9, 11, 0, 5, -1, -1, 16, 26, 2, -1, -1, 550 -1, 27, -1, 8, -1, -1, -1, 13, -1, 18, 23, -1, -1, -1, -1, -1, 552 -1, 14, -1, 25, 3, 19, -1, 21, 15, -1, -1, 4, -1, 6, -1, -1, 554 -1, 28, -1, 7, -1, 20, 24, 12, -1, 1, 17, -1, -1, -1, -1, -1 556 }; 557 558 private static final int[] PATTERN_INDEX_TO_CALENDAR_FIELD = 560 { 561 Calendar.ERA, Calendar.YEAR, Calendar.MONTH, 562 Calendar.DATE, Calendar.HOUR_OF_DAY, Calendar.HOUR_OF_DAY, 563 Calendar.MINUTE, Calendar.SECOND, Calendar.MILLISECOND, 564 Calendar.DAY_OF_WEEK, Calendar.DAY_OF_YEAR, Calendar.DAY_OF_WEEK_IN_MONTH, 565 Calendar.WEEK_OF_YEAR, Calendar.WEEK_OF_MONTH, Calendar.AM_PM, 566 Calendar.HOUR, Calendar.HOUR, Calendar.ZONE_OFFSET, 567 Calendar.YEAR_WOY, Calendar.DOW_LOCAL, Calendar.EXTENDED_YEAR, 568 Calendar.JULIAN_DAY, Calendar.MILLISECONDS_IN_DAY, Calendar.ZONE_OFFSET, 569 Calendar.ZONE_OFFSET, 570 Calendar.DAY_OF_WEEK, 571 Calendar.MONTH, 572 Calendar.MONTH, Calendar.MONTH, 573 }; 574 575 private static final int[] PATTERN_INDEX_TO_DATE_FORMAT_FIELD = { 577 DateFormat.ERA_FIELD, DateFormat.YEAR_FIELD, DateFormat.MONTH_FIELD, 578 DateFormat.DATE_FIELD, DateFormat.HOUR_OF_DAY1_FIELD, DateFormat.HOUR_OF_DAY0_FIELD, 579 DateFormat.MINUTE_FIELD, DateFormat.SECOND_FIELD, DateFormat.FRACTIONAL_SECOND_FIELD, 580 DateFormat.DAY_OF_WEEK_FIELD, DateFormat.DAY_OF_YEAR_FIELD, DateFormat.DAY_OF_WEEK_IN_MONTH_FIELD, 581 DateFormat.WEEK_OF_YEAR_FIELD, DateFormat.WEEK_OF_MONTH_FIELD, DateFormat.AM_PM_FIELD, 582 DateFormat.HOUR1_FIELD, DateFormat.HOUR0_FIELD, DateFormat.TIMEZONE_FIELD, 583 DateFormat.YEAR_WOY_FIELD, DateFormat.DOW_LOCAL_FIELD, DateFormat.EXTENDED_YEAR_FIELD, 584 DateFormat.JULIAN_DAY_FIELD, DateFormat.MILLISECONDS_IN_DAY_FIELD, DateFormat.TIMEZONE_RFC_FIELD, 585 DateFormat.TIMEZONE_GENERIC_FIELD, 586 DateFormat.STANDALONE_DAY_FIELD, 587 DateFormat.STANDALONE_MONTH_FIELD, 588 DateFormat.QUARTER_FIELD, 589 DateFormat.STANDALONE_QUARTER_FIELD, 590 }; 591 592 604 protected String subFormat(char ch, int count, int beginOffset, 605 FieldPosition pos, DateFormatSymbols formatData, 606 Calendar cal) 607 throws IllegalArgumentException 608 { 609 StringBuffer buf = new StringBuffer (); 611 subFormat(buf, ch, count, beginOffset, pos, cal); 612 return buf.toString(); 613 } 614 615 629 protected void subFormat(StringBuffer buf, 630 char ch, int count, int beginOffset, 631 FieldPosition pos, 632 Calendar cal) { 633 final int maxIntCount = Integer.MAX_VALUE; 634 final int bufstart = buf.length(); 635 636 int patternCharIndex = -1; 638 if ('A' <= ch && ch <= 'z') { 639 patternCharIndex = PATTERN_CHAR_TO_INDEX[(int)ch - PATTERN_CHAR_BASE]; 640 } 641 642 if (patternCharIndex == -1) { 643 throw new IllegalArgumentException ("Illegal pattern character " + 644 "'" + ch + "' in \"" + 645 new String (pattern) + '"'); 646 } 647 648 final int field = PATTERN_INDEX_TO_CALENDAR_FIELD[patternCharIndex]; 649 int value = cal.get(field); 650 651 switch (patternCharIndex) { 652 case 0: if (count == 5) { 654 buf.append(formatData.narrowEras[value]); 655 } else if (count == 4) 656 buf.append(formatData.eraNames[value]); 657 else 658 buf.append(formatData.eras[value]); 659 break; 660 case 1: 667 if (count == 2) 668 zeroPaddingNumber(buf, value, 2, 2); else zeroPaddingNumber(buf, value, count, maxIntCount); 671 break; 672 case 2: if (count == 5) 674 buf.append(formatData.narrowMonths[value]); 675 else if (count == 4) 676 buf.append(formatData.months[value]); 677 else if (count == 3) 678 buf.append(formatData.shortMonths[value]); 679 else 680 zeroPaddingNumber(buf, value+1, count, maxIntCount); 681 break; 682 case 4: if (value == 0) 684 zeroPaddingNumber(buf, 685 cal.getMaximum(Calendar.HOUR_OF_DAY)+1, 686 count, maxIntCount); 687 else 688 zeroPaddingNumber(buf, value, count, maxIntCount); 689 break; 690 case 8: { 693 numberFormat.setMinimumIntegerDigits(Math.min(3, count)); 694 numberFormat.setMaximumIntegerDigits(maxIntCount); 695 if (count == 1) { 696 value = (value + 50) / 100; 697 } else if (count == 2) { 698 value = (value + 5) / 10; 699 } 700 FieldPosition p = new FieldPosition (-1); 701 numberFormat.format((long) value, buf, p); 702 if (count > 3) { 703 numberFormat.setMinimumIntegerDigits(count - 3); 704 numberFormat.format(0L, buf, p); 705 } 706 } 707 break; 708 case 9: if (count == 5) { 710 buf.append(formatData.narrowWeekdays[value]); 711 } else if (count == 4) 712 buf.append(formatData.weekdays[value]); 713 else buf.append(formatData.shortWeekdays[value]); 715 break; 716 case 14: buf.append(formatData.ampms[value]); 718 break; 719 case 15: if (value == 0) 721 zeroPaddingNumber(buf, 722 cal.getLeastMaximum(Calendar.HOUR)+1, 723 count, maxIntCount); 724 else 725 zeroPaddingNumber(buf, value, count, maxIntCount); 726 break; 727 case 17: case 24: { 730 731 String zid; 732 String res=null; 733 zid = ZoneMeta.getCanonicalID(cal.getTimeZone().getID()); 734 boolean isGeneric = patternCharIndex == 24; 735 if (zid != null) { 736 if (patternCharIndex == TIMEZONE_GENERIC_FIELD) { 737 if(count < 4){ 738 res = formatData.getZoneString(zid, DateFormatSymbols.TIMEZONE_SHORT_GENERIC); 739 }else{ 740 res = formatData.getZoneString(zid, DateFormatSymbols.TIMEZONE_LONG_GENERIC); 741 } 742 } else { 743 if (cal.get(Calendar.DST_OFFSET) != 0) { 744 if(count < 4){ 745 res = formatData.getZoneString(zid, DateFormatSymbols.TIMEZONE_SHORT_DAYLIGHT); 746 }else{ 747 res = formatData.getZoneString(zid, DateFormatSymbols.TIMEZONE_LONG_DAYLIGHT); 748 } 749 }else{ 750 if(count < 4){ 751 res = formatData.getZoneString(zid, DateFormatSymbols.TIMEZONE_SHORT_STANDARD); 752 }else{ 753 res = formatData.getZoneString(zid, DateFormatSymbols.TIMEZONE_LONG_STANDARD); 754 } 755 } 756 } 757 } 758 if (res == null || res.length() == 0) { 759 if (zid == null || !isGeneric || ZoneMeta.getCanonicalCountry(zid) == null) { 762 long offset = cal.get(Calendar.ZONE_OFFSET) + 763 cal.get(Calendar.DST_OFFSET); 764 res = ZoneMeta.displayGMT(offset, locale); 765 } else { 766 770 res = formatData.getZoneString(zid, DateFormatSymbols.TIMEZONE_EXEMPLAR_CITY); 771 772 if (res == null) { 773 res = ZoneMeta.displayFallback(zid, null, locale); 774 } 775 } 776 } 777 778 if(res.length()==0){ 779 appendGMT(buf, cal); 780 }else{ 781 buf.append(res); 782 } 783 } break; 784 case 23: { 786 if (count < 4) { 787 long val= (cal.get(Calendar.ZONE_OFFSET) + 789 cal.get(Calendar.DST_OFFSET)) / millisPerMinute; 790 char sign = '+'; 791 if (val < 0) { 792 val = -val; 793 sign = '-'; 794 } 795 val = (val / 60) * 100 + (val % 60); buf.append(sign); 797 798 int num = (int)(val % 10000); 800 int denom = 1000; 801 while (denom >= 1) { 802 char digit = (char)((num / denom) + '0'); 803 buf.append(digit); 804 num = num % denom; 805 denom /= 10; 806 } 807 } else { 808 long val = cal.get(Calendar.ZONE_OFFSET) + 811 cal.get(Calendar.DST_OFFSET); 812 buf.append(ZoneMeta.displayGMT(val, locale)); 813 } 814 } 815 break; 816 case 25: if (count == 5) 818 buf.append(formatData.standaloneNarrowWeekdays[value]); 819 else if (count == 4) 820 buf.append(formatData.standaloneWeekdays[value]); 821 else if (count == 3) 822 buf.append(formatData.standaloneShortWeekdays[value]); 823 else 824 zeroPaddingNumber(buf, value, 1, maxIntCount); 825 break; 826 case 26: if (count == 5) 828 buf.append(formatData.standaloneNarrowMonths[value]); 829 else if (count == 4) 830 buf.append(formatData.standaloneMonths[value]); 831 else if (count == 3) 832 buf.append(formatData.standaloneShortMonths[value]); 833 else 834 zeroPaddingNumber(buf, value+1, count, maxIntCount); 835 break; 836 case 27: if (count >= 4) 838 buf.append(formatData.quarters[value/3]); 839 else if (count == 3) 840 buf.append(formatData.shortQuarters[value/3]); 841 else 842 zeroPaddingNumber(buf, (value/3)+1, count, maxIntCount); 843 break; 844 case 28: if (count >= 4) 846 buf.append(formatData.standaloneQuarters[value/3]); 847 else if (count == 3) 848 buf.append(formatData.standaloneShortQuarters[value/3]); 849 else 850 zeroPaddingNumber(buf, (value/3)+1, count, maxIntCount); 851 break; 852 default: 853 868 zeroPaddingNumber(buf, value, count, maxIntCount); 869 break; 870 } 872 if (pos.getBeginIndex() == pos.getEndIndex() && 874 pos.getField() == PATTERN_INDEX_TO_DATE_FORMAT_FIELD[patternCharIndex]) { 875 pos.setBeginIndex(beginOffset); 876 pos.setEndIndex(beginOffset + buf.length() - bufstart); 877 } 878 } 879 880 883 private static class PatternItem { 884 final char type; 885 final int length; 886 final boolean isNumeric; 887 888 PatternItem(char type, int length) { 889 this.type = type; 890 this.length = length; 891 isNumeric = isNumeric(type, length); 892 } 893 } 894 895 private static ICUCache PARSED_PATTERN_CACHE = new SimpleCache(); 896 private transient Object [] patternItems; 897 898 902 private Object [] getPatternItems() { 903 if (patternItems != null) { 904 return patternItems; 905 } 906 907 patternItems = (Object [])PARSED_PATTERN_CACHE.get(pattern); 908 if (patternItems != null) { 909 return patternItems; 910 } 911 912 boolean isPrevQuote = false; 913 boolean inQuote = false; 914 StringBuffer text = new StringBuffer (); 915 char itemType = 0; int itemLength = 1; 917 918 List items = new ArrayList (); 919 920 for (int i = 0; i < pattern.length(); i++) { 921 char ch = pattern.charAt(i); 922 if (ch == '\'') { 923 if (isPrevQuote) { 924 text.append('\''); 925 isPrevQuote = false; 926 } else { 927 isPrevQuote = true; 928 if (itemType != 0) { 929 items.add(new PatternItem(itemType, itemLength)); 930 itemType = 0; 931 } 932 } 933 inQuote = !inQuote; 934 } else { 935 isPrevQuote = false; 936 if (inQuote) { 937 text.append(ch); 938 } else { 939 if ((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z')) { 940 if (ch == itemType) { 942 itemLength++; 943 } else { 944 if (itemType == 0) { 945 if (text.length() > 0) { 946 items.add(text.toString()); 947 text.setLength(0); 948 } 949 } else { 950 items.add(new PatternItem(itemType, itemLength)); 951 } 952 itemType = ch; 953 itemLength = 1; 954 } 955 } else { 956 if (itemType != 0) { 958 items.add(new PatternItem(itemType, itemLength)); 959 itemType = 0; 960 } 961 text.append(ch); 962 } 963 } 964 } 965 } 966 if (itemType == 0) { 968 if (text.length() > 0) { 969 items.add(text.toString()); 970 text.setLength(0); 971 } 972 } else { 973 items.add(new PatternItem(itemType, itemLength)); 974 } 975 976 patternItems = new Object [items.size()]; 977 items.toArray(patternItems); 978 979 PARSED_PATTERN_CACHE.put(pattern, patternItems); 980 981 return patternItems; 982 } 983 984 private void appendGMT(StringBuffer buf, Calendar cal){ 985 int value = cal.get(Calendar.ZONE_OFFSET) + 986 cal.get(Calendar.DST_OFFSET); 987 988 if (value < 0) { 989 buf.append(GMT_MINUS); 990 value = -value; }else{ 992 buf.append(GMT_PLUS); 993 } 994 995 zeroPaddingNumber(buf, (int)(value/millisPerHour), 2, 2); 996 buf.append((char)0x003A) ; 997 zeroPaddingNumber(buf, (int)((value%millisPerHour)/millisPerMinute), 2, 2); 998 } 999 1003 1012 1013 1019 protected void zeroPaddingNumber(StringBuffer buf, int value, 1020 int minDigits, int maxDigits) { 1021 if (useLocalZeroPaddingNumberFormat) { 1022 fastZeroPaddingNumber(buf, value, minDigits, maxDigits); 1023 } else { 1024 numberFormat.setMinimumIntegerDigits(minDigits); 1025 numberFormat.setMaximumIntegerDigits(maxDigits); 1026 numberFormat.format(value, buf, new FieldPosition (-1)); 1027 } 1028 } 1029 1030 1034 public void setNumberFormat(NumberFormat newNumberFormat) { 1035 super.setNumberFormat(newNumberFormat); 1037 initLocalZeroPaddingNumberFormat(); 1038 } 1039 1040 private void initLocalZeroPaddingNumberFormat() { 1041 if (numberFormat instanceof DecimalFormat) { 1042 zeroDigit = ((DecimalFormat)numberFormat).getDecimalFormatSymbols().getZeroDigit(); 1043 useLocalZeroPaddingNumberFormat = true; 1044 } else if (numberFormat instanceof DateNumberFormat) { 1045 zeroDigit = ((DateNumberFormat)numberFormat).getZeroDigit(); 1046 useLocalZeroPaddingNumberFormat = true; 1047 } else { 1048 useLocalZeroPaddingNumberFormat = false; 1049 } 1050 1051 if (useLocalZeroPaddingNumberFormat) { 1052 decimalBuf = new char[10]; } 1054 } 1055 1056 private transient boolean useLocalZeroPaddingNumberFormat; 1058 private transient char zeroDigit; 1059 private transient char[] decimalBuf; 1060 1061 1072 private void fastZeroPaddingNumber(StringBuffer buf, int value, int minDigits, int maxDigits) { 1073 int limit = decimalBuf.length < maxDigits ? decimalBuf.length : maxDigits; 1074 int index = limit - 1; 1075 while (true) { 1076 decimalBuf[index] = (char)((value % 10) + zeroDigit); 1077 value /= 10; 1078 if (index == 0 || value == 0) { 1079 break; 1080 } 1081 index--; 1082 } 1083 int padding = minDigits - (limit - index); 1084 for (; padding > 0; padding--) { 1085 decimalBuf[--index] = zeroDigit; 1086 } 1087 int length = limit - index; 1088 buf.append(decimalBuf, index, length); 1089 } 1090 1091 1095 protected String zeroPaddingNumber(long value, int minDigits, int maxDigits) 1096 { 1097 numberFormat.setMinimumIntegerDigits(minDigits); 1098 numberFormat.setMaximumIntegerDigits(maxDigits); 1099 return numberFormat.format(value); 1100 } 1101 1102 1106 private static final String NUMERIC_FORMAT_CHARS = "MyudhHmsSDFwWkK"; 1107 1108 1112 private static final boolean isNumeric(char formatChar, int count) { 1113 int i = NUMERIC_FORMAT_CHARS.indexOf(formatChar); 1114 return (i > 0 || (i == 0 && count < 3)); 1115 } 1116 1117 1122 public void parse(String text, Calendar cal, ParsePosition parsePos) 1123 { 1124 int pos = parsePos.getIndex(); 1125 int start = pos; 1126 boolean[] ambiguousYear = { false }; 1127 1128 parsedTimeZone = null; 1130 1131 int numericFieldStart = -1; 1133 int numericFieldLength = 0; 1135 int numericStartPos = 0; 1137 1138 Object [] items = getPatternItems(); 1139 int i = 0; 1140 while (i < items.length) { 1141 if (items[i] instanceof PatternItem) { 1142 PatternItem field = (PatternItem)items[i]; 1144 if (field.isNumeric) { 1145 if (numericFieldStart == -1) { 1153 if ((i + 1) < items.length 1155 && (items[i + 1] instanceof PatternItem) 1156 && ((PatternItem)items[i + 1]).isNumeric) { 1157 numericFieldStart = i; 1159 numericFieldLength = field.length; 1160 numericStartPos = pos; 1161 } 1162 } 1163 } 1164 if (numericFieldStart != -1) { 1165 int len = field.length; 1167 if (numericFieldStart == i) { 1168 len = numericFieldLength; 1169 } 1170 1171 pos = subParse(text, pos, field.type, len, 1173 true, false, ambiguousYear, cal); 1174 1175 if (pos < 0) { 1176 --numericFieldLength; 1180 if (numericFieldLength == 0) { 1181 parsePos.setIndex(start); 1183 parsePos.setErrorIndex(pos); 1184 return; 1185 } 1186 i = numericFieldStart; 1187 pos = numericStartPos; 1188 continue; 1189 } 1190 1191 } else { 1192 numericFieldStart = -1; 1194 1195 int s = pos; 1196 pos = subParse(text, pos, field.type, field.length, 1197 false, true, ambiguousYear, cal); 1198 if (pos < 0) { 1199 parsePos.setIndex(start); 1200 parsePos.setErrorIndex(s); 1201 return; 1202 } 1203 } 1204 } else { 1205 numericFieldStart = -1; 1207 1208 String patl = (String )items[i]; 1209 int plen = patl.length(); 1210 int tlen = text.length(); 1211 int idx = 0; 1212 while (idx < plen && pos < tlen) { 1213 char pch = patl.charAt(idx); 1214 char ich = text.charAt(pos); 1215 if (UCharacterProperty.isRuleWhiteSpace(pch) && UCharacterProperty.isRuleWhiteSpace(ich)) { 1216 while ((idx + 1) < plen && 1219 UCharacterProperty.isRuleWhiteSpace(patl.charAt(idx + 1))) { 1220 ++idx; 1221 } 1222 while ((pos + 1) < tlen && 1223 UCharacterProperty.isRuleWhiteSpace(text.charAt(pos + 1))) { 1224 ++pos; 1225 } 1226 } else if (pch != ich) { 1227 break; 1228 } 1229 ++idx; 1230 ++pos; 1231 } 1232 if (idx != plen) { 1233 parsePos.setIndex(start); 1235 parsePos.setErrorIndex(pos); 1236 return; 1237 } 1238 } 1239 ++i; 1240 } 1241 1242 1246 parsePos.setIndex(pos); 1247 1248 1263 try { 1271 if (ambiguousYear[0] || parsedTimeZone != null) { 1272 Calendar copy = (Calendar)cal.clone(); 1277 if (ambiguousYear[0]) { Date parsedDate = copy.getTime(); 1279 if (parsedDate.before(getDefaultCenturyStart())) { 1280 cal.set(Calendar.YEAR, getDefaultCenturyStartYear() + 100); 1282 } 1283 } 1284 1285 if (parsedTimeZone != null) { 1286 TimeZone tz = parsedTimeZone; 1287 1288 int[] offsets = new int[2]; 1292 tz.getOffset(copy.getTimeInMillis()+tz.getRawOffset(), true, offsets); 1293 1294 cal.set(Calendar.ZONE_OFFSET, offsets[0]); 1295 cal.set(Calendar.DST_OFFSET, offsets[1]); 1296 cal.setTimeZone(tz); 1297 } 1298 } 1299 } 1300 catch (IllegalArgumentException e) { 1303 parsePos.setErrorIndex(pos); 1304 parsePos.setIndex(start); 1305 } 1306 } 1307 1308 1325 protected int matchString(String text, int start, int field, String [] data, Calendar cal) 1326 { 1327 int i = 0; 1328 int count = data.length; 1329 1330 if (field == Calendar.DAY_OF_WEEK) i = 1; 1331 1332 int bestMatchLength = 0, bestMatch = -1; 1337 for (; i<count; ++i) 1338 { 1339 int length = data[i].length(); 1340 if (length > bestMatchLength && 1343 text.regionMatches(true, start, data[i], 0, length)) 1344 { 1345 bestMatch = i; 1346 bestMatchLength = length; 1347 } 1348 } 1349 if (bestMatch >= 0) 1350 { 1351 cal.set(field, bestMatch); 1352 return start + bestMatchLength; 1353 } 1354 return -start; 1355 } 1356 1357 1374 protected int matchQuarterString(String text, int start, int field, String [] data, Calendar cal) 1375 { 1376 int i = 0; 1377 int count = data.length; 1378 1379 int bestMatchLength = 0, bestMatch = -1; 1384 for (; i<count; ++i) { 1385 int length = data[i].length(); 1386 if (length > bestMatchLength && 1389 text.regionMatches(true, start, data[i], 0, length)) { 1390 bestMatch = i; 1391 bestMatchLength = length; 1392 } 1393 } 1394 1395 if (bestMatch >= 0) { 1396 cal.set(field, bestMatch * 3); 1397 return start + bestMatchLength; 1398 } 1399 1400 return -start; 1401 } 1402 1403 1406 private int subParseZoneString(String text, int start, Calendar cal) { 1407 1411 TimeZone tz = null; 1413 String zid = null, value = null; 1414 int type = -1; 1415 1416 DateFormatSymbols.ZoneItem item = formatData.findZoneIDTypeValue(text, start); 1417 if (item != null) { 1418 zid = item.zid; 1419 value = item.value; 1420 type = item.type; 1421 } 1422 1423 if (zid != null) { 1424 tz = TimeZone.getTimeZone(zid); 1425 } 1426 1427 if (tz != null) { cal.set(Calendar.ZONE_OFFSET, tz.getRawOffset()); 1431 if (type == DateFormatSymbols.TIMEZONE_SHORT_STANDARD 1432 || type == DateFormatSymbols.TIMEZONE_LONG_STANDARD) { 1433 cal.set(Calendar.DST_OFFSET, 0); 1435 tz = null; 1436 } else if (type == DateFormatSymbols.TIMEZONE_SHORT_DAYLIGHT 1437 || type == DateFormatSymbols.TIMEZONE_LONG_DAYLIGHT) { 1438 cal.set(Calendar.DST_OFFSET, millisPerHour); 1442 tz = null; 1443 } else { 1444 parsedTimeZone = tz; 1448 } 1449 if(value != null) { 1450 return start + value.length(); 1451 } 1452 } 1453 return 0; 1455 } 1456 1457 1476 protected int subParse(String text, int start, char ch, int count, 1477 boolean obeyCount, boolean allowNegative, 1478 boolean[] ambiguousYear, Calendar cal) 1479 { 1480 Number number = null; 1481 int value = 0; 1482 int i; 1483 ParsePosition pos = new ParsePosition (0); 1484 int patternCharIndex = -1; 1486 if ('A' <= ch && ch <= 'z') { 1487 patternCharIndex = PATTERN_CHAR_TO_INDEX[(int)ch - PATTERN_CHAR_BASE]; 1488 } 1489 1490 if (patternCharIndex == -1) { 1491 return -start; 1492 } 1493 1494 int field = PATTERN_INDEX_TO_CALENDAR_FIELD[patternCharIndex]; 1495 1496 for (;;) { 1499 if (start >= text.length()) { 1500 return -start; 1501 } 1502 int c = UTF16.charAt(text, start); 1503 if (!UCharacter.isUWhiteSpace(c)) { 1504 break; 1505 } 1506 start += UTF16.getCharCount(c); 1507 } 1508 pos.setIndex(start); 1509 1510 if (patternCharIndex == 4 || 1515 patternCharIndex == 15 || 1516 (patternCharIndex == 2 && count <= 2) || 1517 patternCharIndex == 1 || 1518 patternCharIndex == 8) 1519 { 1520 if (obeyCount) 1523 { 1524 if ((start+count) > text.length()) return -start; 1525 number = parseInt(text.substring(0, start+count), pos, allowNegative); 1526 } 1527 else number = parseInt(text, pos, allowNegative); 1528 if (number == null) 1529 return -start; 1530 value = number.intValue(); 1531 } 1532 1533 switch (patternCharIndex) 1534 { 1535 case 0: if (count == 4) { 1537 return matchString(text, start, Calendar.ERA, formatData.eraNames, cal); 1538 } else { 1539 return matchString(text, start, Calendar.ERA, formatData.eras, cal); 1540 } 1541 case 1: 1549 if (count == 2 && (pos.getIndex() - start) == 2 1550 && Character.isDigit(text.charAt(start)) 1551 && Character.isDigit(text.charAt(start+1))) 1552 { 1553 int ambiguousTwoDigitYear = getDefaultCenturyStartYear() % 100; 1562 ambiguousYear[0] = value == ambiguousTwoDigitYear; 1563 value += (getDefaultCenturyStartYear()/100)*100 + 1564 (value < ambiguousTwoDigitYear ? 100 : 0); 1565 } 1566 cal.set(Calendar.YEAR, value); 1567 return pos.getIndex(); 1568 case 2: if (count <= 2) { 1571 cal.set(Calendar.MONTH, value - 1); 1575 return pos.getIndex(); 1576 } 1577 else 1578 { 1579 int newStart = matchString(text, start, Calendar.MONTH, 1583 formatData.months, cal); 1584 if (newStart > 0) { 1585 return newStart; 1586 } else { return matchString(text, start, Calendar.MONTH, 1588 formatData.shortMonths, cal); 1589 } 1590 } 1591 case 26: if (count <= 2) { 1594 cal.set(Calendar.MONTH, value - 1); 1598 return pos.getIndex(); 1599 } 1600 else 1601 { 1602 int newStart = matchString(text, start, Calendar.MONTH, 1606 formatData.standaloneMonths, cal); 1607 if (newStart > 0) { 1608 return newStart; 1609 } else { return matchString(text, start, Calendar.MONTH, 1611 formatData.standaloneShortMonths, cal); 1612 } 1613 } 1614 case 4: if (value == cal.getMaximum(Calendar.HOUR_OF_DAY)+1) value = 0; 1617 cal.set(Calendar.HOUR_OF_DAY, value); 1618 return pos.getIndex(); 1619 case 8: i = pos.getIndex() - start; 1622 if (i < 3) { 1623 while (i < 3) { 1624 value *= 10; 1625 i++; 1626 } 1627 } else { 1628 int a = 1; 1629 while (i > 3) { 1630 a *= 10; 1631 i--; 1632 } 1633 value = (value + (a>>1)) / a; 1634 } 1635 cal.set(Calendar.MILLISECOND, value); 1636 return pos.getIndex(); 1637 case 9: { int newStart = matchString(text, start, Calendar.DAY_OF_WEEK, 1641 formatData.weekdays, cal); 1642 if (newStart > 0) { 1643 return newStart; 1644 } else { return matchString(text, start, Calendar.DAY_OF_WEEK, 1646 formatData.shortWeekdays, cal); 1647 } 1648 } 1649 case 25: { int newStart = matchString(text, start, Calendar.DAY_OF_WEEK, 1653 formatData.standaloneWeekdays, cal); 1654 if (newStart > 0) { 1655 return newStart; 1656 } else { return matchString(text, start, Calendar.DAY_OF_WEEK, 1658 formatData.standaloneShortWeekdays, cal); 1659 } 1660 } 1661 case 14: return matchString(text, start, Calendar.AM_PM, formatData.ampms, cal); 1663 case 15: if (value == cal.getLeastMaximum(Calendar.HOUR)+1) value = 0; 1666 cal.set(Calendar.HOUR, value); 1667 return pos.getIndex(); 1668 case 17: case 23: case 24: { 1676 int sign = 0; 1677 int offset; 1678 1679 if ((text.length() - start) >= GMT.length() && 1685 text.regionMatches(true, start, GMT, 0, GMT.length())) 1686 { 1687 cal.set(Calendar.DST_OFFSET, 0); 1688 1689 pos.setIndex(start + GMT.length()); 1690 1691 try { switch (text.charAt(pos.getIndex())) { 1693 case '+': 1694 sign = 1; 1695 break; 1696 case '-': 1697 sign = -1; 1698 break; 1699 } 1700 } catch(StringIndexOutOfBoundsException e) { 1701 } 1702 if (sign == 0) { 1703 cal.set(Calendar.ZONE_OFFSET, 0 ); 1704 return pos.getIndex(); 1705 } 1706 1707 pos.setIndex(pos.getIndex() + 1); 1709 int st = pos.getIndex(); 1710 Number tzNumber = numberFormat.parse(text, pos); 1711 if( tzNumber == null) { 1712 return -start; 1713 } 1714 if( pos.getIndex() < text.length() && 1715 text.charAt(pos.getIndex()) == ':' ) { 1716 1717 offset = tzNumber.intValue() * 60; 1719 pos.setIndex(pos.getIndex() + 1); 1720 tzNumber = numberFormat.parse(text, pos); 1721 if( tzNumber == null) { 1722 return -start; 1723 } 1724 offset += tzNumber.intValue(); 1725 } 1726 else { 1727 offset = tzNumber.intValue(); 1729 if( offset < 24 && (pos.getIndex() - st) <= 2) 1731 offset *= 60; 1732 else 1733 offset = offset % 100 + offset / 100 * 60; 1735 } 1736 1737 } 1739 else { 1740 i = subParseZoneString(text, start, cal); 1744 if (i != 0) 1745 return i; 1746 1747 DecimalFormat fmt = new DecimalFormat("+####;-####"); 1752 fmt.setParseIntegerOnly(true); 1753 Number tzNumber = fmt.parse( text, pos ); 1754 if( tzNumber == null) { 1755 return -start; } 1757 offset = tzNumber.intValue(); 1758 sign = 1; 1759 if( offset < 0 ) { 1760 sign = -1; 1761 offset = -offset; 1762 } 1763 if( offset < 24 && (pos.getIndex() - start) <= 3) 1765 offset = offset * 60; 1766 else 1767 offset = offset % 100 + offset / 100 * 60; 1768 1769 } 1771 1772 1775 offset *= millisPerMinute * sign; 1777 1778 if (cal.getTimeZone().useDaylightTime()) 1779 { 1780 cal.set(Calendar.DST_OFFSET, millisPerHour); 1781 offset -= millisPerHour; 1782 } 1783 cal.set(Calendar.ZONE_OFFSET, offset); 1784 1785 return pos.getIndex(); 1786 } 1787 1788 case 27: if (count <= 2) { 1791 cal.set(Calendar.MONTH, (value - 1) * 3); 1795 return pos.getIndex(); 1796 } 1797 else 1798 { 1799 int newStart = matchQuarterString(text, start, Calendar.MONTH, 1803 formatData.quarters, cal); 1804 if (newStart > 0) { 1805 return newStart; 1806 } else { return matchQuarterString(text, start, Calendar.MONTH, 1808 formatData.shortQuarters, cal); 1809 } 1810 } 1811 1812 case 28: if (count <= 2) { 1815 cal.set(Calendar.MONTH, (value - 1) * 3); 1819 return pos.getIndex(); 1820 } 1821 else 1822 { 1823 int newStart = matchQuarterString(text, start, Calendar.MONTH, 1827 formatData.standaloneQuarters, cal); 1828 if (newStart > 0) { 1829 return newStart; 1830 } else { return matchQuarterString(text, start, Calendar.MONTH, 1832 formatData.standaloneShortQuarters, cal); 1833 } 1834 } 1835 1836 default: 1837 1852 if (obeyCount) 1854 { 1855 if ((start+count) > text.length()) return -start; 1856 number = parseInt(text.substring(0, start+count), pos, allowNegative); 1857 } 1858 else number = parseInt(text, pos, allowNegative); 1859 if (number != null) { 1860 cal.set(field, number.intValue()); 1861 return pos.getIndex(); 1862 } 1863 return -start; 1864 } 1865 } 1866 1867 1871 private Number parseInt(String text, 1872 ParsePosition pos, 1873 boolean allowNegative) { 1874 Number number; 1875 if (allowNegative) { 1876 number = numberFormat.parse(text, pos); 1877 } else { 1878 if (numberFormat instanceof DecimalFormat) { 1880 String oldPrefix = ((DecimalFormat)numberFormat).getNegativePrefix(); 1881 ((DecimalFormat)numberFormat).setNegativePrefix(SUPPRESS_NEGATIVE_PREFIX); 1882 number = numberFormat.parse(text, pos); 1883 ((DecimalFormat)numberFormat).setNegativePrefix(oldPrefix); 1884 } else { 1885 boolean dateNumberFormat = (numberFormat instanceof DateNumberFormat); 1886 if (dateNumberFormat) { 1887 ((DateNumberFormat)numberFormat).setParsePositiveOnly(true); 1888 } 1889 number = numberFormat.parse(text, pos); 1890 if (dateNumberFormat) { 1891 ((DateNumberFormat)numberFormat).setParsePositiveOnly(false); 1892 } 1893 } 1894 } 1895 return number; 1896 } 1897 1898 1902 private String translatePattern(String pattern, String from, String to) { 1903 StringBuffer result = new StringBuffer (); 1904 boolean inQuote = false; 1905 for (int i = 0; i < pattern.length(); ++i) { 1906 char c = pattern.charAt(i); 1907 if (inQuote) { 1908 if (c == '\'') 1909 inQuote = false; 1910 } 1911 else { 1912 if (c == '\'') 1913 inQuote = true; 1914 else if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')) { 1915 int ci = from.indexOf(c); 1916 if (ci != -1) { 1917 c = to.charAt(ci); 1918 } 1919 } 1922 } 1923 result.append(c); 1924 } 1925 if (inQuote) 1926 throw new IllegalArgumentException ("Unfinished quote in pattern"); 1927 return result.toString(); 1928 } 1929 1930 1934 public String toPattern() { 1935 return pattern; 1936 } 1937 1938 1942 public String toLocalizedPattern() { 1943 return translatePattern(pattern, 1944 DateFormatSymbols.patternChars, 1945 formatData.localPatternChars); 1946 } 1947 1948 1952 public void applyPattern(String pattern) 1953 { 1954 this.pattern = pattern; 1955 setLocale(null, null); 1956 patternItems = null; 1958 } 1959 1960 1964 public void applyLocalizedPattern(String pattern) { 1965 this.pattern = translatePattern(pattern, 1966 formatData.localPatternChars, 1967 DateFormatSymbols.patternChars); 1968 setLocale(null, null); 1969 } 1970 1971 1977 public DateFormatSymbols getDateFormatSymbols() 1978 { 1979 return (DateFormatSymbols)formatData.clone(); 1980 } 1981 1982 1987 public void setDateFormatSymbols(DateFormatSymbols newFormatSymbols) 1988 { 1989 this.formatData = (DateFormatSymbols)newFormatSymbols.clone(); 1990 } 1991 1992 1996 protected DateFormatSymbols getSymbols() { 1997 return formatData; 1998 } 1999 2000 2004 public Object clone() { 2005 SimpleDateFormat other = (SimpleDateFormat) super.clone(); 2006 other.formatData = (DateFormatSymbols) formatData.clone(); 2007 return other; 2008 } 2009 2010 2015 public int hashCode() 2016 { 2017 return pattern.hashCode(); 2018 } 2020 2021 2025 public boolean equals(Object obj) 2026 { 2027 if (!super.equals(obj)) return false; SimpleDateFormat that = (SimpleDateFormat) obj; 2029 return (pattern.equals(that.pattern) 2030 && formatData.equals(that.formatData)); 2031 } 2032 2033 2036 private void writeObject(ObjectOutputStream stream) throws IOException { 2037 if (defaultCenturyStart == null) { 2038 initializeDefaultCenturyStart(defaultCenturyBase); 2041 } 2042 stream.defaultWriteObject(); 2043 } 2044 2045 2048 private void readObject(ObjectInputStream stream) 2049 throws IOException , ClassNotFoundException { 2050 stream.defaultReadObject(); 2051 if (serialVersionOnStream < 1) { 2054 defaultCenturyBase = System.currentTimeMillis(); 2056 } 2057 else { 2059 parseAmbiguousDatesAsAfter(defaultCenturyStart); 2061 } 2062 serialVersionOnStream = currentSerialVersion; 2063 locale = getLocale(ULocale.VALID_LOCALE); 2064 2065 initLocalZeroPaddingNumberFormat(); 2066 } 2067} 2068 | Popular Tags |