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