--- old/src/share/classes/java/text/SimpleDateFormat.java Mon Aug 13 12:03:05 2012 +++ new/src/share/classes/java/text/SimpleDateFormat.java Mon Aug 13 12:03:03 2012 @@ -41,23 +41,20 @@ import java.io.IOException; import java.io.InvalidObjectException; import java.io.ObjectInputStream; +import static java.text.DateFormatSymbols.*; import java.util.Calendar; import java.util.Date; import java.util.GregorianCalendar; import java.util.Locale; import java.util.Map; -import java.util.MissingResourceException; -import java.util.ResourceBundle; import java.util.SimpleTimeZone; import java.util.TimeZone; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; +import sun.util.locale.provider.LocaleProviderAdapter; import sun.util.calendar.CalendarUtils; import sun.util.calendar.ZoneInfoFile; -import sun.util.resources.LocaleData; -import static java.text.DateFormatSymbols.*; - /** * SimpleDateFormat is a concrete class for formatting and * parsing dates in a locale-sensitive manner. It allows for formatting @@ -117,95 +114,100 @@ * 2009; 09 * * M - * Month in year + * Month in year (context sensitive) * Month * July; Jul; 07 * + * L + * Month in year (standalone form) + * Month + * July; Jul; 07 + * * w * Week in year * Number * 27 - * + * * W * Week in month * Number * 2 - * + * * D * Day in year * Number * 189 - * + * * d * Day in month * Number * 10 - * + * * F * Day of week in month * Number * 2 - * + * * E * Day name in week * Text * Tuesday; Tue - * + * * u * Day number of week (1 = Monday, ..., 7 = Sunday) * Number * 1 - * + * * a * Am/pm marker * Text * PM - * + * * H * Hour in day (0-23) * Number * 0 - * + * * k * Hour in day (1-24) * Number * 24 - * + * * K * Hour in am/pm (0-11) * Number * 0 - * + * * h * Hour in am/pm (1-12) * Number * 12 - * + * * m * Minute in hour * Number * 30 - * + * * s * Second in minute * Number * 55 - * + * * S * Millisecond * Number * 978 - * + * * z * Time zone * General time zone * Pacific Standard Time; PST; GMT-08:00 - * + * * Z * Time zone * RFC 822 time zone * -0800 - * + * * X * Time zone * ISO 8601 time zone @@ -270,7 +272,17 @@ *
  • Month: * If the number of pattern letters is 3 or more, the month is * interpreted as text; otherwise, - * it is interpreted as a number.

  • + * it is interpreted as a number.
    + * + *
    *
  • General time zone: * Time zones are interpreted as text if they have * names. For time zones representing a GMT offset value, the @@ -459,6 +471,11 @@ transient private boolean hasFollowingMinusSign = false; /** + * True if standalone form needs to be used. + */ + transient private boolean forceStandaloneForm = false; + + /** * The compiled pattern. */ transient private char[] compiledPattern; @@ -502,16 +519,10 @@ private static final String GMT = "GMT"; /** - * Cache to hold the DateTimePatterns of a Locale. - */ - private static final ConcurrentMap cachedLocaleData - = new ConcurrentHashMap(3); - - /** * Cache NumberFormat instances with Locale key. */ private static final ConcurrentMap cachedNumberFormatData - = new ConcurrentHashMap(3); + = new ConcurrentHashMap<>(3); /** * The Locale used to instantiate this @@ -541,7 +552,9 @@ * class. */ public SimpleDateFormat() { - this(SHORT, SHORT, Locale.getDefault(Locale.Category.FORMAT)); + this("", Locale.getDefault(Locale.Category.FORMAT)); + applyPatternImpl(LocaleProviderAdapter.getResourceBundleBased().getLocaleResources(locale) + .getDateTimePattern(SHORT, SHORT, calendar)); } /** @@ -608,51 +621,6 @@ useDateFormatSymbols = true; } - /* Package-private, called by DateFormat factory methods */ - SimpleDateFormat(int timeStyle, int dateStyle, Locale loc) { - if (loc == null) { - throw new NullPointerException(); - } - - this.locale = loc; - // initialize calendar and related fields - initializeCalendar(loc); - - /* try the cache first */ - String[] dateTimePatterns = cachedLocaleData.get(loc); - if (dateTimePatterns == null) { /* cache miss */ - ResourceBundle r = LocaleData.getDateFormatData(loc); - if (!isGregorianCalendar()) { - try { - dateTimePatterns = r.getStringArray(getCalendarName() + ".DateTimePatterns"); - } catch (MissingResourceException e) { - } - } - if (dateTimePatterns == null) { - dateTimePatterns = r.getStringArray("DateTimePatterns"); - } - /* update cache */ - cachedLocaleData.putIfAbsent(loc, dateTimePatterns); - } - formatData = DateFormatSymbols.getInstanceRef(loc); - if ((timeStyle >= 0) && (dateStyle >= 0)) { - Object[] dateTimeArgs = {dateTimePatterns[timeStyle], - dateTimePatterns[dateStyle + 4]}; - pattern = MessageFormat.format(dateTimePatterns[8], dateTimeArgs); - } - else if (timeStyle >= 0) { - pattern = dateTimePatterns[timeStyle]; - } - else if (dateStyle >= 0) { - pattern = dateTimePatterns[dateStyle + 4]; - } - else { - throw new IllegalArgumentException("No date or time style specified"); - } - - initialize(loc); - } - /* Initialize compiledPattern and numberFormat fields */ private void initialize(Locale loc) { // Verify and compile the given pattern. @@ -750,10 +718,10 @@ private char[] compile(String pattern) { int length = pattern.length(); boolean inQuote = false; - StringBuilder compiledPattern = new StringBuilder(length * 2); + StringBuilder compiledCode = new StringBuilder(length * 2); StringBuilder tmpBuffer = null; - int count = 0; - int lastTag = -1; + int count = 0, tagcount = 0; + int lastTag = -1, prevTag = -1; for (int i = 0; i < length; i++) { char c = pattern.charAt(i); @@ -766,7 +734,9 @@ if (c == '\'') { i++; if (count != 0) { - encode(lastTag, count, compiledPattern); + encode(lastTag, count, compiledCode); + tagcount++; + prevTag = lastTag; lastTag = -1; count = 0; } @@ -773,7 +743,7 @@ if (inQuote) { tmpBuffer.append(c); } else { - compiledPattern.append((char)(TAG_QUOTE_ASCII_CHAR << 8 | c)); + compiledCode.append((char)(TAG_QUOTE_ASCII_CHAR << 8 | c)); } continue; } @@ -780,7 +750,9 @@ } if (!inQuote) { if (count != 0) { - encode(lastTag, count, compiledPattern); + encode(lastTag, count, compiledCode); + tagcount++; + prevTag = lastTag; lastTag = -1; count = 0; } @@ -795,14 +767,14 @@ if (len == 1) { char ch = tmpBuffer.charAt(0); if (ch < 128) { - compiledPattern.append((char)(TAG_QUOTE_ASCII_CHAR << 8 | ch)); + compiledCode.append((char)(TAG_QUOTE_ASCII_CHAR << 8 | ch)); } else { - compiledPattern.append((char)(TAG_QUOTE_CHARS << 8 | 1)); - compiledPattern.append(ch); + compiledCode.append((char)(TAG_QUOTE_CHARS << 8 | 1)); + compiledCode.append(ch); } } else { - encode(TAG_QUOTE_CHARS, len, compiledPattern); - compiledPattern.append(tmpBuffer); + encode(TAG_QUOTE_CHARS, len, compiledCode); + compiledCode.append(tmpBuffer); } inQuote = false; } @@ -814,13 +786,15 @@ } if (!(c >= 'a' && c <= 'z' || c >= 'A' && c <= 'Z')) { if (count != 0) { - encode(lastTag, count, compiledPattern); + encode(lastTag, count, compiledCode); + tagcount++; + prevTag = lastTag; lastTag = -1; count = 0; } if (c < 128) { // In most cases, c would be a delimiter, such as ':'. - compiledPattern.append((char)(TAG_QUOTE_ASCII_CHAR << 8 | c)); + compiledCode.append((char)(TAG_QUOTE_ASCII_CHAR << 8 | c)); } else { // Take any contiguous non-ASCII alphabet characters and // put them in a single TAG_QUOTE_CHARS. @@ -831,9 +805,9 @@ break; } } - compiledPattern.append((char)(TAG_QUOTE_CHARS << 8 | (j - i))); + compiledCode.append((char)(TAG_QUOTE_CHARS << 8 | (j - i))); for (; i < j; i++) { - compiledPattern.append(pattern.charAt(i)); + compiledCode.append(pattern.charAt(i)); } i--; } @@ -850,7 +824,9 @@ count++; continue; } - encode(lastTag, count, compiledPattern); + encode(lastTag, count, compiledCode); + tagcount++; + prevTag = lastTag; lastTag = tag; count = 1; } @@ -860,13 +836,17 @@ } if (count != 0) { - encode(lastTag, count, compiledPattern); + encode(lastTag, count, compiledCode); + tagcount++; + prevTag = lastTag; } + forceStandaloneForm = (tagcount == 1 && prevTag == PATTERN_MONTH); + // Copy the compiled pattern to a char array - int len = compiledPattern.length(); + int len = compiledCode.length(); char[] r = new char[len]; - compiledPattern.getChars(0, len, r, 0); + compiledCode.getChars(0, len, r, 0); return r; } @@ -873,7 +853,7 @@ /** * Encodes the given tag and length and puts encoded char(s) into buffer. */ - private static final void encode(int tag, int length, StringBuilder buffer) { + private static void encode(int tag, int length, StringBuilder buffer) { if (tag == PATTERN_ISO_ZONE && length >= 4) { throw new IllegalArgumentException("invalid ISO 8601 format: length=" + length); } @@ -941,6 +921,7 @@ * @return the formatted date-time string. * @exception NullPointerException if the given {@code date} is {@code null}. */ + @Override public StringBuffer format(Date date, StringBuffer toAppendTo, FieldPosition pos) { @@ -999,6 +980,7 @@ * @return AttributedCharacterIterator describing the formatted value. * @since 1.4 */ + @Override public AttributedCharacterIterator formatToCharacterIterator(Object obj) { StringBuffer sb = new StringBuffer(); CharacterIteratorFieldDelegate delegate = new @@ -1022,47 +1004,84 @@ } // Map index into pattern character string to Calendar field number - private static final int[] PATTERN_INDEX_TO_CALENDAR_FIELD = - { - Calendar.ERA, Calendar.YEAR, Calendar.MONTH, Calendar.DATE, - Calendar.HOUR_OF_DAY, Calendar.HOUR_OF_DAY, Calendar.MINUTE, - Calendar.SECOND, Calendar.MILLISECOND, Calendar.DAY_OF_WEEK, - Calendar.DAY_OF_YEAR, Calendar.DAY_OF_WEEK_IN_MONTH, - Calendar.WEEK_OF_YEAR, Calendar.WEEK_OF_MONTH, - Calendar.AM_PM, Calendar.HOUR, Calendar.HOUR, Calendar.ZONE_OFFSET, + private static final int[] PATTERN_INDEX_TO_CALENDAR_FIELD = { + Calendar.ERA, + Calendar.YEAR, + Calendar.MONTH, + Calendar.DATE, + Calendar.HOUR_OF_DAY, + Calendar.HOUR_OF_DAY, + Calendar.MINUTE, + Calendar.SECOND, + Calendar.MILLISECOND, + Calendar.DAY_OF_WEEK, + Calendar.DAY_OF_YEAR, + Calendar.DAY_OF_WEEK_IN_MONTH, + Calendar.WEEK_OF_YEAR, + Calendar.WEEK_OF_MONTH, + Calendar.AM_PM, + Calendar.HOUR, + Calendar.HOUR, Calendar.ZONE_OFFSET, - // Pseudo Calendar fields - CalendarBuilder.WEEK_YEAR, - CalendarBuilder.ISO_DAY_OF_WEEK, - Calendar.ZONE_OFFSET + Calendar.ZONE_OFFSET, + CalendarBuilder.WEEK_YEAR, // Pseudo Calendar field + CalendarBuilder.ISO_DAY_OF_WEEK, // Pseudo Calendar field + Calendar.ZONE_OFFSET, + Calendar.MONTH }; // Map index into pattern character string to DateFormat field number private static final int[] PATTERN_INDEX_TO_DATE_FORMAT_FIELD = { - DateFormat.ERA_FIELD, DateFormat.YEAR_FIELD, DateFormat.MONTH_FIELD, - DateFormat.DATE_FIELD, DateFormat.HOUR_OF_DAY1_FIELD, - DateFormat.HOUR_OF_DAY0_FIELD, DateFormat.MINUTE_FIELD, - DateFormat.SECOND_FIELD, DateFormat.MILLISECOND_FIELD, - DateFormat.DAY_OF_WEEK_FIELD, DateFormat.DAY_OF_YEAR_FIELD, - DateFormat.DAY_OF_WEEK_IN_MONTH_FIELD, DateFormat.WEEK_OF_YEAR_FIELD, - DateFormat.WEEK_OF_MONTH_FIELD, DateFormat.AM_PM_FIELD, - DateFormat.HOUR1_FIELD, DateFormat.HOUR0_FIELD, - DateFormat.TIMEZONE_FIELD, DateFormat.TIMEZONE_FIELD, - DateFormat.YEAR_FIELD, DateFormat.DAY_OF_WEEK_FIELD, - DateFormat.TIMEZONE_FIELD + DateFormat.ERA_FIELD, + DateFormat.YEAR_FIELD, + DateFormat.MONTH_FIELD, + DateFormat.DATE_FIELD, + DateFormat.HOUR_OF_DAY1_FIELD, + DateFormat.HOUR_OF_DAY0_FIELD, + DateFormat.MINUTE_FIELD, + DateFormat.SECOND_FIELD, + DateFormat.MILLISECOND_FIELD, + DateFormat.DAY_OF_WEEK_FIELD, + DateFormat.DAY_OF_YEAR_FIELD, + DateFormat.DAY_OF_WEEK_IN_MONTH_FIELD, + DateFormat.WEEK_OF_YEAR_FIELD, + DateFormat.WEEK_OF_MONTH_FIELD, + DateFormat.AM_PM_FIELD, + DateFormat.HOUR1_FIELD, + DateFormat.HOUR0_FIELD, + DateFormat.TIMEZONE_FIELD, + DateFormat.TIMEZONE_FIELD, + DateFormat.YEAR_FIELD, + DateFormat.DAY_OF_WEEK_FIELD, + DateFormat.TIMEZONE_FIELD, + DateFormat.MONTH_FIELD }; // Maps from DecimalFormatSymbols index to Field constant private static final Field[] PATTERN_INDEX_TO_DATE_FORMAT_FIELD_ID = { - Field.ERA, Field.YEAR, Field.MONTH, Field.DAY_OF_MONTH, - Field.HOUR_OF_DAY1, Field.HOUR_OF_DAY0, Field.MINUTE, - Field.SECOND, Field.MILLISECOND, Field.DAY_OF_WEEK, - Field.DAY_OF_YEAR, Field.DAY_OF_WEEK_IN_MONTH, - Field.WEEK_OF_YEAR, Field.WEEK_OF_MONTH, - Field.AM_PM, Field.HOUR1, Field.HOUR0, Field.TIME_ZONE, + Field.ERA, + Field.YEAR, + Field.MONTH, + Field.DAY_OF_MONTH, + Field.HOUR_OF_DAY1, + Field.HOUR_OF_DAY0, + Field.MINUTE, + Field.SECOND, + Field.MILLISECOND, + Field.DAY_OF_WEEK, + Field.DAY_OF_YEAR, + Field.DAY_OF_WEEK_IN_MONTH, + Field.WEEK_OF_YEAR, + Field.WEEK_OF_MONTH, + Field.AM_PM, + Field.HOUR1, + Field.HOUR0, Field.TIME_ZONE, - Field.YEAR, Field.DAY_OF_WEEK, - Field.TIME_ZONE + Field.TIME_ZONE, + Field.YEAR, + Field.DAY_OF_WEEK, + Field.TIME_ZONE, + Field.MONTH }; /** @@ -1094,7 +1113,8 @@ } int style = (count >= 4) ? Calendar.LONG : Calendar.SHORT; - if (!useDateFormatSymbols && field != CalendarBuilder.ISO_DAY_OF_WEEK) { + if (!useDateFormatSymbols && field < Calendar.ZONE_OFFSET + && patternCharIndex != PATTERN_MONTH_STANDALONE) { current = calendar.getDisplayName(field, style, locale); } @@ -1106,20 +1126,23 @@ case PATTERN_ERA: // 'G' if (useDateFormatSymbols) { String[] eras = formatData.getEras(); - if (value < eras.length) + if (value < eras.length) { current = eras[value]; + } } - if (current == null) + if (current == null) { current = ""; + } break; case PATTERN_WEEK_YEAR: // 'Y' case PATTERN_YEAR: // 'y' if (calendar instanceof GregorianCalendar) { - if (count != 2) + if (count != 2) { zeroPaddingNumber(value, count, maxIntCount, buffer); - else // count == 2 - zeroPaddingNumber(value, 2, 2, buffer); // clip 1996 to 96 + } else { + zeroPaddingNumber(value, 2, 2, buffer); + } // clip 1996 to 96 } else { if (current == null) { zeroPaddingNumber(value, style == Calendar.LONG ? 1 : count, @@ -1128,7 +1151,7 @@ } break; - case PATTERN_MONTH: // 'M' + case PATTERN_MONTH: // 'M' (context seinsive) if (useDateFormatSymbols) { String[] months; if (count >= 4) { @@ -1141,6 +1164,11 @@ } else { if (count < 3) { current = null; + } else if (forceStandaloneForm) { + current = calendar.getDisplayName(field, style | 0x8000, locale); + if (current == null) { + current = calendar.getDisplayName(field, style, locale); + } } } if (current == null) { @@ -1148,13 +1176,35 @@ } break; + case PATTERN_MONTH_STANDALONE: // 'L' + assert current == null; + if (locale == null) { + String[] months; + if (count >= 4) { + months = formatData.getMonths(); + current = months[value]; + } else if (count == 3) { + months = formatData.getShortMonths(); + current = months[value]; + } + } else { + if (count >= 3) { + current = calendar.getDisplayName(field, style | 0x8000, locale); + } + } + if (current == null) { + zeroPaddingNumber(value+1, count, maxIntCount, buffer); + } + break; + case PATTERN_HOUR_OF_DAY1: // 'k' 1-based. eg, 23:59 + 1 hour =>> 24:59 if (current == null) { - if (value == 0) - zeroPaddingNumber(calendar.getMaximum(Calendar.HOUR_OF_DAY)+1, + if (value == 0) { + zeroPaddingNumber(calendar.getMaximum(Calendar.HOUR_OF_DAY) + 1, count, maxIntCount, buffer); - else + } else { zeroPaddingNumber(value, count, maxIntCount, buffer); + } } break; @@ -1180,11 +1230,12 @@ case PATTERN_HOUR1: // 'h' 1-based. eg, 11PM + 1 hour =>> 12 AM if (current == null) { - if (value == 0) - zeroPaddingNumber(calendar.getLeastMaximum(Calendar.HOUR)+1, + if (value == 0) { + zeroPaddingNumber(calendar.getLeastMaximum(Calendar.HOUR) + 1, count, maxIntCount, buffer); - else + } else { zeroPaddingNumber(value, count, maxIntCount, buffer); + } } break; @@ -1289,7 +1340,7 @@ /** * Formats a number with the specified minimum and maximum number of digits. */ - private final void zeroPaddingNumber(int value, int minDigits, int maxDigits, StringBuffer buffer) + private void zeroPaddingNumber(int value, int minDigits, int maxDigits, StringBuffer buffer) { // Optimization for 1, 2 and 4 digit numbers. This should // cover most cases of formatting date/time related items. @@ -1371,6 +1422,7 @@ * error, returns null. * @exception NullPointerException if text or pos is null. */ + @Override public Date parse(String text, ParsePosition pos) { checkNegativeNumberExpression(); @@ -1504,7 +1556,9 @@ int i = 0; int count = data.length; - if (field == Calendar.DAY_OF_WEEK) i = 1; + if (field == Calendar.DAY_OF_WEEK) { + i = 1; + } // There may be multiple strings in the data[] array which begin with // the same prefix (e.g., Cerven and Cervenec (June and July) in Czech). @@ -1767,7 +1821,9 @@ return -1; } char c = text.charAt(pos.index); - if (c != ' ' && c != '\t') break; + if (c != ' ' && c != '\t') { + break; + } ++pos.index; } @@ -1912,8 +1968,9 @@ } } // [We computed 'value' above.] - if (value == calendar.getMaximum(Calendar.HOUR_OF_DAY)+1) + if (value == calendar.getMaximum(Calendar.HOUR_OF_DAY) + 1) { value = 0; + } calb.set(Calendar.HOUR_OF_DAY, value); return pos.index; @@ -1966,8 +2023,9 @@ } } // [We computed 'value' above.] - if (value == calendar.getLeastMaximum(Calendar.HOUR)+1) + if (value == calendar.getLeastMaximum(Calendar.HOUR) + 1) { value = 0; + } calb.set(Calendar.HOUR, value); return pos.index; @@ -2111,21 +2169,14 @@ return -1; } - private final String getCalendarName() { - return calendar.getClass().getName(); - } - + /** + * Returns true if the DateFormatSymbols has been set explicitly or locale + * is null. + */ private boolean useDateFormatSymbols() { - if (useDateFormatSymbols) { - return true; - } - return isGregorianCalendar() || locale == null; + return useDateFormatSymbols || locale == null; } - private boolean isGregorianCalendar() { - return "java.util.GregorianCalendar".equals(getCalendarName()); - } - /** * Translates a pattern, mapping each character in the from string to the * corresponding character in the to string. @@ -2138,13 +2189,14 @@ for (int i = 0; i < pattern.length(); ++i) { char c = pattern.charAt(i); if (inQuote) { - if (c == '\'') + if (c == '\'') { inQuote = false; + } } else { - if (c == '\'') + if (c == '\'') { inQuote = true; - else if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')) { + } else if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')) { int ci = from.indexOf(c); if (ci >= 0) { // patternChars is longer than localPatternChars due @@ -2162,8 +2214,9 @@ } result.append(c); } - if (inQuote) + if (inQuote) { throw new IllegalArgumentException("Unfinished quote in pattern"); + } return result.toString(); } @@ -2196,6 +2249,10 @@ */ public void applyPattern(String pattern) { + applyPatternImpl(pattern); + } + + private void applyPatternImpl(String pattern) { compiledPattern = compile(pattern); this.pattern = pattern; } @@ -2246,6 +2303,7 @@ * * @return a clone of this SimpleDateFormat */ + @Override public Object clone() { SimpleDateFormat other = (SimpleDateFormat) super.clone(); other.formatData = (DateFormatSymbols) formatData.clone(); @@ -2257,6 +2315,7 @@ * * @return the hash code value for this SimpleDateFormat object. */ + @Override public int hashCode() { return pattern.hashCode(); @@ -2270,9 +2329,12 @@ * @return true if the given object is equal to this * SimpleDateFormat */ + @Override public boolean equals(Object obj) { - if (!super.equals(obj)) return false; // super does class check + if (!super.equals(obj)) { + return false; // super does class check + } SimpleDateFormat that = (SimpleDateFormat) obj; return (pattern.equals(that.pattern) && formatData.equals(that.formatData));