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));
}