jdk/src/share/classes/java/time/format/DateTimeFormatterBuilder.java
changeset 16852 60207b2b4b42
parent 16005 b480157c22fe
child 17474 8c100beabcc0
--- a/jdk/src/share/classes/java/time/format/DateTimeFormatterBuilder.java	Thu Apr 11 19:15:24 2013 -0700
+++ b/jdk/src/share/classes/java/time/format/DateTimeFormatterBuilder.java	Fri Apr 12 07:57:35 2013 -0700
@@ -83,11 +83,9 @@
 import java.time.ZoneOffset;
 import java.time.chrono.Chronology;
 import java.time.chrono.IsoChronology;
-import java.time.chrono.JapaneseChronology;
 import java.time.format.DateTimeTextProvider.LocaleStore;
 import java.time.temporal.ChronoField;
 import java.time.temporal.IsoFields;
-import java.time.temporal.Queries;
 import java.time.temporal.TemporalAccessor;
 import java.time.temporal.TemporalField;
 import java.time.temporal.TemporalQuery;
@@ -111,7 +109,10 @@
 import java.util.Set;
 import java.util.TimeZone;
 import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
 
+import sun.util.locale.provider.LocaleProviderAdapter;
+import sun.util.locale.provider.LocaleResources;
 import sun.util.locale.provider.TimeZoneNameUtility;
 
 /**
@@ -129,6 +130,8 @@
  * <li>OffsetId/Offset - the {@linkplain ZoneOffset zone offset}</li>
  * <li>ZoneId - the {@linkplain ZoneId time-zone} id</li>
  * <li>ZoneText - the name of the time-zone</li>
+ * <li>ChronologyId - the {@linkplain Chronology chronology} id</li>
+ * <li>ChronologyText - the name of the chronology</li>
  * <li>Literal - a text literal</li>
  * <li>Nested and Optional - formats can be nested or made optional</li>
  * <li>Other - the printer and parser interfaces can be used to add user supplied formatting</li>
@@ -150,7 +153,7 @@
      * Query for a time-zone that is region-only.
      */
     private static final TemporalQuery<ZoneId> QUERY_REGION_ONLY = (temporal) -> {
-        ZoneId zone = temporal.query(Queries.zoneId());
+        ZoneId zone = temporal.query(TemporalQuery.zoneId());
         return (zone != null && zone instanceof ZoneOffset == false ? zone : null);
     };
 
@@ -288,6 +291,40 @@
 
     //-----------------------------------------------------------------------
     /**
+     * Appends a default value for a field to the formatter for use in parsing.
+     * <p>
+     * This appends an instruction to the builder to inject a default value
+     * into the parsed result. This is especially useful in conjunction with
+     * optional parts of the formatter.
+     * <p>
+     * For example, consider a formatter that parses the year, followed by
+     * an optional month, with a further optional day-of-month. Using such a
+     * formatter would require the calling code to check whether a full date,
+     * year-month or just a year had been parsed. This method can be used to
+     * default the month and day-of-month to a sensible value, such as the
+     * first of the month, allowing the calling code to always get a date.
+     * <p>
+     * During formatting, this method has no effect.
+     * <p>
+     * During parsing, the current state of the parse is inspected.
+     * If the specified field has no associated value, because it has not been
+     * parsed successfully at that point, then the specified value is injected
+     * into the parse result. Injection is immediate, thus the field-value pair
+     * will be visible to any subsequent elements in the formatter.
+     * As such, this method is normally called at the end of the builder.
+     *
+     * @param field  the field to default the value of, not null
+     * @param value  the value to default the field to
+     * @return this, for chaining, not null
+     */
+    public DateTimeFormatterBuilder parseDefaulting(TemporalField field, long value) {
+        Objects.requireNonNull(field, "field");
+        appendInternal(new DefaultValueParser(field, value));
+        return this;
+    }
+
+    //-----------------------------------------------------------------------
+    /**
      * Appends the value of a date-time field to the formatter using a normal
      * output style.
      * <p>
@@ -655,7 +692,7 @@
      * This appends an instruction to format/parse the offset ID to the builder.
      * <p>
      * During formatting, the offset is obtained using a mechanism equivalent
-     * to querying the temporal with {@link Queries#offset()}.
+     * to querying the temporal with {@link TemporalQuery#offset()}.
      * It will be printed using the format defined below.
      * If the offset cannot be obtained then an exception is thrown unless the
      * section of the formatter is optional.
@@ -692,6 +729,44 @@
         return this;
     }
 
+    /**
+     * Appends the localized zone offset, such as 'GMT+01:00', to the formatter.
+     * <p>
+     * This appends a localized zone offset to the builder, the format of the
+     * localized offset is controlled by the specified {@link FormatStyle style}
+     * to this method:
+     * <p><ul>
+     * <li>{@link TextStyle#FULL full} - formats with localized offset text, such
+     * as 'GMT, 2-digit hour and minute field, optional second field if non-zero,
+     * and colon.
+     * <li>{@link TextStyle#SHORT short} - formats with localized offset text,
+     * such as 'GMT, hour without leading zero, optional 2-digit minute and
+     * second if non-zero, and colon.
+     * </ul><p>
+     * <p>
+     * During formatting, the offset is obtained using a mechanism equivalent
+     * to querying the temporal with {@link TemporalQuery#offset()}.
+     * If the offset cannot be obtained then an exception is thrown unless the
+     * section of the formatter is optional.
+     * <p>
+     * During parsing, the offset is parsed using the format defined above.
+     * If the offset cannot be parsed then an exception is thrown unless the
+     * section of the formatter is optional.
+     * <p>
+     * @param style  the format style to use, not null
+     * @return this, for chaining, not null
+     * @throws IllegalArgumentException if style is neither {@link TextStyle#FULL
+     * full} nor {@link TextStyle#SHORT short}
+     */
+    public DateTimeFormatterBuilder appendLocalizedOffset(TextStyle style) {
+        Objects.requireNonNull(style, "style");
+        if (style != TextStyle.FULL && style != TextStyle.SHORT) {
+            throw new IllegalArgumentException("Style must be either full or short");
+        }
+        appendInternal(new LocalizedOffsetIdPrinterParser(style));
+        return this;
+    }
+
     //-----------------------------------------------------------------------
     /**
      * Appends the time-zone ID, such as 'Europe/Paris' or '+02:00', to the formatter.
@@ -702,7 +777,7 @@
      * for use with this method, see {@link #appendZoneOrOffsetId()}.
      * <p>
      * During formatting, the zone is obtained using a mechanism equivalent
-     * to querying the temporal with {@link Queries#zoneId()}.
+     * to querying the temporal with {@link TemporalQuery#zoneId()}.
      * It will be printed using the result of {@link ZoneId#getId()}.
      * If the zone cannot be obtained then an exception is thrown unless the
      * section of the formatter is optional.
@@ -725,25 +800,25 @@
      * <p>
      * For example, the following will parse:
      * <pre>
-     *   "Europe/London"           -> ZoneId.of("Europe/London")
-     *   "Z"                       -> ZoneOffset.UTC
-     *   "UT"                      -> ZoneOffset.UTC
-     *   "UTC"                     -> ZoneOffset.UTC
-     *   "GMT"                     -> ZoneOffset.UTC
-     *   "UT0"                     -> ZoneOffset.UTC
-     *   "UTC0"                    -> ZoneOffset.UTC
-     *   "GMT0"                    -> ZoneOffset.UTC
-     *   "+01:30"                  -> ZoneOffset.of("+01:30")
-     *   "UT+01:30"                -> ZoneOffset.of("+01:30")
-     *   "UTC+01:30"               -> ZoneOffset.of("+01:30")
-     *   "GMT+01:30"               -> ZoneOffset.of("+01:30")
+     *   "Europe/London"           -- ZoneId.of("Europe/London")
+     *   "Z"                       -- ZoneOffset.UTC
+     *   "UT"                      -- ZoneOffset.UTC
+     *   "UTC"                     -- ZoneOffset.UTC
+     *   "GMT"                     -- ZoneOffset.UTC
+     *   "UT0"                     -- ZoneOffset.UTC
+     *   "UTC0"                    -- ZoneOffset.UTC
+     *   "GMT0"                    -- ZoneOffset.UTC
+     *   "+01:30"                  -- ZoneOffset.of("+01:30")
+     *   "UT+01:30"                -- ZoneOffset.of("+01:30")
+     *   "UTC+01:30"               -- ZoneOffset.of("+01:30")
+     *   "GMT+01:30"               -- ZoneOffset.of("+01:30")
      * </pre>
      *
      * @return this, for chaining, not null
      * @see #appendZoneRegionId()
      */
     public DateTimeFormatterBuilder appendZoneId() {
-        appendInternal(new ZoneIdPrinterParser(Queries.zoneId(), "ZoneId()"));
+        appendInternal(new ZoneIdPrinterParser(TemporalQuery.zoneId(), "ZoneId()"));
         return this;
     }
 
@@ -755,7 +830,7 @@
      * only if it is a region-based ID.
      * <p>
      * During formatting, the zone is obtained using a mechanism equivalent
-     * to querying the temporal with {@link Queries#zoneId()}.
+     * to querying the temporal with {@link TemporalQuery#zoneId()}.
      * If the zone is a {@code ZoneOffset} or it cannot be obtained then
      * an exception is thrown unless the section of the formatter is optional.
      * If the zone is not an offset, then the zone will be printed using
@@ -779,18 +854,18 @@
      * <p>
      * For example, the following will parse:
      * <pre>
-     *   "Europe/London"           -> ZoneId.of("Europe/London")
-     *   "Z"                       -> ZoneOffset.UTC
-     *   "UT"                      -> ZoneOffset.UTC
-     *   "UTC"                     -> ZoneOffset.UTC
-     *   "GMT"                     -> ZoneOffset.UTC
-     *   "UT0"                     -> ZoneOffset.UTC
-     *   "UTC0"                    -> ZoneOffset.UTC
-     *   "GMT0"                    -> ZoneOffset.UTC
-     *   "+01:30"                  -> ZoneOffset.of("+01:30")
-     *   "UT+01:30"                -> ZoneOffset.of("+01:30")
-     *   "UTC+01:30"               -> ZoneOffset.of("+01:30")
-     *   "GMT+01:30"               -> ZoneOffset.of("+01:30")
+     *   "Europe/London"           -- ZoneId.of("Europe/London")
+     *   "Z"                       -- ZoneOffset.UTC
+     *   "UT"                      -- ZoneOffset.UTC
+     *   "UTC"                     -- ZoneOffset.UTC
+     *   "GMT"                     -- ZoneOffset.UTC
+     *   "UT0"                     -- ZoneOffset.UTC
+     *   "UTC0"                    -- ZoneOffset.UTC
+     *   "GMT0"                    -- ZoneOffset.UTC
+     *   "+01:30"                  -- ZoneOffset.of("+01:30")
+     *   "UT+01:30"                -- ZoneOffset.of("+01:30")
+     *   "UTC+01:30"               -- ZoneOffset.of("+01:30")
+     *   "GMT+01:30"               -- ZoneOffset.of("+01:30")
      * </pre>
      * <p>
      * Note that this method is is identical to {@code appendZoneId()} except
@@ -817,7 +892,7 @@
      * then attempts to find an offset, such as that on {@code OffsetDateTime}.
      * <p>
      * During formatting, the zone is obtained using a mechanism equivalent
-     * to querying the temporal with {@link Queries#zone()}.
+     * to querying the temporal with {@link TemporalQuery#zone()}.
      * It will be printed using the result of {@link ZoneId#getId()}.
      * If the zone cannot be obtained then an exception is thrown unless the
      * section of the formatter is optional.
@@ -840,18 +915,18 @@
      * <p>
      * For example, the following will parse:
      * <pre>
-     *   "Europe/London"           -> ZoneId.of("Europe/London")
-     *   "Z"                       -> ZoneOffset.UTC
-     *   "UT"                      -> ZoneOffset.UTC
-     *   "UTC"                     -> ZoneOffset.UTC
-     *   "GMT"                     -> ZoneOffset.UTC
-     *   "UT0"                     -> ZoneOffset.UTC
-     *   "UTC0"                    -> ZoneOffset.UTC
-     *   "GMT0"                    -> ZoneOffset.UTC
-     *   "+01:30"                  -> ZoneOffset.of("+01:30")
-     *   "UT+01:30"                -> ZoneOffset.of("+01:30")
-     *   "UTC+01:30"               -> ZoneOffset.of("+01:30")
-     *   "GMT+01:30"               -> ZoneOffset.of("+01:30")
+     *   "Europe/London"           -- ZoneId.of("Europe/London")
+     *   "Z"                       -- ZoneOffset.UTC
+     *   "UT"                      -- ZoneOffset.UTC
+     *   "UTC"                     -- ZoneOffset.UTC
+     *   "GMT"                     -- ZoneOffset.UTC
+     *   "UT0"                     -- ZoneOffset.UTC
+     *   "UTC0"                    -- ZoneOffset.UTC
+     *   "GMT0"                    -- ZoneOffset.UTC
+     *   "+01:30"                  -- ZoneOffset.of("+01:30")
+     *   "UT+01:30"                -- ZoneOffset.of("+01:30")
+     *   "UTC+01:30"               -- ZoneOffset.of("+01:30")
+     *   "GMT+01:30"               -- ZoneOffset.of("+01:30")
      * </pre>
      * <p>
      * Note that this method is is identical to {@code appendZoneId()} except
@@ -861,7 +936,7 @@
      * @see #appendZoneId()
      */
     public DateTimeFormatterBuilder appendZoneOrOffsetId() {
-        appendInternal(new ZoneIdPrinterParser(Queries.zone(), "ZoneOrOffsetId()"));
+        appendInternal(new ZoneIdPrinterParser(TemporalQuery.zone(), "ZoneOrOffsetId()"));
         return this;
     }
 
@@ -872,7 +947,7 @@
      * the builder.
      * <p>
      * During formatting, the zone is obtained using a mechanism equivalent
-     * to querying the temporal with {@link Queries#zoneId()}.
+     * to querying the temporal with {@link TemporalQuery#zoneId()}.
      * If the zone is a {@code ZoneOffset} it will be printed using the
      * result of {@link ZoneOffset#getId()}.
      * If the zone is not an offset, the textual name will be looked up
@@ -908,7 +983,7 @@
      * the builder.
      * <p>
      * During formatting, the zone is obtained using a mechanism equivalent
-     * to querying the temporal with {@link Queries#zoneId()}.
+     * to querying the temporal with {@link TemporalQuery#zoneId()}.
      * If the zone is a {@code ZoneOffset} it will be printed using the
      * result of {@link ZoneOffset#getId()}.
      * If the zone is not an offset, the textual name will be looked up
@@ -951,7 +1026,7 @@
      * This appends an instruction to format/parse the chronology ID to the builder.
      * <p>
      * During formatting, the chronology is obtained using a mechanism equivalent
-     * to querying the temporal with {@link Queries#chronology()}.
+     * to querying the temporal with {@link TemporalQuery#chronology()}.
      * It will be printed using the result of {@link Chronology#getId()}.
      * If the chronology cannot be obtained then an exception is thrown unless the
      * section of the formatter is optional.
@@ -1098,24 +1173,25 @@
      * Appends the elements defined by the specified pattern to the builder.
      * <p>
      * All letters 'A' to 'Z' and 'a' to 'z' are reserved as pattern letters.
-     * The characters '{' and '}' are reserved for future use.
+     * The characters '#', '{' and '}' are reserved for future use.
      * The characters '[' and ']' indicate optional patterns.
      * The following pattern letters are defined:
      * <pre>
      *  Symbol  Meaning                     Presentation      Examples
      *  ------  -------                     ------------      -------
-     *   G       era                         text              A; AD; Anno Domini
-     *   y       year                        year              2004; 04
+     *   G       era                         text              AD; Anno Domini; A
+     *   u       year                        year              2004; 04
+     *   y       year-of-era                 year              2004; 04
      *   D       day-of-year                 number            189
-     *   M       month-of-year               number/text       7; 07; Jul; July; J
+     *   M/L     month-of-year               number/text       7; 07; Jul; July; J
      *   d       day-of-month                number            10
      *
-     *   Q       quarter-of-year             number/text       3; 03; Q3
+     *   Q/q     quarter-of-year             number/text       3; 03; Q3; 3rd quarter
      *   Y       week-based-year             year              1996; 96
-     *   w       week-of-year                number            27
-     *   W       week-of-month               number            27
-     *   e       localized day-of-week       number            2; Tue; Tuesday; T
-     *   E       day-of-week                 number/text       2; Tue; Tuesday; T
+     *   w       week-of-week-based-year     number            27
+     *   W       week-of-month               number            4
+     *   E       day-of-week                 text              Tue; Tuesday; T
+     *   e/c     localized day-of-week       number/text       2; 02; Tue; Tuesday; T
      *   F       week-of-month               number            3
      *
      *   a       am-pm-of-day                text              PM
@@ -1133,6 +1209,7 @@
      *
      *   V       time-zone ID                zone-id           America/Los_Angeles; Z; -08:30
      *   z       time-zone name              zone-name         Pacific Standard Time; PST
+     *   O       localized zone-offset       offset-O          GMT+8; GMT+08:00; UTC-08:00;
      *   X       zone-offset 'Z' for zero    offset-X          Z; -08; -0830; -08:30; -083015; -08:30:15;
      *   x       zone-offset                 offset-x          +0000; -08; -0830; -08:30; -083015; -08:30:15;
      *   Z       zone-offset                 offset-Z          +0000; -0800; -08:00;
@@ -1143,116 +1220,169 @@
      *   ''      single quote                literal           '
      *   [       optional section start
      *   ]       optional section end
-     *   {}      reserved for future use
+     *   #       reserved for future use
+     *   {       reserved for future use
+     *   }       reserved for future use
      * </pre>
      * <p>
      * The count of pattern letters determine the format.
-     * <p>
-     * <b>Text</b>: The text style is determined based on the number of pattern letters used.
-     * Less than 4 pattern letters will use the {@link TextStyle#SHORT short form}.
-     * Exactly 4 pattern letters will use the {@link TextStyle#FULL full form}.
-     * Exactly 5 pattern letters will use the {@link TextStyle#NARROW narrow form}.
-     * <p>
-     * <b>Number</b>: If the count of letters is one, then the value is printed using the minimum number
-     * of digits and without padding as per {@link #appendValue(java.time.temporal.TemporalField)}. Otherwise, the
-     * count of digits is used as the width of the output field as per {@link #appendValue(java.time.temporal.TemporalField, int)}.
-     * <p>
-     * <b>Number/Text</b>: If the count of pattern letters is 3 or greater, use the Text rules above.
-     * Otherwise use the Number rules above.
-     * <p>
-     * <b>Fraction</b>: Outputs the nano-of-second field as a fraction-of-second.
-     * The nano-of-second value has nine digits, thus the count of pattern letters is from 1 to 9.
-     * If it is less than 9, then the nano-of-second value is truncated, with only the most
-     * significant digits being output.
-     * When parsing in strict mode, the number of parsed digits must match the count of pattern letters.
-     * When parsing in lenient mode, the number of parsed digits must be at least the count of pattern
-     * letters, up to 9 digits.
+     * See <a href="DateTimeFormatter.html#patterns">DateTimeFormatter</a> for a user-focused description of the patterns.
+     * The following tables define how the pattern letters map to the builder.
      * <p>
-     * <b>Year</b>: The count of letters determines the minimum field width below which padding is used.
-     * If the count of letters is two, then a {@link #appendValueReduced reduced} two digit form is used.
-     * For formatting, this outputs the rightmost two digits. For parsing, this will parse using the
-     * base value of 2000, resulting in a year within the range 2000 to 2099 inclusive.
-     * If the count of letters is less than four (but not two), then the sign is only output for negative
-     * years as per {@link SignStyle#NORMAL}.
-     * Otherwise, the sign is output if the pad width is exceeded, as per {@link SignStyle#EXCEEDS_PAD}
-     * <p>
-     * <b>ZoneId</b>: This outputs the time-zone ID, such as 'Europe/Paris'.
-     * If the count of letters is two, then the time-zone ID is output.
-     * Any other count of letters throws {@code IllegalArgumentException}.
+     * <b>Date fields</b>: Pattern letters to output a date.
      * <pre>
-     *  Pattern     Equivalent builder methods
-     *   VV          appendZoneId()
-     * </pre>
-     * <p>
-     * <b>Zone names</b>: This outputs the display name of the time-zone ID.
-     * If the count of letters is one, two or three, then the short name is output.
-     * If the count of letters is four, then the full name is output.
-     * Five or more letters throws {@code IllegalArgumentException}.
-     * <pre>
-     *  Pattern     Equivalent builder methods
-     *   z           appendZoneText(TextStyle.SHORT)
-     *   zz          appendZoneText(TextStyle.SHORT)
-     *   zzz         appendZoneText(TextStyle.SHORT)
-     *   zzzz        appendZoneText(TextStyle.FULL)
+     *  Pattern  Count  Equivalent builder methods
+     *  -------  -----  --------------------------
+     *    G       1      appendText(ChronoField.ERA, TextStyle.SHORT)
+     *    GG      2      appendText(ChronoField.ERA, TextStyle.SHORT)
+     *    GGG     3      appendText(ChronoField.ERA, TextStyle.SHORT)
+     *    GGGG    4      appendText(ChronoField.ERA, TextStyle.FULL)
+     *    GGGGG   5      appendText(ChronoField.ERA, TextStyle.NARROW)
+     *
+     *    u       1      appendValue(ChronoField.YEAR, 1, 19, SignStyle.NORMAL);
+     *    uu      2      appendValueReduced(ChronoField.YEAR, 2, 2000);
+     *    uuu     3      appendValue(ChronoField.YEAR, 3, 19, SignStyle.NORMAL);
+     *    u..u    4..n   appendValue(ChronoField.YEAR, n, 19, SignStyle.EXCEEDS_PAD);
+     *    y       1      appendValue(ChronoField.YEAR_OF_ERA, 1, 19, SignStyle.NORMAL);
+     *    yy      2      appendValueReduced(ChronoField.YEAR_OF_ERA, 2, 2000);
+     *    yyy     3      appendValue(ChronoField.YEAR_OF_ERA, 3, 19, SignStyle.NORMAL);
+     *    y..y    4..n   appendValue(ChronoField.YEAR_OF_ERA, n, 19, SignStyle.EXCEEDS_PAD);
+     *    Y       1      append special localized WeekFields element for numeric week-based-year
+     *    YY      2      append special localized WeekFields element for reduced numeric week-based-year 2 digits;
+     *    YYY     3      append special localized WeekFields element for numeric week-based-year (3, 19, SignStyle.NORMAL);
+     *    Y..Y    4..n   append special localized WeekFields element for numeric week-based-year (n, 19, SignStyle.EXCEEDS_PAD);
+     *
+     *    Q       1      appendValue(IsoFields.QUARTER_OF_YEAR);
+     *    QQ      2      appendValue(IsoFields.QUARTER_OF_YEAR, 2);
+     *    QQQ     3      appendText(IsoFields.QUARTER_OF_YEAR, TextStyle.SHORT)
+     *    QQQQ    4      appendText(IsoFields.QUARTER_OF_YEAR, TextStyle.FULL)
+     *    QQQQQ   5      appendText(IsoFields.QUARTER_OF_YEAR, TextStyle.NARROW)
+     *    q       1      appendValue(IsoFields.QUARTER_OF_YEAR);
+     *    qq      2      appendValue(IsoFields.QUARTER_OF_YEAR, 2);
+     *    qqq     3      appendText(IsoFields.QUARTER_OF_YEAR, TextStyle.SHORT_STANDALONE)
+     *    qqqq    4      appendText(IsoFields.QUARTER_OF_YEAR, TextStyle.FULL_STANDALONE)
+     *    qqqqq   5      appendText(IsoFields.QUARTER_OF_YEAR, TextStyle.NARROW_STANDALONE)
+     *
+     *    M       1      appendValue(ChronoField.MONTH_OF_YEAR);
+     *    MM      2      appendValue(ChronoField.MONTH_OF_YEAR, 2);
+     *    MMM     3      appendText(ChronoField.MONTH_OF_YEAR, TextStyle.SHORT)
+     *    MMMM    4      appendText(ChronoField.MONTH_OF_YEAR, TextStyle.FULL)
+     *    MMMMM   5      appendText(ChronoField.MONTH_OF_YEAR, TextStyle.NARROW)
+     *    L       1      appendValue(ChronoField.MONTH_OF_YEAR);
+     *    LL      2      appendValue(ChronoField.MONTH_OF_YEAR, 2);
+     *    LLL     3      appendText(ChronoField.MONTH_OF_YEAR, TextStyle.SHORT_STANDALONE)
+     *    LLLL    4      appendText(ChronoField.MONTH_OF_YEAR, TextStyle.FULL_STANDALONE)
+     *    LLLLL   5      appendText(ChronoField.MONTH_OF_YEAR, TextStyle.NARROW_STANDALONE)
+     *
+     *    w       1      append special localized WeekFields element for numeric week-of-year
+     *    ww      1      append special localized WeekFields element for numeric week-of-year, zero-padded
+     *    W       1      append special localized WeekFields element for numeric week-of-month
+     *    d       1      appendValue(ChronoField.DAY_OF_MONTH)
+     *    dd      2      appendValue(ChronoField.DAY_OF_MONTH, 2)
+     *    D       1      appendValue(ChronoField.DAY_OF_YEAR)
+     *    DD      2      appendValue(ChronoField.DAY_OF_YEAR, 2)
+     *    DDD     3      appendValue(ChronoField.DAY_OF_YEAR, 3)
+     *    F       1      appendValue(ChronoField.ALIGNED_DAY_OF_WEEK_IN_MONTH)
+     *    E       1      appendText(ChronoField.DAY_OF_WEEK, TextStyle.SHORT)
+     *    EE      2      appendText(ChronoField.DAY_OF_WEEK, TextStyle.SHORT)
+     *    EEE     3      appendText(ChronoField.DAY_OF_WEEK, TextStyle.SHORT)
+     *    EEEE    4      appendText(ChronoField.DAY_OF_WEEK, TextStyle.FULL)
+     *    EEEEE   5      appendText(ChronoField.DAY_OF_WEEK, TextStyle.NARROW)
+     *    e       1      append special localized WeekFields element for numeric day-of-week
+     *    ee      2      append special localized WeekFields element for numeric day-of-week, zero-padded
+     *    eee     3      appendText(ChronoField.DAY_OF_WEEK, TextStyle.SHORT)
+     *    eeee    4      appendText(ChronoField.DAY_OF_WEEK, TextStyle.FULL)
+     *    eeeee   5      appendText(ChronoField.DAY_OF_WEEK, TextStyle.NARROW)
+     *    c       1      append special localized WeekFields element for numeric day-of-week
+     *    ccc     3      appendText(ChronoField.DAY_OF_WEEK, TextStyle.SHORT_STANDALONE)
+     *    cccc    4      appendText(ChronoField.DAY_OF_WEEK, TextStyle.FULL_STANDALONE)
+     *    ccccc   5      appendText(ChronoField.DAY_OF_WEEK, TextStyle.NARROW_STANDALONE)
      * </pre>
      * <p>
-     * <b>Offset X and x</b>: This formats the offset based on the number of pattern letters.
-     * One letter outputs just the hour', such as '+01', unless the minute is non-zero
-     * in which case the minute is also output, such as '+0130'.
-     * Two letters outputs the hour and minute, without a colon, such as '+0130'.
-     * Three letters outputs the hour and minute, with a colon, such as '+01:30'.
-     * Four letters outputs the hour and minute and optional second, without a colon, such as '+013015'.
-     * Five letters outputs the hour and minute and optional second, with a colon, such as '+01:30:15'.
-     * Six or more letters throws {@code IllegalArgumentException}.
-     * Pattern letter 'X' (upper case) will output 'Z' when the offset to be output would be zero,
-     * whereas pattern letter 'x' (lower case) will output '+00', '+0000', or '+00:00'.
+     * <b>Time fields</b>: Pattern letters to output a time.
      * <pre>
-     *  Pattern     Equivalent builder methods
-     *   X           appendOffset("+HHmm","Z")
-     *   XX          appendOffset("+HHMM","Z")
-     *   XXX         appendOffset("+HH:MM","Z")
-     *   XXXX        appendOffset("+HHMMss","Z")
-     *   XXXXX       appendOffset("+HH:MM:ss","Z")
-     *   x           appendOffset("+HHmm","+00")
-     *   xx          appendOffset("+HHMM","+0000")
-     *   xxx         appendOffset("+HH:MM","+00:00")
-     *   xxxx        appendOffset("+HHMMss","+0000")
-     *   xxxxx       appendOffset("+HH:MM:ss","+00:00")
+     *  Pattern  Count  Equivalent builder methods
+     *  -------  -----  --------------------------
+     *    a       1      appendText(ChronoField.AMPM_OF_DAY, TextStyle.SHORT)
+     *    h       1      appendValue(ChronoField.CLOCK_HOUR_OF_AMPM)
+     *    hh      2      appendValue(ChronoField.CLOCK_HOUR_OF_AMPM, 2)
+     *    H       1      appendValue(ChronoField.HOUR_OF_DAY)
+     *    HH      2      appendValue(ChronoField.HOUR_OF_DAY, 2)
+     *    k       1      appendValue(ChronoField.CLOCK_HOUR_OF_DAY)
+     *    kk      2      appendValue(ChronoField.CLOCK_HOUR_OF_DAY, 2)
+     *    K       1      appendValue(ChronoField.HOUR_OF_AMPM)
+     *    KK      2      appendValue(ChronoField.HOUR_OF_AMPM, 2)
+     *    m       1      appendValue(ChronoField.MINUTE_OF_HOUR)
+     *    mm      2      appendValue(ChronoField.MINUTE_OF_HOUR, 2)
+     *    s       1      appendValue(ChronoField.SECOND_OF_MINUTE)
+     *    ss      2      appendValue(ChronoField.SECOND_OF_MINUTE, 2)
+     *
+     *    S..S    1..n   appendFraction(ChronoField.NANO_OF_SECOND, n, n, false)
+     *    A       1      appendValue(ChronoField.MILLI_OF_DAY)
+     *    A..A    2..n   appendValue(ChronoField.MILLI_OF_DAY, n)
+     *    n       1      appendValue(ChronoField.NANO_OF_SECOND)
+     *    n..n    2..n   appendValue(ChronoField.NANO_OF_SECOND, n)
+     *    N       1      appendValue(ChronoField.NANO_OF_DAY)
+     *    N..N    2..n   appendValue(ChronoField.NANO_OF_DAY, n)
+     * </pre>
+     * <p>
+     * <b>Zone ID</b>: Pattern letters to output {@code ZoneId}.
+     * <pre>
+     *  Pattern  Count  Equivalent builder methods
+     *  -------  -----  --------------------------
+     *    VV      2      appendZoneId()
+     *    z       1      appendZoneText(TextStyle.SHORT)
+     *    zz      2      appendZoneText(TextStyle.SHORT)
+     *    zzz     3      appendZoneText(TextStyle.SHORT)
+     *    zzzz    4      appendZoneText(TextStyle.FULL)
      * </pre>
      * <p>
-     * <b>Offset Z</b>: This formats the offset based on the number of pattern letters.
-     * One, two or three letters outputs the hour and minute, without a colon, such as '+0130'.
-     * Four or more letters throws {@code IllegalArgumentException}.
-     * The output will be '+0000' when the offset is zero.
+     * <b>Zone offset</b>: Pattern letters to output {@code ZoneOffset}.
      * <pre>
-     *  Pattern     Equivalent builder methods
-     *   Z           appendOffset("+HHMM","+0000")
-     *   ZZ          appendOffset("+HHMM","+0000")
-     *   ZZZ         appendOffset("+HHMM","+0000")
+     *  Pattern  Count  Equivalent builder methods
+     *  -------  -----  --------------------------
+     *    O       1      appendLocalizedOffsetPrefixed(TextStyle.SHORT);
+     *    OOOO    4      appendLocalizedOffsetPrefixed(TextStyle.FULL);
+     *    X       1      appendOffset("+HHmm","Z")
+     *    XX      2      appendOffset("+HHMM","Z")
+     *    XXX     3      appendOffset("+HH:MM","Z")
+     *    XXXX    4      appendOffset("+HHMMss","Z")
+     *    XXXXX   5      appendOffset("+HH:MM:ss","Z")
+     *    x       1      appendOffset("+HHmm","+00")
+     *    xx      2      appendOffset("+HHMM","+0000")
+     *    xxx     3      appendOffset("+HH:MM","+00:00")
+     *    xxxx    4      appendOffset("+HHMMss","+0000")
+     *    xxxxx   5      appendOffset("+HH:MM:ss","+00:00")
+     *    Z       1      appendOffset("+HHMM","+0000")
+     *    ZZ      2      appendOffset("+HHMM","+0000")
+     *    ZZZ     3      appendOffset("+HHMM","+0000")
+     *    ZZZZ    4      appendLocalizedOffset(TextStyle.FULL);
+     *    ZZZZZ   5      appendOffset("+HH:MM:ss","Z")
      * </pre>
      * <p>
-     * <b>Optional section</b>: The optional section markers work exactly like calling {@link #optionalStart()}
-     * and {@link #optionalEnd()}.
-     * <p>
-     * <b>Pad modifier</b>: Modifies the pattern that immediately follows to be padded with spaces.
-     * The pad width is determined by the number of pattern letters.
-     * This is the same as calling {@link #padNext(int)}.
+     * <b>Modifiers</b>: Pattern letters that modify the rest of the pattern:
+     * <pre>
+     *  Pattern  Count  Equivalent builder methods
+     *  -------  -----  --------------------------
+     *    [       1      optionalStart()
+     *    ]       1      optionalEnd()
+     *    p..p    1..n   padNext(n)
+     * </pre>
      * <p>
-     * For example, 'ppH' outputs the hour-of-day padded on the left with spaces to a width of 2.
-     * <p>
-     * Any unrecognized letter is an error.
-     * Any non-letter character, other than '[', ']', '{', '}' and the single quote will be output directly.
-     * Despite this, it is recommended to use single quotes around all characters that you want to
-     * output directly to ensure that future changes do not break your application.
+     * Any sequence of letters not specified above, unrecognized letter or
+     * reserved character will throw an exception.
+     * Future versions may add to the set of patterns.
+     * It is recommended to use single quotes around all characters that you want
+     * to output directly to ensure that future changes do not break your application.
      * <p>
      * Note that the pattern string is similar, but not identical, to
      * {@link java.text.SimpleDateFormat SimpleDateFormat}.
      * The pattern string is also similar, but not identical, to that defined by the
      * Unicode Common Locale Data Repository (CLDR/LDML).
-     * Pattern letters 'E' and 'u' are merged, which changes the meaning of "E" and "EE" to be numeric.
-     * Pattern letters 'X' is aligned with Unicode CLDR/LDML, which affects pattern 'X'.
-     * Pattern letter 'y' and 'Y' parse years of two digits and more than 4 digits differently.
-     * Pattern letters 'n', 'A', 'N', 'I' and 'p' are added.
+     * Pattern letters 'X' and 'u' are aligned with Unicode CLDR/LDML.
+     * By contrast, {@code SimpleDateFormat} uses 'u' for the numeric day of week.
+     * Pattern letters 'y' and 'Y' parse years of two digits and more than 4 digits differently.
+     * Pattern letters 'n', 'A', 'N', and 'p' are added.
      * Number types will reject large numbers.
      *
      * @param pattern  the pattern to add, not null
@@ -1308,10 +1438,23 @@
                     }
                     appendZoneId();
                 } else if (cur == 'Z') {
-                    if (count > 3) {
+                    if (count < 4) {
+                        appendOffset("+HHMM", "+0000");
+                    } else if (count == 4) {
+                        appendLocalizedOffset(TextStyle.FULL);
+                    } else if (count == 5) {
+                        appendOffset("+HH:MM:ss","Z");
+                    } else {
                         throw new IllegalArgumentException("Too many pattern letters: " + cur);
                     }
-                    appendOffset("+HHMM", "+0000");
+                } else if (cur == 'O') {
+                    if (count == 1) {
+                        appendLocalizedOffset(TextStyle.SHORT);
+                    } else if (count == 4) {
+                        appendLocalizedOffset(TextStyle.FULL);
+                    } else {
+                        throw new IllegalArgumentException("Pattern letter count must be 1 or 4: " + cur);
+                    }
                 } else if (cur == 'X') {
                     if (count > 5) {
                         throw new IllegalArgumentException("Too many pattern letters: " + cur);
@@ -1323,18 +1466,21 @@
                     }
                     String zero = (count == 1 ? "+00" : (count % 2 == 0 ? "+0000" : "+00:00"));
                     appendOffset(OffsetIdPrinterParser.PATTERNS[count + (count == 1 ? 0 : 1)], zero);
-                } else if (cur == 'w' || cur == 'e') {
+                } else if (cur == 'W') {
                     // Fields defined by Locale
                     if (count > 1) {
                         throw new IllegalArgumentException("Too many pattern letters: " + cur);
                     }
                     appendInternal(new WeekBasedFieldPrinterParser(cur, count));
-                } else if (cur == 'W') {
+                } else if (cur == 'w') {
                     // Fields defined by Locale
                     if (count > 2) {
                         throw new IllegalArgumentException("Too many pattern letters: " + cur);
                     }
                     appendInternal(new WeekBasedFieldPrinterParser(cur, count));
+                } else if (cur == 'Y') {
+                    // Fields defined by Locale
+                    appendInternal(new WeekBasedFieldPrinterParser(cur, count));
                 } else {
                     throw new IllegalArgumentException("Unknown pattern letter: " + cur);
                 }
@@ -1371,7 +1517,7 @@
                 }
                 optionalEnd();
 
-            } else if (cur == '{' || cur == '}') {
+            } else if (cur == '{' || cur == '}' || cur == '#') {
                 throw new IllegalArgumentException("Pattern includes reserved character: '" + cur + "'");
             } else {
                 appendLiteral(cur);
@@ -1379,10 +1525,12 @@
         }
     }
 
+    @SuppressWarnings("fallthrough")
     private void parseField(char cur, int count, TemporalField field) {
+        boolean standalone = false;
         switch (cur) {
+            case 'u':
             case 'y':
-            case 'Y':
                 if (count == 2) {
                     appendValueReduced(field, 2, 2000);
                 } else if (count < 4) {
@@ -1391,31 +1539,55 @@
                     appendValue(field, count, 19, SignStyle.EXCEEDS_PAD);
                 }
                 break;
+            case 'c':
+                if (count == 2) {
+                    throw new IllegalArgumentException("Invalid pattern \"cc\"");
+                }
+                /*fallthrough*/
+            case 'L':
+            case 'q':
+                standalone = true;
+                /*fallthrough*/
             case 'M':
             case 'Q':
             case 'E':
+            case 'e':
                 switch (count) {
                     case 1:
-                        appendValue(field);
-                        break;
                     case 2:
-                        appendValue(field, 2);
+                        if (cur == 'c' || cur == 'e') {
+                            appendInternal(new WeekBasedFieldPrinterParser(cur, count));
+                        } else if (cur == 'E') {
+                            appendText(field, TextStyle.SHORT);
+                        } else {
+                            if (count == 1) {
+                                appendValue(field);
+                            } else {
+                                appendValue(field, 2);
+                            }
+                        }
                         break;
                     case 3:
-                        appendText(field, TextStyle.SHORT);
+                        appendText(field, standalone ? TextStyle.SHORT_STANDALONE : TextStyle.SHORT);
                         break;
                     case 4:
-                        appendText(field, TextStyle.FULL);
+                        appendText(field, standalone ? TextStyle.FULL_STANDALONE : TextStyle.FULL);
                         break;
                     case 5:
-                        appendText(field, TextStyle.NARROW);
+                        appendText(field, standalone ? TextStyle.NARROW_STANDALONE : TextStyle.NARROW);
                         break;
                     default:
                         throw new IllegalArgumentException("Too many pattern letters: " + cur);
                 }
                 break;
+            case 'a':
+                if (count == 1) {
+                    appendText(field, TextStyle.SHORT);
+                } else {
+                    throw new IllegalArgumentException("Too many pattern letters: " + cur);
+                }
+                break;
             case 'G':
-            case 'a':
                 switch (count) {
                     case 1:
                     case 2:
@@ -1435,6 +1607,37 @@
             case 'S':
                 appendFraction(NANO_OF_SECOND, count, count, false);
                 break;
+            case 'F':
+                if (count == 1) {
+                    appendValue(field);
+                } else {
+                    throw new IllegalArgumentException("Too many pattern letters: " + cur);
+                }
+                break;
+            case 'd':
+            case 'h':
+            case 'H':
+            case 'k':
+            case 'K':
+            case 'm':
+            case 's':
+                if (count == 1) {
+                    appendValue(field);
+                } else if (count == 2) {
+                    appendValue(field, count);
+                } else {
+                    throw new IllegalArgumentException("Too many pattern letters: " + cur);
+                }
+                break;
+            case 'D':
+                if (count == 1) {
+                    appendValue(field);
+                } else if (count <= 3) {
+                    appendValue(field, count);
+                } else {
+                    throw new IllegalArgumentException("Too many pattern letters: " + cur);
+                }
+                break;
             default:
                 if (count == 1) {
                     appendValue(field);
@@ -1448,44 +1651,43 @@
     /** Map of letters to fields. */
     private static final Map<Character, TemporalField> FIELD_MAP = new HashMap<>();
     static {
-        FIELD_MAP.put('G', ChronoField.ERA);                       // Java, LDML (different to both for 1/2 chars)
-        FIELD_MAP.put('y', ChronoField.YEAR);                      // LDML
-        // FIELD_MAP.put('y', ChronoField.YEAR_OF_ERA);            // Java, LDML  // TODO redefine from above
-        // FIELD_MAP.put('u', ChronoField.YEAR);                   // LDML  // TODO
-        // FIELD_MAP.put('Y', IsoFields.WEEK_BASED_YEAR);          // Java7, LDML (needs localized week number)  // TODO
+        // SDF = SimpleDateFormat
+        FIELD_MAP.put('G', ChronoField.ERA);                       // SDF, LDML (different to both for 1/2 chars)
+        FIELD_MAP.put('y', ChronoField.YEAR_OF_ERA);               // SDF, LDML
+        FIELD_MAP.put('u', ChronoField.YEAR);                      // LDML (different in SDF)
         FIELD_MAP.put('Q', IsoFields.QUARTER_OF_YEAR);             // LDML (removed quarter from 310)
-        FIELD_MAP.put('M', ChronoField.MONTH_OF_YEAR);             // Java, LDML
-        // FIELD_MAP.put('w', WeekFields.weekOfYear());            // Java, LDML (needs localized week number)
-        // FIELD_MAP.put('W', WeekFields.weekOfMonth());           // Java, LDML (needs localized week number)
-        FIELD_MAP.put('D', ChronoField.DAY_OF_YEAR);               // Java, LDML
-        FIELD_MAP.put('d', ChronoField.DAY_OF_MONTH);              // Java, LDML
-        FIELD_MAP.put('F', ChronoField.ALIGNED_WEEK_OF_MONTH);     // Java, LDML
-        FIELD_MAP.put('E', ChronoField.DAY_OF_WEEK);               // Java, LDML (different to both for 1/2 chars)
-        // FIELD_MAP.put('e', WeekFields.dayOfWeek());             // LDML (needs localized week number)
-        FIELD_MAP.put('a', ChronoField.AMPM_OF_DAY);               // Java, LDML
-        FIELD_MAP.put('H', ChronoField.HOUR_OF_DAY);               // Java, LDML
-        FIELD_MAP.put('k', ChronoField.CLOCK_HOUR_OF_DAY);         // Java, LDML
-        FIELD_MAP.put('K', ChronoField.HOUR_OF_AMPM);              // Java, LDML
-        FIELD_MAP.put('h', ChronoField.CLOCK_HOUR_OF_AMPM);        // Java, LDML
-        FIELD_MAP.put('m', ChronoField.MINUTE_OF_HOUR);            // Java, LDML
-        FIELD_MAP.put('s', ChronoField.SECOND_OF_MINUTE);          // Java, LDML
-        FIELD_MAP.put('S', ChronoField.NANO_OF_SECOND);            // LDML (Java uses milli-of-second number)
+        FIELD_MAP.put('q', IsoFields.QUARTER_OF_YEAR);             // LDML (stand-alone)
+        FIELD_MAP.put('M', ChronoField.MONTH_OF_YEAR);             // SDF, LDML
+        FIELD_MAP.put('L', ChronoField.MONTH_OF_YEAR);             // SDF, LDML (stand-alone)
+        FIELD_MAP.put('D', ChronoField.DAY_OF_YEAR);               // SDF, LDML
+        FIELD_MAP.put('d', ChronoField.DAY_OF_MONTH);              // SDF, LDML
+        FIELD_MAP.put('F', ChronoField.ALIGNED_DAY_OF_WEEK_IN_MONTH);  // SDF, LDML
+        FIELD_MAP.put('E', ChronoField.DAY_OF_WEEK);               // SDF, LDML (different to both for 1/2 chars)
+        FIELD_MAP.put('c', ChronoField.DAY_OF_WEEK);               // LDML (stand-alone)
+        FIELD_MAP.put('e', ChronoField.DAY_OF_WEEK);               // LDML (needs localized week number)
+        FIELD_MAP.put('a', ChronoField.AMPM_OF_DAY);               // SDF, LDML
+        FIELD_MAP.put('H', ChronoField.HOUR_OF_DAY);               // SDF, LDML
+        FIELD_MAP.put('k', ChronoField.CLOCK_HOUR_OF_DAY);         // SDF, LDML
+        FIELD_MAP.put('K', ChronoField.HOUR_OF_AMPM);              // SDF, LDML
+        FIELD_MAP.put('h', ChronoField.CLOCK_HOUR_OF_AMPM);        // SDF, LDML
+        FIELD_MAP.put('m', ChronoField.MINUTE_OF_HOUR);            // SDF, LDML
+        FIELD_MAP.put('s', ChronoField.SECOND_OF_MINUTE);          // SDF, LDML
+        FIELD_MAP.put('S', ChronoField.NANO_OF_SECOND);            // LDML (SDF uses milli-of-second number)
         FIELD_MAP.put('A', ChronoField.MILLI_OF_DAY);              // LDML
         FIELD_MAP.put('n', ChronoField.NANO_OF_SECOND);            // 310 (proposed for LDML)
         FIELD_MAP.put('N', ChronoField.NANO_OF_DAY);               // 310 (proposed for LDML)
         // 310 - z - time-zone names, matches LDML and SimpleDateFormat 1 to 4
         // 310 - Z - matches SimpleDateFormat and LDML
-        // 310 - V - time-zone id, matches proposed LDML
+        // 310 - V - time-zone id, matches LDML
         // 310 - p - prefix for padding
-        // 310 - X - matches proposed LDML, almost matches JavaSDF for 1, exact match 2&3, extended 4&5
-        // 310 - x - matches proposed LDML
-        // Java - u - clashes with LDML, go with LDML (year-proleptic) here
+        // 310 - X - matches LDML, almost matches SDF for 1, exact match 2&3, extended 4&5
+        // 310 - x - matches LDML
+        // 310 - w, W, and Y are localized forms matching LDML
         // LDML - U - cycle year name, not supported by 310 yet
         // LDML - l - deprecated
         // LDML - j - not relevant
         // LDML - g - modified-julian-day
         // LDML - v,V - extended time-zone names
-        // LDML - q/c/L - standalone quarter/day-of-week/month
     }
 
     //-----------------------------------------------------------------------
@@ -1632,10 +1834,12 @@
 
     //-----------------------------------------------------------------------
     /**
-     * Completes this builder by creating the DateTimeFormatter using the default locale.
+     * Completes this builder by creating the {@code DateTimeFormatter}
+     * using the default locale.
      * <p>
-     * This will create a formatter with the {@link Locale#getDefault(Locale.Category) default FORMAT locale}.
+     * This will create a formatter with the {@linkplain Locale#getDefault(Locale.Category) default FORMAT locale}.
      * Numbers will be printed and parsed using the standard non-localized set of symbols.
+     * The resolver style will be {@link ResolverStyle#SMART SMART}.
      * <p>
      * Calling this method will end any open optional sections by repeatedly
      * calling {@link #optionalEnd()} before creating the formatter.
@@ -1650,10 +1854,12 @@
     }
 
     /**
-     * Completes this builder by creating the DateTimeFormatter using the specified locale.
+     * Completes this builder by creating the {@code DateTimeFormatter}
+     * using the specified locale.
      * <p>
      * This will create a formatter with the specified locale.
      * Numbers will be printed and parsed using the standard non-localized set of symbols.
+     * The resolver style will be {@link ResolverStyle#SMART SMART}.
      * <p>
      * Calling this method will end any open optional sections by repeatedly
      * calling {@link #optionalEnd()} before creating the formatter.
@@ -1665,12 +1871,35 @@
      * @return the created formatter, not null
      */
     public DateTimeFormatter toFormatter(Locale locale) {
+        return toFormatter(locale, ResolverStyle.SMART, null);
+    }
+
+    /**
+     * Completes this builder by creating the formatter.
+     * This uses the default locale.
+     *
+     * @param resolverStyle  the resolver style to use, not null
+     * @return the created formatter, not null
+     */
+    DateTimeFormatter toFormatter(ResolverStyle resolverStyle, Chronology chrono) {
+        return toFormatter(Locale.getDefault(Locale.Category.FORMAT), resolverStyle, chrono);
+    }
+
+    /**
+     * Completes this builder by creating the formatter.
+     *
+     * @param locale  the locale to use for formatting, not null
+     * @param chrono  the chronology to use, may be null
+     * @return the created formatter, not null
+     */
+    private DateTimeFormatter toFormatter(Locale locale, ResolverStyle resolverStyle, Chronology chrono) {
         Objects.requireNonNull(locale, "locale");
         while (active.parent != null) {
             optionalEnd();
         }
         CompositePrinterParser pp = new CompositePrinterParser(printerParsers, false);
-        return new DateTimeFormatter(pp, locale, DateTimeFormatSymbols.STANDARD, null, null);
+        return new DateTimeFormatter(pp, locale, DateTimeFormatSymbols.STANDARD,
+                resolverStyle, null, chrono, null);
     }
 
     //-----------------------------------------------------------------------
@@ -1942,6 +2171,31 @@
 
     //-----------------------------------------------------------------------
     /**
+     * Defaults a value into the parse if not currently present.
+     */
+    static class DefaultValueParser implements DateTimePrinterParser {
+        private final TemporalField field;
+        private final long value;
+
+        DefaultValueParser(TemporalField field, long value) {
+            this.field = field;
+            this.value = value;
+        }
+
+        public boolean format(DateTimePrintContext context, StringBuilder buf) {
+            return true;
+        }
+
+        public int parse(DateTimeParseContext context, CharSequence text, int position) {
+            if (context.getParsed(field) == null) {
+                context.setParsedField(field, value, position, position);
+            }
+            return position;
+        }
+    }
+
+    //-----------------------------------------------------------------------
+    /**
      * Prints or parses a character literal.
      */
     static final class CharLiteralPrinterParser implements DateTimePrinterParser {
@@ -2104,13 +2358,7 @@
 
         @Override
         public boolean format(DateTimePrintContext context, StringBuilder buf) {
-            Chronology chrono = context.getTemporal().query(Queries.chronology());
-            Long valueLong;
-            if (chrono == JapaneseChronology.INSTANCE && field == ChronoField.YEAR) {
-                valueLong = context.getValue(ChronoField.YEAR_OF_ERA);
-            } else {
-                valueLong = context.getValue(field);
-            }
+            Long valueLong = context.getValue(field);
             if (valueLong == null) {
                 return false;
             }
@@ -2281,14 +2529,7 @@
          * @return the new position
          */
         int setValue(DateTimeParseContext context, long value, int errorPos, int successPos) {
-            TemporalField f = field;
-            if (field == ChronoField.YEAR) {
-                Chronology chrono = context.getEffectiveChronology();
-                if (chrono == JapaneseChronology.INSTANCE) {
-                    f = ChronoField.YEAR_OF_ERA;
-                }
-            }
-            return context.setParsedField(f, value, errorPos, successPos);
+            return context.setParsedField(field, value, errorPos, successPos);
         }
 
         @Override
@@ -2570,7 +2811,7 @@
                 return false;
             }
             String text;
-            Chronology chrono = context.getTemporal().query(Queries.chronology());
+            Chronology chrono = context.getTemporal().query(TemporalQuery.chronology());
             if (chrono == null || chrono == IsoChronology.INSTANCE) {
                 text = provider.getText(field, value, textStyle, context.getLocale());
             } else {
@@ -2887,6 +3128,167 @@
 
     //-----------------------------------------------------------------------
     /**
+     * Prints or parses an offset ID.
+     */
+    static final class LocalizedOffsetIdPrinterParser implements DateTimePrinterParser {
+        private final TextStyle style;
+
+        /**
+         * Constructor.
+         *
+         * @param style  the style, not null
+         */
+        LocalizedOffsetIdPrinterParser(TextStyle style) {
+            this.style = style;
+        }
+
+        private static StringBuilder appendHMS(StringBuilder buf, int t) {
+            return buf.append((char)(t / 10 + '0'))
+                      .append((char)(t % 10 + '0'));
+        }
+
+        @Override
+        public boolean format(DateTimePrintContext context, StringBuilder buf) {
+            Long offsetSecs = context.getValue(OFFSET_SECONDS);
+            if (offsetSecs == null) {
+                return false;
+            }
+            String gmtText = "GMT";  // TODO: get localized version of 'GMT'
+            if (gmtText != null) {
+                buf.append(gmtText);
+            }
+            int totalSecs = Math.toIntExact(offsetSecs);
+            if (totalSecs != 0) {
+                int absHours = Math.abs((totalSecs / 3600) % 100);  // anything larger than 99 silently dropped
+                int absMinutes = Math.abs((totalSecs / 60) % 60);
+                int absSeconds = Math.abs(totalSecs % 60);
+                buf.append(totalSecs < 0 ? "-" : "+");
+                if (style == TextStyle.FULL) {
+                    appendHMS(buf, absHours);
+                    buf.append(':');
+                    appendHMS(buf, absMinutes);
+                    if (absSeconds != 0) {
+                       buf.append(':');
+                       appendHMS(buf, absSeconds);
+                    }
+                } else {
+                    if (absHours >= 10) {
+                        buf.append((char)(absHours / 10 + '0'));
+                    }
+                    buf.append((char)(absHours % 10 + '0'));
+                    if (absMinutes != 0 || absSeconds != 0) {
+                        buf.append(':');
+                        appendHMS(buf, absMinutes);
+                        if (absSeconds != 0) {
+                            buf.append(':');
+                            appendHMS(buf, absSeconds);
+                        }
+                    }
+                }
+            }
+            return true;
+        }
+
+        int getDigit(CharSequence text, int position) {
+            char c = text.charAt(position);
+            if (c < '0' || c > '9') {
+                return -1;
+            }
+            return c - '0';
+        }
+
+        @Override
+        public int parse(DateTimeParseContext context, CharSequence text, int position) {
+            int pos = position;
+            int end = pos + text.length();
+            String gmtText = "GMT";  // TODO: get localized version of 'GMT'
+            if (gmtText != null) {
+                if (!context.subSequenceEquals(text, pos, gmtText, 0, gmtText.length())) {
+                    return ~position;
+                }
+                pos += gmtText.length();
+            }
+            // parse normal plus/minus offset
+            int negative = 0;
+            if (pos == end) {
+                return context.setParsedField(OFFSET_SECONDS, 0, position, pos);
+            }
+            char sign = text.charAt(pos);  // IOOBE if invalid position
+            if (sign == '+') {
+                negative = 1;
+            } else if (sign == '-') {
+                negative = -1;
+            } else {
+                return context.setParsedField(OFFSET_SECONDS, 0, position, pos);
+            }
+            pos++;
+            int h = 0;
+            int m = 0;
+            int s = 0;
+            if (style == TextStyle.FULL) {
+                int h1 = getDigit(text, pos++);
+                int h2 = getDigit(text, pos++);
+                if (h1 < 0 || h2 < 0 || text.charAt(pos++) != ':') {
+                    return ~position;
+                }
+                h = h1 * 10 + h2;
+                int m1 = getDigit(text, pos++);
+                int m2 = getDigit(text, pos++);
+                if (m1 < 0 || m2 < 0) {
+                    return ~position;
+                }
+                m = m1 * 10 + m2;
+                if (pos + 2 < end && text.charAt(pos) == ':') {
+                    int s1 = getDigit(text, pos + 1);
+                    int s2 = getDigit(text, pos + 2);
+                    if (s1 >= 0 && s2 >= 0) {
+                        s = s1 * 10 + s2;
+                        pos += 3;
+                    }
+                }
+            } else {
+                h = getDigit(text, pos++);
+                if (h < 0) {
+                    return ~position;
+                }
+                if (pos < end) {
+                    int h2 = getDigit(text, pos);
+                    if (h2 >=0) {
+                        h = h * 10 + h2;
+                        pos++;
+                    }
+                    if (pos + 2 < end && text.charAt(pos) == ':') {
+                        if (pos + 2 < end && text.charAt(pos) == ':') {
+                            int m1 = getDigit(text, pos + 1);
+                            int m2 = getDigit(text, pos + 2);
+                            if (m1 >= 0 && m2 >= 0) {
+                                m = m1 * 10 + m2;
+                                pos += 3;
+                                if (pos + 2 < end && text.charAt(pos) == ':') {
+                                    int s1 = getDigit(text, pos + 1);
+                                    int s2 = getDigit(text, pos + 2);
+                                    if (s1 >= 0 && s2 >= 0) {
+                                        s = s1 * 10 + s2;
+                                        pos += 3;
+                                   }
+                                }
+                            }
+                        }
+                    }
+                }
+            }
+            long offsetSecs = negative * (h * 3600L + m * 60L + s);
+            return context.setParsedField(OFFSET_SECONDS, offsetSecs, position, pos);
+        }
+
+        @Override
+        public String toString() {
+            return "LocalizedOffset(" + style + ")";
+        }
+    }
+
+    //-----------------------------------------------------------------------
+    /**
      * Prints or parses a zone ID.
      */
     static final class ZoneTextPrinterParser extends ZoneIdPrinterParser {
@@ -2898,7 +3300,7 @@
         private Set<String> preferredZones;
 
         ZoneTextPrinterParser(TextStyle textStyle, Set<ZoneId> preferredZones) {
-            super(Queries.zone(), "ZoneText(" + textStyle + ")");
+            super(TemporalQuery.zone(), "ZoneText(" + textStyle + ")");
             this.textStyle = Objects.requireNonNull(textStyle, "textStyle");
             if (preferredZones != null && preferredZones.size() != 0) {
                 this.preferredZones = new HashSet<>();
@@ -2929,12 +3331,12 @@
                 }
                 names = Arrays.copyOfRange(names, 0, 7);
                 names[5] =
-                    TimeZoneNameUtility.retrieveGenericDisplayName(id, TimeZone.LONG,locale);
+                    TimeZoneNameUtility.retrieveGenericDisplayName(id, TimeZone.LONG, locale);
                 if (names[5] == null) {
                     names[5] = names[0]; // use the id
                 }
                 names[6] =
-                    TimeZoneNameUtility.retrieveGenericDisplayName(id, TimeZone.SHORT,locale);
+                    TimeZoneNameUtility.retrieveGenericDisplayName(id, TimeZone.SHORT, locale);
                 if (names[6] == null) {
                     names[6] = names[0];
                 }
@@ -2946,16 +3348,16 @@
             }
             switch (type) {
             case STD:
-                return names[textStyle.ordinal() + 1];
+                return names[textStyle.zoneNameStyleIndex() + 1];
             case DST:
-                return names[textStyle.ordinal() + 3];
+                return names[textStyle.zoneNameStyleIndex() + 3];
             }
-            return names[textStyle.ordinal() + 5];
+            return names[textStyle.zoneNameStyleIndex() + 5];
         }
 
         @Override
         public boolean format(DateTimePrintContext context, StringBuilder buf) {
-            ZoneId zone = context.getValue(Queries.zoneId());
+            ZoneId zone = context.getValue(TemporalQuery.zoneId());
             if (zone == null) {
                 return false;
             }
@@ -3507,14 +3909,14 @@
 
         @Override
         public boolean format(DateTimePrintContext context, StringBuilder buf) {
-            Chronology chrono = context.getValue(Queries.chronology());
+            Chronology chrono = context.getValue(TemporalQuery.chronology());
             if (chrono == null) {
                 return false;
             }
             if (textStyle == null) {
                 buf.append(chrono.getId());
             } else {
-                buf.append(chrono.getId());  // TODO: Use symbols
+                buf.append(getChronologyName(chrono, context.getLocale()));
             }
             return true;
         }
@@ -3529,11 +3931,16 @@
             Chronology bestMatch = null;
             int matchLen = -1;
             for (Chronology chrono : chronos) {
-                String id = chrono.getId();
-                int idLen = id.length();
-                if (idLen > matchLen && context.subSequenceEquals(text, position, id, 0, idLen)) {
+                String name;
+                if (textStyle == null) {
+                    name = chrono.getId();
+                } else {
+                    name = getChronologyName(chrono, context.getLocale());
+                }
+                int nameLen = name.length();
+                if (nameLen > matchLen && context.subSequenceEquals(text, position, name, 0, nameLen)) {
                     bestMatch = chrono;
-                    matchLen = idLen;
+                    matchLen = nameLen;
                 }
             }
             if (bestMatch == null) {
@@ -3542,6 +3949,22 @@
             context.setParsed(bestMatch);
             return position + matchLen;
         }
+
+        /**
+         * Returns the chronology name of the given chrono in the given locale
+         * if available, or the chronology Id otherwise. The regular ResourceBundle
+         * search path is used for looking up the chronology name.
+         *
+         * @param chrono  the chronology, not null
+         * @param locale  the locale, not null
+         * @return the chronology name of chrono in locale, or the id if no name is available
+         * @throws NullPointerException if chrono or locale is null
+         */
+        private String getChronologyName(Chronology chrono, Locale locale) {
+            String key = "calendarname." + chrono.getCalendarType();
+            String name = DateTimeTextProvider.getLocalizedResource(key, locale);
+            return name != null ? name : chrono.getId();
+        }
     }
 
     //-----------------------------------------------------------------------
@@ -3549,6 +3972,9 @@
      * Prints or parses a localized pattern.
      */
     static final class LocalizedPrinterParser implements DateTimePrinterParser {
+        /** Cache of formatters. */
+        private static final ConcurrentMap<String, DateTimeFormatter> FORMATTER_CACHE = new ConcurrentHashMap<>(16, 0.75f, 2);
+
         private final FormatStyle dateStyle;
         private final FormatStyle timeStyle;
 
@@ -3578,6 +4004,9 @@
 
         /**
          * Gets the formatter to use.
+         * <p>
+         * The formatter will be the most appropriate to use for the date and time style in the locale.
+         * For example, some locales will use the month name while others will use the number.
          *
          * @param locale  the locale to use, not null
          * @param chrono  the chronology to use, not null
@@ -3585,8 +4014,32 @@
          * @throws IllegalArgumentException if the formatter cannot be found
          */
         private DateTimeFormatter formatter(Locale locale, Chronology chrono) {
-            return DateTimeFormatStyleProvider.getInstance()
-                                              .getFormatter(dateStyle, timeStyle, chrono, locale);
+            String key = chrono.getId() + '|' + locale.toString() + '|' + dateStyle + timeStyle;
+            DateTimeFormatter formatter = FORMATTER_CACHE.get(key);
+            if (formatter == null) {
+                LocaleResources lr = LocaleProviderAdapter.getResourceBundleBased().getLocaleResources(locale);
+                String pattern = lr.getJavaTimeDateTimePattern(
+                        convertStyle(timeStyle), convertStyle(dateStyle), chrono.getCalendarType());
+                formatter = new DateTimeFormatterBuilder().appendPattern(pattern).toFormatter(locale);
+                DateTimeFormatter old = FORMATTER_CACHE.putIfAbsent(key, formatter);
+                if (old != null) {
+                    formatter = old;
+                }
+            }
+            return formatter;
+        }
+
+        /**
+         * Converts the given FormatStyle to the java.text.DateFormat style.
+         *
+         * @param style  the FormatStyle style
+         * @return the int style, or -1 if style is null, indicating unrequired
+         */
+        private int convertStyle(FormatStyle style) {
+            if (style == null) {
+                return -1;
+            }
+            return style.ordinal();  // indices happen to align
         }
 
         @Override
@@ -3596,7 +4049,6 @@
         }
     }
 
-
     //-----------------------------------------------------------------------
     /**
      * Prints or parses a localized pattern from a localized field.
@@ -3641,14 +4093,23 @@
             WeekFields weekDef = WeekFields.of(locale);
             TemporalField field = null;
             switch (chr) {
+                case 'Y':
+                    field = weekDef.weekBasedYear();
+                    if (count == 2) {
+                        return new ReducedPrinterParser(field, 2, 2000);
+                    } else {
+                        return new NumberPrinterParser(field, count, 19,
+                                (count < 4) ? SignStyle.NORMAL : SignStyle.EXCEEDS_PAD, -1);
+                    }
                 case 'e':
+                case 'c':
                     field = weekDef.dayOfWeek();
                     break;
                 case 'w':
-                    field = weekDef.weekOfMonth();
+                    field = weekDef.weekOfWeekBasedYear();
                     break;
                 case 'W':
-                    field = weekDef.weekOfYear();
+                    field = weekDef.weekOfMonth();
                     break;
                 default:
                     throw new IllegalStateException("unreachable");
@@ -3658,11 +4119,41 @@
 
         @Override
         public String toString() {
-            return String.format("WeekBased(%c%d)", chr, count);
+            StringBuilder sb = new StringBuilder(30);
+            sb.append("Localized(");
+            if (chr == 'Y') {
+                if (count == 1) {
+                    sb.append("WeekBasedYear");
+                } else if (count == 2) {
+                    sb.append("ReducedValue(WeekBasedYear,2,2000)");
+                } else {
+                    sb.append("WeekBasedYear,").append(count).append(",")
+                            .append(19).append(",")
+                            .append((count < 4) ? SignStyle.NORMAL : SignStyle.EXCEEDS_PAD);
+                }
+            } else {
+                switch (chr) {
+                    case 'c':
+                    case 'e':
+                        sb.append("DayOfWeek");
+                        break;
+                    case 'w':
+                        sb.append("WeekOfWeekBasedYear");
+                        break;
+                    case 'W':
+                        sb.append("WeekOfMonth");
+                        break;
+                    default:
+                        break;
+                }
+                sb.append(",");
+                sb.append(count);
+            }
+            sb.append(")");
+            return sb.toString();
         }
     }
 
-
     //-------------------------------------------------------------------------
     /**
      * Length comparator.
@@ -3673,5 +4164,4 @@
             return str1.length() == str2.length() ? str1.compareTo(str2) : str1.length() - str2.length();
         }
     };
-
 }