src/share/classes/java/text/SimpleDateFormat.java
Print this page
rev 5615 : 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 Jigsaw. by Naoto Sato and Masayoshi Okutsu)
@@ -39,27 +39,24 @@
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.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.*;
-
/**
* <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,99 +112,104 @@
* <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>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 bgcolor="#eeeeff">
+ * <tr>
* <td><code>W</code>
* <td>Week in month
* <td><a href="#number">Number</a>
* <td><code>2</code>
- * <tr>
+ * <tr bgcolor="#eeeeff">
* <td><code>D</code>
* <td>Day in year
* <td><a href="#number">Number</a>
* <td><code>189</code>
- * <tr bgcolor="#eeeeff">
+ * <tr>
* <td><code>d</code>
* <td>Day in month
* <td><a href="#number">Number</a>
* <td><code>10</code>
- * <tr>
+ * <tr bgcolor="#eeeeff">
* <td><code>F</code>
* <td>Day of week in month
* <td><a href="#number">Number</a>
* <td><code>2</code>
- * <tr bgcolor="#eeeeff">
+ * <tr>
* <td><code>E</code>
* <td>Day name in week
* <td><a href="#text">Text</a>
* <td><code>Tuesday</code>; <code>Tue</code>
- * <tr>
+ * <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 bgcolor="#eeeeff">
+ * <tr>
* <td><code>a</code>
* <td>Am/pm marker
* <td><a href="#text">Text</a>
* <td><code>PM</code>
- * <tr>
+ * <tr bgcolor="#eeeeff">
* <td><code>H</code>
* <td>Hour in day (0-23)
* <td><a href="#number">Number</a>
* <td><code>0</code>
- * <tr bgcolor="#eeeeff">
+ * <tr>
* <td><code>k</code>
* <td>Hour in day (1-24)
* <td><a href="#number">Number</a>
* <td><code>24</code>
- * <tr>
+ * <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 bgcolor="#eeeeff">
+ * <tr>
* <td><code>h</code>
* <td>Hour in am/pm (1-12)
* <td><a href="#number">Number</a>
* <td><code>12</code>
- * <tr>
+ * <tr bgcolor="#eeeeff">
* <td><code>m</code>
* <td>Minute in hour
* <td><a href="#number">Number</a>
* <td><code>30</code>
- * <tr bgcolor="#eeeeff">
+ * <tr>
* <td><code>s</code>
* <td>Second in minute
* <td><a href="#number">Number</a>
* <td><code>55</code>
- * <tr>
+ * <tr bgcolor="#eeeeff">
* <td><code>S</code>
* <td>Millisecond
* <td><a href="#number">Number</a>
* <td><code>978</code>
- * <tr bgcolor="#eeeeff">
+ * <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>
+ * <tr bgcolor="#eeeeff">
* <td><code>Z</code>
* <td>Time zone
* <td><a href="#rfc822timezone">RFC 822 time zone</a>
* <td><code>-0800</code>
- * <tr bgcolor="#eeeeff">
+ * <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,11 +270,21 @@
* 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>
+ * 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,10 +469,15 @@
* (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,20 +517,14 @@
// 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);
+ = 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,11 +550,13 @@
* <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));
+ 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,55 +619,10 @@
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);
@@ -748,14 +716,14 @@
* @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 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);
if (c == '\'') {
@@ -764,25 +732,29 @@
if ((i + 1) < length) {
c = pattern.charAt(i + 1);
if (c == '\'') {
i++;
if (count != 0) {
- encode(lastTag, count, compiledPattern);
+ encode(lastTag, count, compiledCode);
+ tagcount++;
+ prevTag = lastTag;
lastTag = -1;
count = 0;
}
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;
}
}
if (!inQuote) {
if (count != 0) {
- encode(lastTag, count, compiledPattern);
+ encode(lastTag, count, compiledCode);
+ tagcount++;
+ prevTag = lastTag;
lastTag = -1;
count = 0;
}
if (tmpBuffer == null) {
tmpBuffer = new StringBuilder(length);
@@ -793,18 +765,18 @@
} 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));
+ 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;
}
continue;
}
@@ -812,17 +784,19 @@
tmpBuffer.append(c);
continue;
}
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.
int j;
for (j = i + 1; j < length; j++) {
@@ -829,13 +803,13 @@
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)));
+ compiledCode.append((char)(TAG_QUOTE_CHARS << 8 | (j - i)));
for (; i < j; i++) {
- compiledPattern.append(pattern.charAt(i));
+ compiledCode.append(pattern.charAt(i));
}
i--;
}
continue;
}
@@ -848,34 +822,40 @@
if (lastTag == -1 || lastTag == tag) {
lastTag = tag;
count++;
continue;
}
- encode(lastTag, count, compiledPattern);
+ encode(lastTag, count, compiledCode);
+ tagcount++;
+ prevTag = lastTag;
lastTag = tag;
count = 1;
}
if (inQuote) {
throw new IllegalArgumentException("Unterminated quote");
}
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;
}
/**
* 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);
}
if (length < 255) {
buffer.append((char)(tag << 8 | length));
@@ -939,10 +919,11 @@
* @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,10 +978,11 @@
* 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,51 +1002,88 @@
}
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,
+ 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
};
/**
* Private member function that does the real date/time formatting.
*/
@@ -1092,11 +1111,12 @@
} else {
value = calendar.get(field);
}
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);
}
// Note: zeroPaddingNumber() assumes that maxDigits is either
// 2 or maxIntCount. If we make any changes to this,
@@ -1104,33 +1124,36 @@
switch (patternCharIndex) {
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,
maxIntCount, buffer);
}
}
break;
- case PATTERN_MONTH: // 'M'
+ case PATTERN_MONTH: // 'M' (context seinsive)
if (useDateFormatSymbols) {
String[] months;
if (count >= 4) {
months = formatData.getMonths();
current = months[value];
@@ -1139,25 +1162,52 @@
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,
+ if (value == 0) {
+ zeroPaddingNumber(calendar.getMaximum(Calendar.HOUR_OF_DAY) + 1,
count, maxIntCount, buffer);
- else
+ } else {
zeroPaddingNumber(value, count, maxIntCount, buffer);
}
+ }
break;
case PATTERN_DAY_OF_WEEK: // 'E'
if (useDateFormatSymbols) {
String[] weekdays;
@@ -1178,16 +1228,17 @@
}
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,
+ if (value == 0) {
+ zeroPaddingNumber(calendar.getLeastMaximum(Calendar.HOUR) + 1,
count, maxIntCount, buffer);
- else
+ } else {
zeroPaddingNumber(value, count, maxIntCount, buffer);
}
+ }
break;
case PATTERN_ZONE_NAME: // 'z'
if (current == null) {
if (formatData.locale == null || formatData.isZoneStringsSet) {
@@ -1287,11 +1338,11 @@
}
/**
* 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.
// Note: This optimization code assumes that maxDigits is
// either 2 or Integer.MAX_VALUE (maxIntCount in format()).
@@ -1369,10 +1420,11 @@
* 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,11 +1554,13 @@
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;
+ 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,11 +1819,13 @@
if (pos.index >= text.length()) {
origPos.errorIndex = start;
return -1;
}
char c = text.charAt(pos.index);
- if (c != ' ' && c != '\t') break;
+ if (c != ' ' && c != '\t') {
+ break;
+ }
++pos.index;
}
parsing:
{
@@ -1910,12 +1966,13 @@
if (value < 1 || value > 24) {
break parsing;
}
}
// [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;
case PATTERN_DAY_OF_WEEK: // 'E'
{
@@ -1964,12 +2021,13 @@
if (value < 1 || value > 12) {
break parsing;
}
}
// [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;
case PATTERN_ZONE_NAME: // 'z'
case PATTERN_ZONE_VALUE: // 'Z'
@@ -2109,25 +2167,18 @@
// Parsing failed.
origPos.errorIndex = pos.index;
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 useDateFormatSymbols || locale == null;
}
- 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
@@ -2136,17 +2187,18 @@
StringBuilder result = new StringBuilder();
boolean inQuote = false;
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
// to serialization compatibility. The pattern letters
// unsupported by localPatternChars pass through.
@@ -2160,12 +2212,13 @@
}
}
}
result.append(c);
}
- if (inQuote)
+ if (inQuote) {
throw new IllegalArgumentException("Unfinished quote in pattern");
+ }
return result.toString();
}
/**
* Returns a pattern string describing this date format.
@@ -2194,10 +2247,14 @@
* @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,10 +2301,11 @@
* 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,10 +2313,11 @@
/**
* 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,13 +2327,16 @@
* 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
+ if (!super.equals(obj)) {
+ return false; // super does class check
+ }
SimpleDateFormat that = (SimpleDateFormat) obj;
return (pattern.equals(that.pattern)
&& formatData.equals(that.formatData));
}