src/share/classes/java/text/SimpleDateFormat.java

Print this page
rev 5696 : 6336885: RFE: Locale Data Deployment Enhancements
4609153: Provide locale data for Indic locales
5104387: Support for gl_ES locale (galician language)
6337471: desktop/system locale preferences support
7056139: (cal) SPI support for locale-dependent Calendar parameters
7058206: Provide CalendarData SPI for week params and display field value names
7073852: Support multiple scripts for digits and decimal symbols per locale
7079560: [Fmt-Da] Context dependent month names support in SimpleDateFormat
7171324: getAvailableLocales() of locale sensitive services should return the actual availability of locales
7151414: (cal) Support calendar type identification
7168528: LocaleServiceProvider needs to be aware of Locale extensions
7171372: (cal) locale's default Calendar should be created if unknown calendar is specified
Summary: JEP 127: Improve Locale Data Packaging and Adopt Unicode CLDR Data (part 1 w/o packaging changes. by Naoto Sato and Masayoshi Okutsu)

*** 39,65 **** package java.text; import java.io.IOException; import java.io.InvalidObjectException; import java.io.ObjectInputStream; 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.calendar.CalendarUtils; import sun.util.calendar.ZoneInfoFile; - import sun.util.resources.LocaleData; - import static java.text.DateFormatSymbols.*; - /** * <code>SimpleDateFormat</code> is a concrete class for formatting and * parsing dates in a locale-sensitive manner. It allows for formatting * (date -> text), parsing (text -> date), and normalization. * --- 39,62 ---- package java.text; 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.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; /** * <code>SimpleDateFormat</code> is a concrete class for formatting and * parsing dates in a locale-sensitive manner. It allows for formatting * (date -> text), parsing (text -> date), and normalization. *
*** 115,213 **** * <td>Week year * <td><a href="#year">Year</a> * <td><code>2009</code>; <code>09</code> * <tr bgcolor="#eeeeff"> * <td><code>M</code> ! * <td>Month in year * <td><a href="#month">Month</a> * <td><code>July</code>; <code>Jul</code>; <code>07</code> * <tr> * <td><code>w</code> * <td>Week in year * <td><a href="#number">Number</a> * <td><code>27</code> ! * <tr bgcolor="#eeeeff"> * <td><code>W</code> * <td>Week in month * <td><a href="#number">Number</a> * <td><code>2</code> ! * <tr> * <td><code>D</code> * <td>Day in year * <td><a href="#number">Number</a> * <td><code>189</code> ! * <tr bgcolor="#eeeeff"> * <td><code>d</code> * <td>Day in month * <td><a href="#number">Number</a> * <td><code>10</code> ! * <tr> * <td><code>F</code> * <td>Day of week in month * <td><a href="#number">Number</a> * <td><code>2</code> ! * <tr bgcolor="#eeeeff"> * <td><code>E</code> * <td>Day name in week * <td><a href="#text">Text</a> * <td><code>Tuesday</code>; <code>Tue</code> ! * <tr> * <td><code>u</code> * <td>Day number of week (1 = Monday, ..., 7 = Sunday) * <td><a href="#number">Number</a> * <td><code>1</code> ! * <tr bgcolor="#eeeeff"> * <td><code>a</code> * <td>Am/pm marker * <td><a href="#text">Text</a> * <td><code>PM</code> ! * <tr> * <td><code>H</code> * <td>Hour in day (0-23) * <td><a href="#number">Number</a> * <td><code>0</code> ! * <tr bgcolor="#eeeeff"> * <td><code>k</code> * <td>Hour in day (1-24) * <td><a href="#number">Number</a> * <td><code>24</code> ! * <tr> * <td><code>K</code> * <td>Hour in am/pm (0-11) * <td><a href="#number">Number</a> * <td><code>0</code> ! * <tr bgcolor="#eeeeff"> * <td><code>h</code> * <td>Hour in am/pm (1-12) * <td><a href="#number">Number</a> * <td><code>12</code> ! * <tr> * <td><code>m</code> * <td>Minute in hour * <td><a href="#number">Number</a> * <td><code>30</code> ! * <tr bgcolor="#eeeeff"> * <td><code>s</code> * <td>Second in minute * <td><a href="#number">Number</a> * <td><code>55</code> ! * <tr> * <td><code>S</code> * <td>Millisecond * <td><a href="#number">Number</a> * <td><code>978</code> ! * <tr bgcolor="#eeeeff"> * <td><code>z</code> * <td>Time zone * <td><a href="#timezone">General time zone</a> * <td><code>Pacific Standard Time</code>; <code>PST</code>; <code>GMT-08:00</code> ! * <tr> * <td><code>Z</code> * <td>Time zone * <td><a href="#rfc822timezone">RFC 822 time zone</a> * <td><code>-0800</code> ! * <tr bgcolor="#eeeeff"> * <td><code>X</code> * <td>Time zone * <td><a href="#iso8601timezone">ISO 8601 time zone</a> * <td><code>-08</code>; <code>-0800</code>; <code>-08:00</code> * </table> --- 112,215 ---- * <td>Week year * <td><a href="#year">Year</a> * <td><code>2009</code>; <code>09</code> * <tr bgcolor="#eeeeff"> * <td><code>M</code> ! * <td>Month in year (context sensitive) * <td><a href="#month">Month</a> * <td><code>July</code>; <code>Jul</code>; <code>07</code> * <tr> + * <td><code>L</code> + * <td>Month in year (standalone form) + * <td><a href="#month">Month</a> + * <td><code>July</code>; <code>Jul</code>; <code>07</code> + * <tr bgcolor="#eeeeff"> * <td><code>w</code> * <td>Week in year * <td><a href="#number">Number</a> * <td><code>27</code> ! * <tr> * <td><code>W</code> * <td>Week in month * <td><a href="#number">Number</a> * <td><code>2</code> ! * <tr bgcolor="#eeeeff"> * <td><code>D</code> * <td>Day in year * <td><a href="#number">Number</a> * <td><code>189</code> ! * <tr> * <td><code>d</code> * <td>Day in month * <td><a href="#number">Number</a> * <td><code>10</code> ! * <tr bgcolor="#eeeeff"> * <td><code>F</code> * <td>Day of week in month * <td><a href="#number">Number</a> * <td><code>2</code> ! * <tr> * <td><code>E</code> * <td>Day name in week * <td><a href="#text">Text</a> * <td><code>Tuesday</code>; <code>Tue</code> ! * <tr bgcolor="#eeeeff"> * <td><code>u</code> * <td>Day number of week (1 = Monday, ..., 7 = Sunday) * <td><a href="#number">Number</a> * <td><code>1</code> ! * <tr> * <td><code>a</code> * <td>Am/pm marker * <td><a href="#text">Text</a> * <td><code>PM</code> ! * <tr bgcolor="#eeeeff"> * <td><code>H</code> * <td>Hour in day (0-23) * <td><a href="#number">Number</a> * <td><code>0</code> ! * <tr> * <td><code>k</code> * <td>Hour in day (1-24) * <td><a href="#number">Number</a> * <td><code>24</code> ! * <tr bgcolor="#eeeeff"> * <td><code>K</code> * <td>Hour in am/pm (0-11) * <td><a href="#number">Number</a> * <td><code>0</code> ! * <tr> * <td><code>h</code> * <td>Hour in am/pm (1-12) * <td><a href="#number">Number</a> * <td><code>12</code> ! * <tr bgcolor="#eeeeff"> * <td><code>m</code> * <td>Minute in hour * <td><a href="#number">Number</a> * <td><code>30</code> ! * <tr> * <td><code>s</code> * <td>Second in minute * <td><a href="#number">Number</a> * <td><code>55</code> ! * <tr bgcolor="#eeeeff"> * <td><code>S</code> * <td>Millisecond * <td><a href="#number">Number</a> * <td><code>978</code> ! * <tr> * <td><code>z</code> * <td>Time zone * <td><a href="#timezone">General time zone</a> * <td><code>Pacific Standard Time</code>; <code>PST</code>; <code>GMT-08:00</code> ! * <tr bgcolor="#eeeeff"> * <td><code>Z</code> * <td>Time zone * <td><a href="#rfc822timezone">RFC 822 time zone</a> * <td><code>-0800</code> ! * <tr> * <td><code>X</code> * <td>Time zone * <td><a href="#iso8601timezone">ISO 8601 time zone</a> * <td><code>-08</code>; <code>-0800</code>; <code>-08:00</code> * </table>
*** 268,278 **** * java.util.Calendar#isWeekDateSupported() * isWeekDateSupported()}.<br><br></li> * <li><strong><a name="month">Month:</a></strong> * If the number of pattern letters is 3 or more, the month is * interpreted as <a href="#text">text</a>; otherwise, ! * it is interpreted as a <a href="#number">number</a>.<br><br></li> * <li><strong><a name="timezone">General time zone:</a></strong> * Time zones are interpreted as <a href="#text">text</a> if they have * names. For time zones representing a GMT offset value, the * following syntax is used: * <pre> --- 270,290 ---- * java.util.Calendar#isWeekDateSupported() * isWeekDateSupported()}.<br><br></li> * <li><strong><a name="month">Month:</a></strong> * If the number of pattern letters is 3 or more, the month is * interpreted as <a href="#text">text</a>; otherwise, ! * it is interpreted as a <a href="#number">number</a>.<br> ! * <ul> ! * <li>Letter <em>M</em> produces context-sensitive month names, such as the ! * embedded form of names. If a {@code DateFormatSymbols} has been set ! * explicitly with constructor {@link #SimpleDateFormat(String, ! * DateFormatSymbols)} or method {@link ! * #setDateFormatSymbols(DateFormatSymbols)}, the month names given by ! * the {@code DateFormatSymbols} are used.</li> ! * <li>Letter <em>L</em> produces the standalone form of month names.</li> ! * </ul> ! * <br></li> * <li><strong><a name="timezone">General time zone:</a></strong> * Time zones are interpreted as <a href="#text">text</a> if they have * names. For time zones representing a GMT offset value, the * following syntax is used: * <pre>
*** 457,466 **** --- 469,483 ---- * (True as default in Arabic.) */ 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; /**
*** 500,519 **** // For time zones that have no names, use strings GMT+minutes and // GMT-minutes. For instance, in France the time zone is GMT+60. private static final String GMT = "GMT"; /** - * Cache to hold the DateTimePatterns of a Locale. - */ - private static final ConcurrentMap<Locale, String[]> cachedLocaleData - = new ConcurrentHashMap<Locale, String[]>(3); - - /** * Cache NumberFormat instances with Locale key. */ private static final ConcurrentMap<Locale, NumberFormat> cachedNumberFormatData ! = new ConcurrentHashMap<Locale, NumberFormat>(3); /** * The Locale used to instantiate this * <code>SimpleDateFormat</code>. The value may be null if this object * has been created by an older <code>SimpleDateFormat</code> and --- 517,530 ---- // For time zones that have no names, use strings GMT+minutes and // GMT-minutes. For instance, in France the time zone is GMT+60. private static final String GMT = "GMT"; /** * Cache NumberFormat instances with Locale key. */ private static final ConcurrentMap<Locale, NumberFormat> cachedNumberFormatData ! = new ConcurrentHashMap<>(3); /** * The Locale used to instantiate this * <code>SimpleDateFormat</code>. The value may be null if this object * has been created by an older <code>SimpleDateFormat</code> and
*** 539,549 **** * <b>Note:</b> This constructor may not support all locales. * For full coverage, use the factory methods in the {@link DateFormat} * class. */ public SimpleDateFormat() { ! this(SHORT, SHORT, Locale.getDefault(Locale.Category.FORMAT)); } /** * Constructs a <code>SimpleDateFormat</code> using the given pattern and * the default date format symbols for the default locale. --- 550,562 ---- * <b>Note:</b> This constructor may not support all locales. * For full coverage, use the factory methods in the {@link DateFormat} * class. */ public SimpleDateFormat() { ! this("", Locale.getDefault(Locale.Category.FORMAT)); ! applyPatternImpl(LocaleProviderAdapter.getResourceBundleBased().getLocaleResources(locale) ! .getDateTimePattern(SHORT, SHORT, calendar)); } /** * Constructs a <code>SimpleDateFormat</code> using the given pattern and * the default date format symbols for the default locale.
*** 606,660 **** initializeCalendar(this.locale); initialize(this.locale); 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. compiledPattern = compile(pattern); --- 619,628 ----
*** 748,761 **** * @exception IllegalArgumentException if the given pattern is invalid */ private char[] compile(String pattern) { int length = pattern.length(); boolean inQuote = false; ! StringBuilder compiledPattern = new StringBuilder(length * 2); StringBuilder tmpBuffer = null; ! int count = 0; ! int lastTag = -1; for (int i = 0; i < length; i++) { char c = pattern.charAt(i); if (c == '\'') { --- 716,729 ---- * @exception IllegalArgumentException if the given pattern is invalid */ private char[] compile(String pattern) { int length = pattern.length(); boolean inQuote = false; ! StringBuilder compiledCode = new StringBuilder(length * 2); StringBuilder tmpBuffer = null; ! int count = 0, tagcount = 0; ! int lastTag = -1, prevTag = -1; for (int i = 0; i < length; i++) { char c = pattern.charAt(i); if (c == '\'') {
*** 764,788 **** if ((i + 1) < length) { c = pattern.charAt(i + 1); if (c == '\'') { i++; if (count != 0) { ! encode(lastTag, count, compiledPattern); lastTag = -1; count = 0; } if (inQuote) { tmpBuffer.append(c); } else { ! compiledPattern.append((char)(TAG_QUOTE_ASCII_CHAR << 8 | c)); } continue; } } if (!inQuote) { if (count != 0) { ! encode(lastTag, count, compiledPattern); lastTag = -1; count = 0; } if (tmpBuffer == null) { tmpBuffer = new StringBuilder(length); --- 732,760 ---- if ((i + 1) < length) { c = pattern.charAt(i + 1); if (c == '\'') { i++; if (count != 0) { ! encode(lastTag, count, compiledCode); ! tagcount++; ! prevTag = lastTag; lastTag = -1; count = 0; } if (inQuote) { tmpBuffer.append(c); } else { ! compiledCode.append((char)(TAG_QUOTE_ASCII_CHAR << 8 | c)); } continue; } } if (!inQuote) { if (count != 0) { ! encode(lastTag, count, compiledCode); ! tagcount++; ! prevTag = lastTag; lastTag = -1; count = 0; } if (tmpBuffer == null) { tmpBuffer = new StringBuilder(length);
*** 793,810 **** } else { int len = tmpBuffer.length(); if (len == 1) { char ch = tmpBuffer.charAt(0); if (ch < 128) { ! compiledPattern.append((char)(TAG_QUOTE_ASCII_CHAR << 8 | ch)); } else { ! compiledPattern.append((char)(TAG_QUOTE_CHARS << 8 | 1)); ! compiledPattern.append(ch); } } else { ! encode(TAG_QUOTE_CHARS, len, compiledPattern); ! compiledPattern.append(tmpBuffer); } inQuote = false; } continue; } --- 765,782 ---- } else { int len = tmpBuffer.length(); if (len == 1) { char ch = tmpBuffer.charAt(0); if (ch < 128) { ! compiledCode.append((char)(TAG_QUOTE_ASCII_CHAR << 8 | ch)); } else { ! compiledCode.append((char)(TAG_QUOTE_CHARS << 8 | 1)); ! compiledCode.append(ch); } } else { ! encode(TAG_QUOTE_CHARS, len, compiledCode); ! compiledCode.append(tmpBuffer); } inQuote = false; } continue; }
*** 812,828 **** tmpBuffer.append(c); continue; } if (!(c >= 'a' && c <= 'z' || c >= 'A' && c <= 'Z')) { if (count != 0) { ! encode(lastTag, count, compiledPattern); 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)); } else { // Take any contiguous non-ASCII alphabet characters and // put them in a single TAG_QUOTE_CHARS. int j; for (j = i + 1; j < length; j++) { --- 784,802 ---- tmpBuffer.append(c); continue; } if (!(c >= 'a' && c <= 'z' || c >= 'A' && c <= 'Z')) { if (count != 0) { ! encode(lastTag, count, compiledCode); ! tagcount++; ! prevTag = lastTag; lastTag = -1; count = 0; } if (c < 128) { // In most cases, c would be a delimiter, such as ':'. ! 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. int j; for (j = i + 1; j < length; j++) {
*** 829,841 **** char d = pattern.charAt(j); if (d == '\'' || (d >= 'a' && d <= 'z' || d >= 'A' && d <= 'Z')) { break; } } ! compiledPattern.append((char)(TAG_QUOTE_CHARS << 8 | (j - i))); for (; i < j; i++) { ! compiledPattern.append(pattern.charAt(i)); } i--; } continue; } --- 803,815 ---- char d = pattern.charAt(j); if (d == '\'' || (d >= 'a' && d <= 'z' || d >= 'A' && d <= 'Z')) { break; } } ! compiledCode.append((char)(TAG_QUOTE_CHARS << 8 | (j - i))); for (; i < j; i++) { ! compiledCode.append(pattern.charAt(i)); } i--; } continue; }
*** 848,881 **** if (lastTag == -1 || lastTag == tag) { lastTag = tag; count++; continue; } ! encode(lastTag, count, compiledPattern); lastTag = tag; count = 1; } if (inQuote) { throw new IllegalArgumentException("Unterminated quote"); } if (count != 0) { ! encode(lastTag, count, compiledPattern); } // Copy the compiled pattern to a char array ! int len = compiledPattern.length(); char[] r = new char[len]; ! compiledPattern.getChars(0, len, r, 0); return r; } /** * Encodes the given tag and length and puts encoded char(s) into buffer. */ ! private static final void encode(int tag, int length, StringBuilder buffer) { if (tag == PATTERN_ISO_ZONE && length >= 4) { throw new IllegalArgumentException("invalid ISO 8601 format: length=" + length); } if (length < 255) { buffer.append((char)(tag << 8 | length)); --- 822,861 ---- if (lastTag == -1 || lastTag == tag) { lastTag = tag; count++; continue; } ! encode(lastTag, count, compiledCode); ! tagcount++; ! prevTag = lastTag; lastTag = tag; count = 1; } if (inQuote) { throw new IllegalArgumentException("Unterminated quote"); } if (count != 0) { ! encode(lastTag, count, compiledCode); ! tagcount++; ! prevTag = lastTag; } + forceStandaloneForm = (tagcount == 1 && prevTag == PATTERN_MONTH); + // Copy the compiled pattern to a char array ! int len = compiledCode.length(); char[] r = new char[len]; ! compiledCode.getChars(0, len, r, 0); return r; } /** * Encodes the given tag and length and puts encoded char(s) into 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); } if (length < 255) { buffer.append((char)(tag << 8 | length));
*** 939,948 **** --- 919,929 ---- * @param pos the formatting position. On input: an alignment field, * if desired. On output: the offsets of the alignment field. * @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) { pos.beginIndex = pos.endIndex = 0; return format(date, toAppendTo, pos.getFieldDelegate());
*** 997,1006 **** --- 978,988 ---- * given object, or if the Format's pattern string is invalid. * @param obj The object to format * @return AttributedCharacterIterator describing the formatted value. * @since 1.4 */ + @Override public AttributedCharacterIterator formatToCharacterIterator(Object obj) { StringBuffer sb = new StringBuffer(); CharacterIteratorFieldDelegate delegate = new CharacterIteratorFieldDelegate();
*** 1020,1070 **** } return delegate.getIterator(sb.toString()); } // 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, Calendar.ZONE_OFFSET, ! // Pseudo Calendar fields ! CalendarBuilder.WEEK_YEAR, ! CalendarBuilder.ISO_DAY_OF_WEEK, ! Calendar.ZONE_OFFSET }; // 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 }; // 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.TIME_ZONE, ! Field.YEAR, Field.DAY_OF_WEEK, ! Field.TIME_ZONE }; /** * Private member function that does the real date/time formatting. */ --- 1002,1089 ---- } return delegate.getIterator(sb.toString()); } // 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, ! 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.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.TIME_ZONE, ! Field.YEAR, ! Field.DAY_OF_WEEK, ! Field.TIME_ZONE, ! Field.MONTH }; /** * Private member function that does the real date/time formatting. */
*** 1092,1102 **** } else { value = calendar.get(field); } int style = (count >= 4) ? Calendar.LONG : Calendar.SHORT; ! if (!useDateFormatSymbols && field != CalendarBuilder.ISO_DAY_OF_WEEK) { current = calendar.getDisplayName(field, style, locale); } // Note: zeroPaddingNumber() assumes that maxDigits is either // 2 or maxIntCount. If we make any changes to this, --- 1111,1122 ---- } else { value = calendar.get(field); } int style = (count >= 4) ? Calendar.LONG : Calendar.SHORT; ! if (!useDateFormatSymbols && field < Calendar.ZONE_OFFSET ! && patternCharIndex != PATTERN_MONTH_STANDALONE) { current = calendar.getDisplayName(field, style, locale); } // Note: zeroPaddingNumber() assumes that maxDigits is either // 2 or maxIntCount. If we make any changes to this,
*** 1104,1136 **** switch (patternCharIndex) { case PATTERN_ERA: // 'G' if (useDateFormatSymbols) { String[] eras = formatData.getEras(); ! if (value < eras.length) current = eras[value]; } ! if (current == null) current = ""; break; case PATTERN_WEEK_YEAR: // 'Y' case PATTERN_YEAR: // 'y' if (calendar instanceof GregorianCalendar) { ! if (count != 2) zeroPaddingNumber(value, count, maxIntCount, buffer); - else // count == 2 - zeroPaddingNumber(value, 2, 2, buffer); // clip 1996 to 96 } else { if (current == null) { zeroPaddingNumber(value, style == Calendar.LONG ? 1 : count, maxIntCount, buffer); } } break; ! case PATTERN_MONTH: // 'M' if (useDateFormatSymbols) { String[] months; if (count >= 4) { months = formatData.getMonths(); current = months[value]; --- 1124,1159 ---- switch (patternCharIndex) { case PATTERN_ERA: // 'G' if (useDateFormatSymbols) { String[] eras = formatData.getEras(); ! if (value < eras.length) { current = eras[value]; } ! } ! if (current == null) { current = ""; + } break; case PATTERN_WEEK_YEAR: // 'Y' case PATTERN_YEAR: // 'y' if (calendar instanceof GregorianCalendar) { ! if (count != 2) { zeroPaddingNumber(value, count, maxIntCount, buffer); } else { + zeroPaddingNumber(value, 2, 2, buffer); + } // clip 1996 to 96 + } else { if (current == null) { zeroPaddingNumber(value, style == Calendar.LONG ? 1 : count, maxIntCount, buffer); } } break; ! case PATTERN_MONTH: // 'M' (context seinsive) if (useDateFormatSymbols) { String[] months; if (count >= 4) { months = formatData.getMonths(); current = months[value];
*** 1139,1163 **** current = months[value]; } } else { if (count < 3) { current = null; } } 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, count, maxIntCount, buffer); ! else zeroPaddingNumber(value, count, maxIntCount, buffer); } break; case PATTERN_DAY_OF_WEEK: // 'E' if (useDateFormatSymbols) { String[] weekdays; --- 1162,1213 ---- current = months[value]; } } 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) { zeroPaddingNumber(value+1, count, maxIntCount, buffer); } 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, count, maxIntCount, buffer); ! } else { zeroPaddingNumber(value, count, maxIntCount, buffer); } + } break; case PATTERN_DAY_OF_WEEK: // 'E' if (useDateFormatSymbols) { String[] weekdays;
*** 1178,1193 **** } break; case PATTERN_HOUR1: // 'h' 1-based. eg, 11PM + 1 hour =>> 12 AM if (current == null) { ! if (value == 0) ! zeroPaddingNumber(calendar.getLeastMaximum(Calendar.HOUR)+1, count, maxIntCount, buffer); ! else zeroPaddingNumber(value, count, maxIntCount, buffer); } break; case PATTERN_ZONE_NAME: // 'z' if (current == null) { if (formatData.locale == null || formatData.isZoneStringsSet) { --- 1228,1244 ---- } break; case PATTERN_HOUR1: // 'h' 1-based. eg, 11PM + 1 hour =>> 12 AM if (current == null) { ! if (value == 0) { ! zeroPaddingNumber(calendar.getLeastMaximum(Calendar.HOUR) + 1, count, maxIntCount, buffer); ! } else { zeroPaddingNumber(value, count, maxIntCount, buffer); } + } break; case PATTERN_ZONE_NAME: // 'z' if (current == null) { if (formatData.locale == null || formatData.isZoneStringsSet) {
*** 1287,1297 **** } /** * Formats a number with the specified minimum and maximum number of digits. */ ! private final 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. // Note: This optimization code assumes that maxDigits is // either 2 or Integer.MAX_VALUE (maxIntCount in format()). --- 1338,1348 ---- } /** * Formats a number with the specified minimum and maximum number of digits. */ ! 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. // Note: This optimization code assumes that maxDigits is // either 2 or Integer.MAX_VALUE (maxIntCount in format()).
*** 1369,1378 **** --- 1420,1430 ---- * index information as described above. * @return A <code>Date</code> parsed from the string. In case of * error, returns null. * @exception NullPointerException if <code>text</code> or <code>pos</code> is null. */ + @Override public Date parse(String text, ParsePosition pos) { checkNegativeNumberExpression(); int start = pos.index;
*** 1502,1512 **** private int matchString(String text, int start, int field, String[] data, CalendarBuilder calb) { int i = 0; int count = data.length; ! 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). // We keep track of the longest match, and return that. Note that this // unfortunately requires us to test all array elements. --- 1554,1566 ---- private int matchString(String text, int start, int field, String[] data, CalendarBuilder calb) { int i = 0; int count = data.length; ! 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). // We keep track of the longest match, and return that. Note that this // unfortunately requires us to test all array elements.
*** 1765,1775 **** if (pos.index >= text.length()) { origPos.errorIndex = start; return -1; } char c = text.charAt(pos.index); ! if (c != ' ' && c != '\t') break; ++pos.index; } parsing: { --- 1819,1831 ---- if (pos.index >= text.length()) { origPos.errorIndex = start; return -1; } char c = text.charAt(pos.index); ! if (c != ' ' && c != '\t') { ! break; ! } ++pos.index; } parsing: {
*** 1910,1921 **** if (value < 1 || value > 24) { break parsing; } } // [We computed 'value' above.] ! if (value == calendar.getMaximum(Calendar.HOUR_OF_DAY)+1) value = 0; calb.set(Calendar.HOUR_OF_DAY, value); return pos.index; case PATTERN_DAY_OF_WEEK: // 'E' { --- 1966,1978 ---- if (value < 1 || value > 24) { break parsing; } } // [We computed 'value' above.] ! if (value == calendar.getMaximum(Calendar.HOUR_OF_DAY) + 1) { value = 0; + } calb.set(Calendar.HOUR_OF_DAY, value); return pos.index; case PATTERN_DAY_OF_WEEK: // 'E' {
*** 1964,1975 **** if (value < 1 || value > 12) { break parsing; } } // [We computed 'value' above.] ! if (value == calendar.getLeastMaximum(Calendar.HOUR)+1) value = 0; calb.set(Calendar.HOUR, value); return pos.index; case PATTERN_ZONE_NAME: // 'z' case PATTERN_ZONE_VALUE: // 'Z' --- 2021,2033 ---- if (value < 1 || value > 12) { break parsing; } } // [We computed 'value' above.] ! if (value == calendar.getLeastMaximum(Calendar.HOUR) + 1) { value = 0; + } calb.set(Calendar.HOUR, value); return pos.index; case PATTERN_ZONE_NAME: // 'z' case PATTERN_ZONE_VALUE: // 'Z'
*** 2109,2133 **** // Parsing failed. origPos.errorIndex = pos.index; return -1; } ! private final String getCalendarName() { ! return calendar.getClass().getName(); ! } ! private boolean useDateFormatSymbols() { ! if (useDateFormatSymbols) { ! return true; } - return isGregorianCalendar() || 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. * * @exception IllegalArgumentException if the given pattern is invalid --- 2167,2184 ---- // Parsing failed. origPos.errorIndex = pos.index; return -1; } ! /** ! * Returns true if the DateFormatSymbols has been set explicitly or locale ! * is null. ! */ private boolean useDateFormatSymbols() { ! return useDateFormatSymbols || locale == null; } /** * Translates a pattern, mapping each character in the from string to the * corresponding character in the to string. * * @exception IllegalArgumentException if the given pattern is invalid
*** 2136,2152 **** StringBuilder result = new StringBuilder(); boolean inQuote = false; for (int i = 0; i < pattern.length(); ++i) { char c = pattern.charAt(i); if (inQuote) { ! if (c == '\'') inQuote = false; } else { ! if (c == '\'') inQuote = true; ! else if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')) { int ci = from.indexOf(c); if (ci >= 0) { // patternChars is longer than localPatternChars due // to serialization compatibility. The pattern letters // unsupported by localPatternChars pass through. --- 2187,2204 ---- StringBuilder result = new StringBuilder(); boolean inQuote = false; for (int i = 0; i < pattern.length(); ++i) { char c = pattern.charAt(i); if (inQuote) { ! if (c == '\'') { inQuote = false; } + } else { ! if (c == '\'') { inQuote = true; ! } else if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')) { int ci = from.indexOf(c); if (ci >= 0) { // patternChars is longer than localPatternChars due // to serialization compatibility. The pattern letters // unsupported by localPatternChars pass through.
*** 2160,2171 **** } } } result.append(c); } ! if (inQuote) throw new IllegalArgumentException("Unfinished quote in pattern"); return result.toString(); } /** * Returns a pattern string describing this date format. --- 2212,2224 ---- } } } result.append(c); } ! if (inQuote) { throw new IllegalArgumentException("Unfinished quote in pattern"); + } return result.toString(); } /** * Returns a pattern string describing this date format.
*** 2194,2203 **** --- 2247,2260 ---- * @exception NullPointerException if the given pattern is null * @exception IllegalArgumentException if the given pattern is invalid */ public void applyPattern(String pattern) { + applyPatternImpl(pattern); + } + + private void applyPatternImpl(String pattern) { compiledPattern = compile(pattern); this.pattern = pattern; } /**
*** 2244,2253 **** --- 2301,2311 ---- * Creates a copy of this <code>SimpleDateFormat</code>. This also * clones the format's date format symbols. * * @return a clone of this <code>SimpleDateFormat</code> */ + @Override public Object clone() { SimpleDateFormat other = (SimpleDateFormat) super.clone(); other.formatData = (DateFormatSymbols) formatData.clone(); return other; }
*** 2255,2264 **** --- 2313,2323 ---- /** * Returns the hash code value for this <code>SimpleDateFormat</code> object. * * @return the hash code value for this <code>SimpleDateFormat</code> object. */ + @Override public int hashCode() { return pattern.hashCode(); // just enough fields for a reasonable distribution }
*** 2268,2280 **** * equality. * * @return true if the given object is equal to this * <code>SimpleDateFormat</code> */ public boolean equals(Object obj) { ! if (!super.equals(obj)) return false; // super does class check SimpleDateFormat that = (SimpleDateFormat) obj; return (pattern.equals(that.pattern) && formatData.equals(that.formatData)); } --- 2327,2342 ---- * equality. * * @return true if the given object is equal to this * <code>SimpleDateFormat</code> */ + @Override public boolean equals(Object obj) { ! if (!super.equals(obj)) { ! return false; // super does class check ! } SimpleDateFormat that = (SimpleDateFormat) obj; return (pattern.equals(that.pattern) && formatData.equals(that.formatData)); }