Home | History | Annotate | Download | only in format
      1 /*
      2  * Copyright (c) 2012, 2015, Oracle and/or its affiliates. All rights reserved.
      3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
      4  *
      5  * This code is free software; you can redistribute it and/or modify it
      6  * under the terms of the GNU General Public License version 2 only, as
      7  * published by the Free Software Foundation.  Oracle designates this
      8  * particular file as subject to the "Classpath" exception as provided
      9  * by Oracle in the LICENSE file that accompanied this code.
     10  *
     11  * This code is distributed in the hope that it will be useful, but WITHOUT
     12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
     13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
     14  * version 2 for more details (a copy is included in the LICENSE file that
     15  * accompanied this code).
     16  *
     17  * You should have received a copy of the GNU General Public License version
     18  * 2 along with this work; if not, write to the Free Software Foundation,
     19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
     20  *
     21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
     22  * or visit www.oracle.com if you need additional information or have any
     23  * questions.
     24  */
     25 
     26 /*
     27  * This file is available under and governed by the GNU General Public
     28  * License version 2 only, as published by the Free Software Foundation.
     29  * However, the following notice accompanied the original version of this
     30  * file:
     31  *
     32  * Copyright (c) 2008-2012, Stephen Colebourne & Michael Nascimento Santos
     33  *
     34  * All rights hg qreserved.
     35  *
     36  * Redistribution and use in source and binary forms, with or without
     37  * modification, are permitted provided that the following conditions are met:
     38  *
     39  *  * Redistributions of source code must retain the above copyright notice,
     40  *    this list of conditions and the following disclaimer.
     41  *
     42  *  * Redistributions in binary form must reproduce the above copyright notice,
     43  *    this list of conditions and the following disclaimer in the documentation
     44  *    and/or other materials provided with the distribution.
     45  *
     46  *  * Neither the name of JSR-310 nor the names of its contributors
     47  *    may be used to endorse or promote products derived from this software
     48  *    without specific prior written permission.
     49  *
     50  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
     51  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
     52  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
     53  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
     54  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
     55  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
     56  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
     57  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
     58  * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
     59  * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
     60  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     61  */
     62 package java.time.format;
     63 
     64 import android.icu.impl.ZoneMeta;
     65 import android.icu.text.LocaleDisplayNames;
     66 import android.icu.text.TimeZoneFormat;
     67 import android.icu.text.TimeZoneNames;
     68 import android.icu.util.Calendar;
     69 import android.icu.util.ULocale;
     70 
     71 import static java.time.temporal.ChronoField.DAY_OF_MONTH;
     72 import static java.time.temporal.ChronoField.HOUR_OF_DAY;
     73 import static java.time.temporal.ChronoField.INSTANT_SECONDS;
     74 import static java.time.temporal.ChronoField.MINUTE_OF_HOUR;
     75 import static java.time.temporal.ChronoField.MONTH_OF_YEAR;
     76 import static java.time.temporal.ChronoField.NANO_OF_SECOND;
     77 import static java.time.temporal.ChronoField.OFFSET_SECONDS;
     78 import static java.time.temporal.ChronoField.SECOND_OF_MINUTE;
     79 import static java.time.temporal.ChronoField.YEAR;
     80 
     81 import java.lang.ref.SoftReference;
     82 import java.math.BigDecimal;
     83 import java.math.BigInteger;
     84 import java.math.RoundingMode;
     85 import java.text.ParsePosition;
     86 import java.time.DateTimeException;
     87 import java.time.Instant;
     88 import java.time.LocalDate;
     89 import java.time.LocalDateTime;
     90 import java.time.ZoneId;
     91 import java.time.ZoneOffset;
     92 import java.time.chrono.ChronoLocalDate;
     93 import java.time.chrono.Chronology;
     94 import java.time.chrono.IsoChronology;
     95 import java.time.format.DateTimeTextProvider.LocaleStore;
     96 import java.time.temporal.ChronoField;
     97 import java.time.temporal.IsoFields;
     98 import java.time.temporal.TemporalAccessor;
     99 import java.time.temporal.TemporalField;
    100 import java.time.temporal.TemporalQueries;
    101 import java.time.temporal.TemporalQuery;
    102 import java.time.temporal.ValueRange;
    103 import java.time.temporal.WeekFields;
    104 import java.time.zone.ZoneRulesProvider;
    105 import java.util.AbstractMap;
    106 import java.util.AbstractMap.SimpleImmutableEntry;
    107 import java.util.ArrayList;
    108 import java.util.Collections;
    109 import java.util.Comparator;
    110 import java.util.HashMap;
    111 import java.util.HashSet;
    112 import java.util.Iterator;
    113 import java.util.LinkedHashMap;
    114 import java.util.List;
    115 import java.util.Locale;
    116 import java.util.Map;
    117 import java.util.Map.Entry;
    118 import java.util.Objects;
    119 import java.util.Set;
    120 import java.util.TimeZone;
    121 import java.util.concurrent.ConcurrentHashMap;
    122 import java.util.concurrent.ConcurrentMap;
    123 
    124 /**
    125  * Builder to create date-time formatters.
    126  * <p>
    127  * This allows a {@code DateTimeFormatter} to be created.
    128  * All date-time formatters are created ultimately using this builder.
    129  * <p>
    130  * The basic elements of date-time can all be added:
    131  * <ul>
    132  * <li>Value - a numeric value</li>
    133  * <li>Fraction - a fractional value including the decimal place. Always use this when
    134  * outputting fractions to ensure that the fraction is parsed correctly</li>
    135  * <li>Text - the textual equivalent for the value</li>
    136  * <li>OffsetId/Offset - the {@linkplain ZoneOffset zone offset}</li>
    137  * <li>ZoneId - the {@linkplain ZoneId time-zone} id</li>
    138  * <li>ZoneText - the name of the time-zone</li>
    139  * <li>ChronologyId - the {@linkplain Chronology chronology} id</li>
    140  * <li>ChronologyText - the name of the chronology</li>
    141  * <li>Literal - a text literal</li>
    142  * <li>Nested and Optional - formats can be nested or made optional</li>
    143  * </ul>
    144  * In addition, any of the elements may be decorated by padding, either with spaces or any other character.
    145  * <p>
    146  * Finally, a shorthand pattern, mostly compatible with {@code java.text.SimpleDateFormat SimpleDateFormat}
    147  * can be used, see {@link #appendPattern(String)}.
    148  * In practice, this simply parses the pattern and calls other methods on the builder.
    149  *
    150  * @implSpec
    151  * This class is a mutable builder intended for use from a single thread.
    152  *
    153  * @since 1.8
    154  */
    155 public final class DateTimeFormatterBuilder {
    156 
    157     /**
    158      * Query for a time-zone that is region-only.
    159      */
    160     private static final TemporalQuery<ZoneId> QUERY_REGION_ONLY = (temporal) -> {
    161         ZoneId zone = temporal.query(TemporalQueries.zoneId());
    162         return (zone != null && zone instanceof ZoneOffset == false ? zone : null);
    163     };
    164 
    165     /**
    166      * The currently active builder, used by the outermost builder.
    167      */
    168     private DateTimeFormatterBuilder active = this;
    169     /**
    170      * The parent builder, null for the outermost builder.
    171      */
    172     private final DateTimeFormatterBuilder parent;
    173     /**
    174      * The list of printers that will be used.
    175      */
    176     private final List<DateTimePrinterParser> printerParsers = new ArrayList<>();
    177     /**
    178      * Whether this builder produces an optional formatter.
    179      */
    180     private final boolean optional;
    181     /**
    182      * The width to pad the next field to.
    183      */
    184     private int padNextWidth;
    185     /**
    186      * The character to pad the next field with.
    187      */
    188     private char padNextChar;
    189     /**
    190      * The index of the last variable width value parser.
    191      */
    192     private int valueParserIndex = -1;
    193 
    194     /**
    195      * Gets the formatting pattern for date and time styles for a locale and chronology.
    196      * The locale and chronology are used to lookup the locale specific format
    197      * for the requested dateStyle and/or timeStyle.
    198      *
    199      * @param dateStyle  the FormatStyle for the date, null for time-only pattern
    200      * @param timeStyle  the FormatStyle for the time, null for date-only pattern
    201      * @param chrono  the Chronology, non-null
    202      * @param locale  the locale, non-null
    203      * @return the locale and Chronology specific formatting pattern
    204      * @throws IllegalArgumentException if both dateStyle and timeStyle are null
    205      */
    206     public static String getLocalizedDateTimePattern(FormatStyle dateStyle, FormatStyle timeStyle,
    207             Chronology chrono, Locale locale) {
    208         Objects.requireNonNull(locale, "locale");
    209         Objects.requireNonNull(chrono, "chrono");
    210         if (dateStyle == null && timeStyle == null) {
    211             throw new IllegalArgumentException("Either dateStyle or timeStyle must be non-null");
    212         }
    213 
    214         // Android-changed: get format string from ICU.
    215         String pattern = Calendar.getDateTimeFormatString(
    216                 ULocale.forLocale(locale), chrono.getCalendarType(),
    217                 convertStyle(dateStyle), convertStyle(timeStyle));
    218         return pattern;
    219     }
    220 
    221     /**
    222      * Converts the given FormatStyle to the java.text.DateFormat style.
    223      *
    224      * @param style  the FormatStyle style
    225      * @return the int style, or -1 if style is null, indicating un-required
    226      */
    227     private static int convertStyle(FormatStyle style) {
    228         if (style == null) {
    229             return -1;
    230         }
    231         return style.ordinal();  // indices happen to align
    232     }
    233 
    234     /**
    235      * Constructs a new instance of the builder.
    236      */
    237     public DateTimeFormatterBuilder() {
    238         super();
    239         parent = null;
    240         optional = false;
    241     }
    242 
    243     /**
    244      * Constructs a new instance of the builder.
    245      *
    246      * @param parent  the parent builder, not null
    247      * @param optional  whether the formatter is optional, not null
    248      */
    249     private DateTimeFormatterBuilder(DateTimeFormatterBuilder parent, boolean optional) {
    250         super();
    251         this.parent = parent;
    252         this.optional = optional;
    253     }
    254 
    255     //-----------------------------------------------------------------------
    256     /**
    257      * Changes the parse style to be case sensitive for the remainder of the formatter.
    258      * <p>
    259      * Parsing can be case sensitive or insensitive - by default it is case sensitive.
    260      * This method allows the case sensitivity setting of parsing to be changed.
    261      * <p>
    262      * Calling this method changes the state of the builder such that all
    263      * subsequent builder method calls will parse text in case sensitive mode.
    264      * See {@link #parseCaseInsensitive} for the opposite setting.
    265      * The parse case sensitive/insensitive methods may be called at any point
    266      * in the builder, thus the parser can swap between case parsing modes
    267      * multiple times during the parse.
    268      * <p>
    269      * Since the default is case sensitive, this method should only be used after
    270      * a previous call to {@code #parseCaseInsensitive}.
    271      *
    272      * @return this, for chaining, not null
    273      */
    274     public DateTimeFormatterBuilder parseCaseSensitive() {
    275         appendInternal(SettingsParser.SENSITIVE);
    276         return this;
    277     }
    278 
    279     /**
    280      * Changes the parse style to be case insensitive for the remainder of the formatter.
    281      * <p>
    282      * Parsing can be case sensitive or insensitive - by default it is case sensitive.
    283      * This method allows the case sensitivity setting of parsing to be changed.
    284      * <p>
    285      * Calling this method changes the state of the builder such that all
    286      * subsequent builder method calls will parse text in case insensitive mode.
    287      * See {@link #parseCaseSensitive()} for the opposite setting.
    288      * The parse case sensitive/insensitive methods may be called at any point
    289      * in the builder, thus the parser can swap between case parsing modes
    290      * multiple times during the parse.
    291      *
    292      * @return this, for chaining, not null
    293      */
    294     public DateTimeFormatterBuilder parseCaseInsensitive() {
    295         appendInternal(SettingsParser.INSENSITIVE);
    296         return this;
    297     }
    298 
    299     //-----------------------------------------------------------------------
    300     /**
    301      * Changes the parse style to be strict for the remainder of the formatter.
    302      * <p>
    303      * Parsing can be strict or lenient - by default its strict.
    304      * This controls the degree of flexibility in matching the text and sign styles.
    305      * <p>
    306      * When used, this method changes the parsing to be strict from this point onwards.
    307      * As strict is the default, this is normally only needed after calling {@link #parseLenient()}.
    308      * The change will remain in force until the end of the formatter that is eventually
    309      * constructed or until {@code parseLenient} is called.
    310      *
    311      * @return this, for chaining, not null
    312      */
    313     public DateTimeFormatterBuilder parseStrict() {
    314         appendInternal(SettingsParser.STRICT);
    315         return this;
    316     }
    317 
    318     /**
    319      * Changes the parse style to be lenient for the remainder of the formatter.
    320      * Note that case sensitivity is set separately to this method.
    321      * <p>
    322      * Parsing can be strict or lenient - by default its strict.
    323      * This controls the degree of flexibility in matching the text and sign styles.
    324      * Applications calling this method should typically also call {@link #parseCaseInsensitive()}.
    325      * <p>
    326      * When used, this method changes the parsing to be lenient from this point onwards.
    327      * The change will remain in force until the end of the formatter that is eventually
    328      * constructed or until {@code parseStrict} is called.
    329      *
    330      * @return this, for chaining, not null
    331      */
    332     public DateTimeFormatterBuilder parseLenient() {
    333         appendInternal(SettingsParser.LENIENT);
    334         return this;
    335     }
    336 
    337     //-----------------------------------------------------------------------
    338     /**
    339      * Appends a default value for a field to the formatter for use in parsing.
    340      * <p>
    341      * This appends an instruction to the builder to inject a default value
    342      * into the parsed result. This is especially useful in conjunction with
    343      * optional parts of the formatter.
    344      * <p>
    345      * For example, consider a formatter that parses the year, followed by
    346      * an optional month, with a further optional day-of-month. Using such a
    347      * formatter would require the calling code to check whether a full date,
    348      * year-month or just a year had been parsed. This method can be used to
    349      * default the month and day-of-month to a sensible value, such as the
    350      * first of the month, allowing the calling code to always get a date.
    351      * <p>
    352      * During formatting, this method has no effect.
    353      * <p>
    354      * During parsing, the current state of the parse is inspected.
    355      * If the specified field has no associated value, because it has not been
    356      * parsed successfully at that point, then the specified value is injected
    357      * into the parse result. Injection is immediate, thus the field-value pair
    358      * will be visible to any subsequent elements in the formatter.
    359      * As such, this method is normally called at the end of the builder.
    360      *
    361      * @param field  the field to default the value of, not null
    362      * @param value  the value to default the field to
    363      * @return this, for chaining, not null
    364      */
    365     public DateTimeFormatterBuilder parseDefaulting(TemporalField field, long value) {
    366         Objects.requireNonNull(field, "field");
    367         appendInternal(new DefaultValueParser(field, value));
    368         return this;
    369     }
    370 
    371     //-----------------------------------------------------------------------
    372     /**
    373      * Appends the value of a date-time field to the formatter using a normal
    374      * output style.
    375      * <p>
    376      * The value of the field will be output during a format.
    377      * If the value cannot be obtained then an exception will be thrown.
    378      * <p>
    379      * The value will be printed as per the normal format of an integer value.
    380      * Only negative numbers will be signed. No padding will be added.
    381      * <p>
    382      * The parser for a variable width value such as this normally behaves greedily,
    383      * requiring one digit, but accepting as many digits as possible.
    384      * This behavior can be affected by 'adjacent value parsing'.
    385      * See {@link #appendValue(java.time.temporal.TemporalField, int)} for full details.
    386      *
    387      * @param field  the field to append, not null
    388      * @return this, for chaining, not null
    389      */
    390     public DateTimeFormatterBuilder appendValue(TemporalField field) {
    391         Objects.requireNonNull(field, "field");
    392         appendValue(new NumberPrinterParser(field, 1, 19, SignStyle.NORMAL));
    393         return this;
    394     }
    395 
    396     /**
    397      * Appends the value of a date-time field to the formatter using a fixed
    398      * width, zero-padded approach.
    399      * <p>
    400      * The value of the field will be output during a format.
    401      * If the value cannot be obtained then an exception will be thrown.
    402      * <p>
    403      * The value will be zero-padded on the left. If the size of the value
    404      * means that it cannot be printed within the width then an exception is thrown.
    405      * If the value of the field is negative then an exception is thrown during formatting.
    406      * <p>
    407      * This method supports a special technique of parsing known as 'adjacent value parsing'.
    408      * This technique solves the problem where a value, variable or fixed width, is followed by one or more
    409      * fixed length values. The standard parser is greedy, and thus it would normally
    410      * steal the digits that are needed by the fixed width value parsers that follow the
    411      * variable width one.
    412      * <p>
    413      * No action is required to initiate 'adjacent value parsing'.
    414      * When a call to {@code appendValue} is made, the builder
    415      * enters adjacent value parsing setup mode. If the immediately subsequent method
    416      * call or calls on the same builder are for a fixed width value, then the parser will reserve
    417      * space so that the fixed width values can be parsed.
    418      * <p>
    419      * For example, consider {@code builder.appendValue(YEAR).appendValue(MONTH_OF_YEAR, 2);}
    420      * The year is a variable width parse of between 1 and 19 digits.
    421      * The month is a fixed width parse of 2 digits.
    422      * Because these were appended to the same builder immediately after one another,
    423      * the year parser will reserve two digits for the month to parse.
    424      * Thus, the text '201106' will correctly parse to a year of 2011 and a month of 6.
    425      * Without adjacent value parsing, the year would greedily parse all six digits and leave
    426      * nothing for the month.
    427      * <p>
    428      * Adjacent value parsing applies to each set of fixed width not-negative values in the parser
    429      * that immediately follow any kind of value, variable or fixed width.
    430      * Calling any other append method will end the setup of adjacent value parsing.
    431      * Thus, in the unlikely event that you need to avoid adjacent value parsing behavior,
    432      * simply add the {@code appendValue} to another {@code DateTimeFormatterBuilder}
    433      * and add that to this builder.
    434      * <p>
    435      * If adjacent parsing is active, then parsing must match exactly the specified
    436      * number of digits in both strict and lenient modes.
    437      * In addition, no positive or negative sign is permitted.
    438      *
    439      * @param field  the field to append, not null
    440      * @param width  the width of the printed field, from 1 to 19
    441      * @return this, for chaining, not null
    442      * @throws IllegalArgumentException if the width is invalid
    443      */
    444     public DateTimeFormatterBuilder appendValue(TemporalField field, int width) {
    445         Objects.requireNonNull(field, "field");
    446         if (width < 1 || width > 19) {
    447             throw new IllegalArgumentException("The width must be from 1 to 19 inclusive but was " + width);
    448         }
    449         NumberPrinterParser pp = new NumberPrinterParser(field, width, width, SignStyle.NOT_NEGATIVE);
    450         appendValue(pp);
    451         return this;
    452     }
    453 
    454     /**
    455      * Appends the value of a date-time field to the formatter providing full
    456      * control over formatting.
    457      * <p>
    458      * The value of the field will be output during a format.
    459      * If the value cannot be obtained then an exception will be thrown.
    460      * <p>
    461      * This method provides full control of the numeric formatting, including
    462      * zero-padding and the positive/negative sign.
    463      * <p>
    464      * The parser for a variable width value such as this normally behaves greedily,
    465      * accepting as many digits as possible.
    466      * This behavior can be affected by 'adjacent value parsing'.
    467      * See {@link #appendValue(java.time.temporal.TemporalField, int)} for full details.
    468      * <p>
    469      * In strict parsing mode, the minimum number of parsed digits is {@code minWidth}
    470      * and the maximum is {@code maxWidth}.
    471      * In lenient parsing mode, the minimum number of parsed digits is one
    472      * and the maximum is 19 (except as limited by adjacent value parsing).
    473      * <p>
    474      * If this method is invoked with equal minimum and maximum widths and a sign style of
    475      * {@code NOT_NEGATIVE} then it delegates to {@code appendValue(TemporalField,int)}.
    476      * In this scenario, the formatting and parsing behavior described there occur.
    477      *
    478      * @param field  the field to append, not null
    479      * @param minWidth  the minimum field width of the printed field, from 1 to 19
    480      * @param maxWidth  the maximum field width of the printed field, from 1 to 19
    481      * @param signStyle  the positive/negative output style, not null
    482      * @return this, for chaining, not null
    483      * @throws IllegalArgumentException if the widths are invalid
    484      */
    485     public DateTimeFormatterBuilder appendValue(
    486             TemporalField field, int minWidth, int maxWidth, SignStyle signStyle) {
    487         if (minWidth == maxWidth && signStyle == SignStyle.NOT_NEGATIVE) {
    488             return appendValue(field, maxWidth);
    489         }
    490         Objects.requireNonNull(field, "field");
    491         Objects.requireNonNull(signStyle, "signStyle");
    492         if (minWidth < 1 || minWidth > 19) {
    493             throw new IllegalArgumentException("The minimum width must be from 1 to 19 inclusive but was " + minWidth);
    494         }
    495         if (maxWidth < 1 || maxWidth > 19) {
    496             throw new IllegalArgumentException("The maximum width must be from 1 to 19 inclusive but was " + maxWidth);
    497         }
    498         if (maxWidth < minWidth) {
    499             throw new IllegalArgumentException("The maximum width must exceed or equal the minimum width but " +
    500                     maxWidth + " < " + minWidth);
    501         }
    502         NumberPrinterParser pp = new NumberPrinterParser(field, minWidth, maxWidth, signStyle);
    503         appendValue(pp);
    504         return this;
    505     }
    506 
    507     //-----------------------------------------------------------------------
    508     /**
    509      * Appends the reduced value of a date-time field to the formatter.
    510      * <p>
    511      * Since fields such as year vary by chronology, it is recommended to use the
    512      * {@link #appendValueReduced(TemporalField, int, int, ChronoLocalDate)} date}
    513      * variant of this method in most cases. This variant is suitable for
    514      * simple fields or working with only the ISO chronology.
    515      * <p>
    516      * For formatting, the {@code width} and {@code maxWidth} are used to
    517      * determine the number of characters to format.
    518      * If they are equal then the format is fixed width.
    519      * If the value of the field is within the range of the {@code baseValue} using
    520      * {@code width} characters then the reduced value is formatted otherwise the value is
    521      * truncated to fit {@code maxWidth}.
    522      * The rightmost characters are output to match the width, left padding with zero.
    523      * <p>
    524      * For strict parsing, the number of characters allowed by {@code width} to {@code maxWidth} are parsed.
    525      * For lenient parsing, the number of characters must be at least 1 and less than 10.
    526      * If the number of digits parsed is equal to {@code width} and the value is positive,
    527      * the value of the field is computed to be the first number greater than
    528      * or equal to the {@code baseValue} with the same least significant characters,
    529      * otherwise the value parsed is the field value.
    530      * This allows a reduced value to be entered for values in range of the baseValue
    531      * and width and absolute values can be entered for values outside the range.
    532      * <p>
    533      * For example, a base value of {@code 1980} and a width of {@code 2} will have
    534      * valid values from {@code 1980} to {@code 2079}.
    535      * During parsing, the text {@code "12"} will result in the value {@code 2012} as that
    536      * is the value within the range where the last two characters are "12".
    537      * By contrast, parsing the text {@code "1915"} will result in the value {@code 1915}.
    538      *
    539      * @param field  the field to append, not null
    540      * @param width  the field width of the printed and parsed field, from 1 to 10
    541      * @param maxWidth  the maximum field width of the printed field, from 1 to 10
    542      * @param baseValue  the base value of the range of valid values
    543      * @return this, for chaining, not null
    544      * @throws IllegalArgumentException if the width or base value is invalid
    545      */
    546     public DateTimeFormatterBuilder appendValueReduced(TemporalField field,
    547             int width, int maxWidth, int baseValue) {
    548         Objects.requireNonNull(field, "field");
    549         ReducedPrinterParser pp = new ReducedPrinterParser(field, width, maxWidth, baseValue, null);
    550         appendValue(pp);
    551         return this;
    552     }
    553 
    554     /**
    555      * Appends the reduced value of a date-time field to the formatter.
    556      * <p>
    557      * This is typically used for formatting and parsing a two digit year.
    558      * <p>
    559      * The base date is used to calculate the full value during parsing.
    560      * For example, if the base date is 1950-01-01 then parsed values for
    561      * a two digit year parse will be in the range 1950-01-01 to 2049-12-31.
    562      * Only the year would be extracted from the date, thus a base date of
    563      * 1950-08-25 would also parse to the range 1950-01-01 to 2049-12-31.
    564      * This behavior is necessary to support fields such as week-based-year
    565      * or other calendar systems where the parsed value does not align with
    566      * standard ISO years.
    567      * <p>
    568      * The exact behavior is as follows. Parse the full set of fields and
    569      * determine the effective chronology using the last chronology if
    570      * it appears more than once. Then convert the base date to the
    571      * effective chronology. Then extract the specified field from the
    572      * chronology-specific base date and use it to determine the
    573      * {@code baseValue} used below.
    574      * <p>
    575      * For formatting, the {@code width} and {@code maxWidth} are used to
    576      * determine the number of characters to format.
    577      * If they are equal then the format is fixed width.
    578      * If the value of the field is within the range of the {@code baseValue} using
    579      * {@code width} characters then the reduced value is formatted otherwise the value is
    580      * truncated to fit {@code maxWidth}.
    581      * The rightmost characters are output to match the width, left padding with zero.
    582      * <p>
    583      * For strict parsing, the number of characters allowed by {@code width} to {@code maxWidth} are parsed.
    584      * For lenient parsing, the number of characters must be at least 1 and less than 10.
    585      * If the number of digits parsed is equal to {@code width} and the value is positive,
    586      * the value of the field is computed to be the first number greater than
    587      * or equal to the {@code baseValue} with the same least significant characters,
    588      * otherwise the value parsed is the field value.
    589      * This allows a reduced value to be entered for values in range of the baseValue
    590      * and width and absolute values can be entered for values outside the range.
    591      * <p>
    592      * For example, a base value of {@code 1980} and a width of {@code 2} will have
    593      * valid values from {@code 1980} to {@code 2079}.
    594      * During parsing, the text {@code "12"} will result in the value {@code 2012} as that
    595      * is the value within the range where the last two characters are "12".
    596      * By contrast, parsing the text {@code "1915"} will result in the value {@code 1915}.
    597      *
    598      * @param field  the field to append, not null
    599      * @param width  the field width of the printed and parsed field, from 1 to 10
    600      * @param maxWidth  the maximum field width of the printed field, from 1 to 10
    601      * @param baseDate  the base date used to calculate the base value for the range
    602      *  of valid values in the parsed chronology, not null
    603      * @return this, for chaining, not null
    604      * @throws IllegalArgumentException if the width or base value is invalid
    605      */
    606     public DateTimeFormatterBuilder appendValueReduced(
    607             TemporalField field, int width, int maxWidth, ChronoLocalDate baseDate) {
    608         Objects.requireNonNull(field, "field");
    609         Objects.requireNonNull(baseDate, "baseDate");
    610         ReducedPrinterParser pp = new ReducedPrinterParser(field, width, maxWidth, 0, baseDate);
    611         appendValue(pp);
    612         return this;
    613     }
    614 
    615     /**
    616      * Appends a fixed or variable width printer-parser handling adjacent value mode.
    617      * If a PrinterParser is not active then the new PrinterParser becomes
    618      * the active PrinterParser.
    619      * Otherwise, the active PrinterParser is modified depending on the new PrinterParser.
    620      * If the new PrinterParser is fixed width and has sign style {@code NOT_NEGATIVE}
    621      * then its width is added to the active PP and
    622      * the new PrinterParser is forced to be fixed width.
    623      * If the new PrinterParser is variable width, the active PrinterParser is changed
    624      * to be fixed width and the new PrinterParser becomes the active PP.
    625      *
    626      * @param pp  the printer-parser, not null
    627      * @return this, for chaining, not null
    628      */
    629     private DateTimeFormatterBuilder appendValue(NumberPrinterParser pp) {
    630         if (active.valueParserIndex >= 0) {
    631             final int activeValueParser = active.valueParserIndex;
    632 
    633             // adjacent parsing mode, update setting in previous parsers
    634             NumberPrinterParser basePP = (NumberPrinterParser) active.printerParsers.get(activeValueParser);
    635             if (pp.minWidth == pp.maxWidth && pp.signStyle == SignStyle.NOT_NEGATIVE) {
    636                 // Append the width to the subsequentWidth of the active parser
    637                 basePP = basePP.withSubsequentWidth(pp.maxWidth);
    638                 // Append the new parser as a fixed width
    639                 appendInternal(pp.withFixedWidth());
    640                 // Retain the previous active parser
    641                 active.valueParserIndex = activeValueParser;
    642             } else {
    643                 // Modify the active parser to be fixed width
    644                 basePP = basePP.withFixedWidth();
    645                 // The new parser becomes the mew active parser
    646                 active.valueParserIndex = appendInternal(pp);
    647             }
    648             // Replace the modified parser with the updated one
    649             active.printerParsers.set(activeValueParser, basePP);
    650         } else {
    651             // The new Parser becomes the active parser
    652             active.valueParserIndex = appendInternal(pp);
    653         }
    654         return this;
    655     }
    656 
    657     //-----------------------------------------------------------------------
    658     /**
    659      * Appends the fractional value of a date-time field to the formatter.
    660      * <p>
    661      * The fractional value of the field will be output including the
    662      * preceding decimal point. The preceding value is not output.
    663      * For example, the second-of-minute value of 15 would be output as {@code .25}.
    664      * <p>
    665      * The width of the printed fraction can be controlled. Setting the
    666      * minimum width to zero will cause no output to be generated.
    667      * The printed fraction will have the minimum width necessary between
    668      * the minimum and maximum widths - trailing zeroes are omitted.
    669      * No rounding occurs due to the maximum width - digits are simply dropped.
    670      * <p>
    671      * When parsing in strict mode, the number of parsed digits must be between
    672      * the minimum and maximum width. When parsing in lenient mode, the minimum
    673      * width is considered to be zero and the maximum is nine.
    674      * <p>
    675      * If the value cannot be obtained then an exception will be thrown.
    676      * If the value is negative an exception will be thrown.
    677      * If the field does not have a fixed set of valid values then an
    678      * exception will be thrown.
    679      * If the field value in the date-time to be printed is invalid it
    680      * cannot be printed and an exception will be thrown.
    681      *
    682      * @param field  the field to append, not null
    683      * @param minWidth  the minimum width of the field excluding the decimal point, from 0 to 9
    684      * @param maxWidth  the maximum width of the field excluding the decimal point, from 1 to 9
    685      * @param decimalPoint  whether to output the localized decimal point symbol
    686      * @return this, for chaining, not null
    687      * @throws IllegalArgumentException if the field has a variable set of valid values or
    688      *  either width is invalid
    689      */
    690     public DateTimeFormatterBuilder appendFraction(
    691             TemporalField field, int minWidth, int maxWidth, boolean decimalPoint) {
    692         appendInternal(new FractionPrinterParser(field, minWidth, maxWidth, decimalPoint));
    693         return this;
    694     }
    695 
    696     //-----------------------------------------------------------------------
    697     /**
    698      * Appends the text of a date-time field to the formatter using the full
    699      * text style.
    700      * <p>
    701      * The text of the field will be output during a format.
    702      * The value must be within the valid range of the field.
    703      * If the value cannot be obtained then an exception will be thrown.
    704      * If the field has no textual representation, then the numeric value will be used.
    705      * <p>
    706      * The value will be printed as per the normal format of an integer value.
    707      * Only negative numbers will be signed. No padding will be added.
    708      *
    709      * @param field  the field to append, not null
    710      * @return this, for chaining, not null
    711      */
    712     public DateTimeFormatterBuilder appendText(TemporalField field) {
    713         return appendText(field, TextStyle.FULL);
    714     }
    715 
    716     /**
    717      * Appends the text of a date-time field to the formatter.
    718      * <p>
    719      * The text of the field will be output during a format.
    720      * The value must be within the valid range of the field.
    721      * If the value cannot be obtained then an exception will be thrown.
    722      * If the field has no textual representation, then the numeric value will be used.
    723      * <p>
    724      * The value will be printed as per the normal format of an integer value.
    725      * Only negative numbers will be signed. No padding will be added.
    726      *
    727      * @param field  the field to append, not null
    728      * @param textStyle  the text style to use, not null
    729      * @return this, for chaining, not null
    730      */
    731     public DateTimeFormatterBuilder appendText(TemporalField field, TextStyle textStyle) {
    732         Objects.requireNonNull(field, "field");
    733         Objects.requireNonNull(textStyle, "textStyle");
    734         appendInternal(new TextPrinterParser(field, textStyle, DateTimeTextProvider.getInstance()));
    735         return this;
    736     }
    737 
    738     /**
    739      * Appends the text of a date-time field to the formatter using the specified
    740      * map to supply the text.
    741      * <p>
    742      * The standard text outputting methods use the localized text in the JDK.
    743      * This method allows that text to be specified directly.
    744      * The supplied map is not validated by the builder to ensure that formatting or
    745      * parsing is possible, thus an invalid map may throw an error during later use.
    746      * <p>
    747      * Supplying the map of text provides considerable flexibility in formatting and parsing.
    748      * For example, a legacy application might require or supply the months of the
    749      * year as "JNY", "FBY", "MCH" etc. These do not match the standard set of text
    750      * for localized month names. Using this method, a map can be created which
    751      * defines the connection between each value and the text:
    752      * <pre>
    753      * Map&lt;Long, String&gt; map = new HashMap&lt;&gt;();
    754      * map.put(1L, "JNY");
    755      * map.put(2L, "FBY");
    756      * map.put(3L, "MCH");
    757      * ...
    758      * builder.appendText(MONTH_OF_YEAR, map);
    759      * </pre>
    760      * <p>
    761      * Other uses might be to output the value with a suffix, such as "1st", "2nd", "3rd",
    762      * or as Roman numerals "I", "II", "III", "IV".
    763      * <p>
    764      * During formatting, the value is obtained and checked that it is in the valid range.
    765      * If text is not available for the value then it is output as a number.
    766      * During parsing, the parser will match against the map of text and numeric values.
    767      *
    768      * @param field  the field to append, not null
    769      * @param textLookup  the map from the value to the text
    770      * @return this, for chaining, not null
    771      */
    772     public DateTimeFormatterBuilder appendText(TemporalField field, Map<Long, String> textLookup) {
    773         Objects.requireNonNull(field, "field");
    774         Objects.requireNonNull(textLookup, "textLookup");
    775         Map<Long, String> copy = new LinkedHashMap<>(textLookup);
    776         Map<TextStyle, Map<Long, String>> map = Collections.singletonMap(TextStyle.FULL, copy);
    777         final LocaleStore store = new LocaleStore(map);
    778         DateTimeTextProvider provider = new DateTimeTextProvider() {
    779             @Override
    780             public String getText(TemporalField field, long value, TextStyle style, Locale locale) {
    781                 return store.getText(value, style);
    782             }
    783             @Override
    784             public Iterator<Entry<String, Long>> getTextIterator(TemporalField field, TextStyle style, Locale locale) {
    785                 return store.getTextIterator(style);
    786             }
    787         };
    788         appendInternal(new TextPrinterParser(field, TextStyle.FULL, provider));
    789         return this;
    790     }
    791 
    792     //-----------------------------------------------------------------------
    793     /**
    794      * Appends an instant using ISO-8601 to the formatter, formatting fractional
    795      * digits in groups of three.
    796      * <p>
    797      * Instants have a fixed output format.
    798      * They are converted to a date-time with a zone-offset of UTC and formatted
    799      * using the standard ISO-8601 format.
    800      * With this method, formatting nano-of-second outputs zero, three, six
    801      * or nine digits digits as necessary.
    802      * The localized decimal style is not used.
    803      * <p>
    804      * The instant is obtained using {@link ChronoField#INSTANT_SECONDS INSTANT_SECONDS}
    805      * and optionally (@code NANO_OF_SECOND). The value of {@code INSTANT_SECONDS}
    806      * may be outside the maximum range of {@code LocalDateTime}.
    807      * <p>
    808      * The {@linkplain ResolverStyle resolver style} has no effect on instant parsing.
    809      * The end-of-day time of '24:00' is handled as midnight at the start of the following day.
    810      * The leap-second time of '23:59:59' is handled to some degree, see
    811      * {@link DateTimeFormatter#parsedLeapSecond()} for full details.
    812      * <p>
    813      * An alternative to this method is to format/parse the instant as a single
    814      * epoch-seconds value. That is achieved using {@code appendValue(INSTANT_SECONDS)}.
    815      *
    816      * @return this, for chaining, not null
    817      */
    818     public DateTimeFormatterBuilder appendInstant() {
    819         appendInternal(new InstantPrinterParser(-2));
    820         return this;
    821     }
    822 
    823     /**
    824      * Appends an instant using ISO-8601 to the formatter with control over
    825      * the number of fractional digits.
    826      * <p>
    827      * Instants have a fixed output format, although this method provides some
    828      * control over the fractional digits. They are converted to a date-time
    829      * with a zone-offset of UTC and printed using the standard ISO-8601 format.
    830      * The localized decimal style is not used.
    831      * <p>
    832      * The {@code fractionalDigits} parameter allows the output of the fractional
    833      * second to be controlled. Specifying zero will cause no fractional digits
    834      * to be output. From 1 to 9 will output an increasing number of digits, using
    835      * zero right-padding if necessary. The special value -1 is used to output as
    836      * many digits as necessary to avoid any trailing zeroes.
    837      * <p>
    838      * When parsing in strict mode, the number of parsed digits must match the
    839      * fractional digits. When parsing in lenient mode, any number of fractional
    840      * digits from zero to nine are accepted.
    841      * <p>
    842      * The instant is obtained using {@link ChronoField#INSTANT_SECONDS INSTANT_SECONDS}
    843      * and optionally (@code NANO_OF_SECOND). The value of {@code INSTANT_SECONDS}
    844      * may be outside the maximum range of {@code LocalDateTime}.
    845      * <p>
    846      * The {@linkplain ResolverStyle resolver style} has no effect on instant parsing.
    847      * The end-of-day time of '24:00' is handled as midnight at the start of the following day.
    848      * The leap-second time of '23:59:60' is handled to some degree, see
    849      * {@link DateTimeFormatter#parsedLeapSecond()} for full details.
    850      * <p>
    851      * An alternative to this method is to format/parse the instant as a single
    852      * epoch-seconds value. That is achieved using {@code appendValue(INSTANT_SECONDS)}.
    853      *
    854      * @param fractionalDigits  the number of fractional second digits to format with,
    855      *  from 0 to 9, or -1 to use as many digits as necessary
    856      * @return this, for chaining, not null
    857      */
    858     public DateTimeFormatterBuilder appendInstant(int fractionalDigits) {
    859         if (fractionalDigits < -1 || fractionalDigits > 9) {
    860             throw new IllegalArgumentException("The fractional digits must be from -1 to 9 inclusive but was " + fractionalDigits);
    861         }
    862         appendInternal(new InstantPrinterParser(fractionalDigits));
    863         return this;
    864     }
    865 
    866     //-----------------------------------------------------------------------
    867     /**
    868      * Appends the zone offset, such as '+01:00', to the formatter.
    869      * <p>
    870      * This appends an instruction to format/parse the offset ID to the builder.
    871      * This is equivalent to calling {@code appendOffset("+HH:MM:ss", "Z")}.
    872      *
    873      * @return this, for chaining, not null
    874      */
    875     public DateTimeFormatterBuilder appendOffsetId() {
    876         appendInternal(OffsetIdPrinterParser.INSTANCE_ID_Z);
    877         return this;
    878     }
    879 
    880     /**
    881      * Appends the zone offset, such as '+01:00', to the formatter.
    882      * <p>
    883      * This appends an instruction to format/parse the offset ID to the builder.
    884      * <p>
    885      * During formatting, the offset is obtained using a mechanism equivalent
    886      * to querying the temporal with {@link TemporalQueries#offset()}.
    887      * It will be printed using the format defined below.
    888      * If the offset cannot be obtained then an exception is thrown unless the
    889      * section of the formatter is optional.
    890      * <p>
    891      * During parsing, the offset is parsed using the format defined below.
    892      * If the offset cannot be parsed then an exception is thrown unless the
    893      * section of the formatter is optional.
    894      * <p>
    895      * The format of the offset is controlled by a pattern which must be one
    896      * of the following:
    897      * <ul>
    898      * <li>{@code +HH} - hour only, ignoring minute and second
    899      * <li>{@code +HHmm} - hour, with minute if non-zero, ignoring second, no colon
    900      * <li>{@code +HH:mm} - hour, with minute if non-zero, ignoring second, with colon
    901      * <li>{@code +HHMM} - hour and minute, ignoring second, no colon
    902      * <li>{@code +HH:MM} - hour and minute, ignoring second, with colon
    903      * <li>{@code +HHMMss} - hour and minute, with second if non-zero, no colon
    904      * <li>{@code +HH:MM:ss} - hour and minute, with second if non-zero, with colon
    905      * <li>{@code +HHMMSS} - hour, minute and second, no colon
    906      * <li>{@code +HH:MM:SS} - hour, minute and second, with colon
    907      * </ul>
    908      * The "no offset" text controls what text is printed when the total amount of
    909      * the offset fields to be output is zero.
    910      * Example values would be 'Z', '+00:00', 'UTC' or 'GMT'.
    911      * Three formats are accepted for parsing UTC - the "no offset" text, and the
    912      * plus and minus versions of zero defined by the pattern.
    913      *
    914      * @param pattern  the pattern to use, not null
    915      * @param noOffsetText  the text to use when the offset is zero, not null
    916      * @return this, for chaining, not null
    917      */
    918     public DateTimeFormatterBuilder appendOffset(String pattern, String noOffsetText) {
    919         appendInternal(new OffsetIdPrinterParser(pattern, noOffsetText));
    920         return this;
    921     }
    922 
    923     /**
    924      * Appends the localized zone offset, such as 'GMT+01:00', to the formatter.
    925      * <p>
    926      * This appends a localized zone offset to the builder, the format of the
    927      * localized offset is controlled by the specified {@link FormatStyle style}
    928      * to this method:
    929      * <ul>
    930      * <li>{@link TextStyle#FULL full} - formats with localized offset text, such
    931      * as 'GMT, 2-digit hour and minute field, optional second field if non-zero,
    932      * and colon.
    933      * <li>{@link TextStyle#SHORT short} - formats with localized offset text,
    934      * such as 'GMT, hour without leading zero, optional 2-digit minute and
    935      * second if non-zero, and colon.
    936      * </ul>
    937      * <p>
    938      * During formatting, the offset is obtained using a mechanism equivalent
    939      * to querying the temporal with {@link TemporalQueries#offset()}.
    940      * If the offset cannot be obtained then an exception is thrown unless the
    941      * section of the formatter is optional.
    942      * <p>
    943      * During parsing, the offset is parsed using the format defined above.
    944      * If the offset cannot be parsed then an exception is thrown unless the
    945      * section of the formatter is optional.
    946      * <p>
    947      * @param style  the format style to use, not null
    948      * @return this, for chaining, not null
    949      * @throws IllegalArgumentException if style is neither {@link TextStyle#FULL
    950      * full} nor {@link TextStyle#SHORT short}
    951      */
    952     public DateTimeFormatterBuilder appendLocalizedOffset(TextStyle style) {
    953         Objects.requireNonNull(style, "style");
    954         if (style != TextStyle.FULL && style != TextStyle.SHORT) {
    955             throw new IllegalArgumentException("Style must be either full or short");
    956         }
    957         appendInternal(new LocalizedOffsetIdPrinterParser(style));
    958         return this;
    959     }
    960 
    961     //-----------------------------------------------------------------------
    962     /**
    963      * Appends the time-zone ID, such as 'Europe/Paris' or '+02:00', to the formatter.
    964      * <p>
    965      * This appends an instruction to format/parse the zone ID to the builder.
    966      * The zone ID is obtained in a strict manner suitable for {@code ZonedDateTime}.
    967      * By contrast, {@code OffsetDateTime} does not have a zone ID suitable
    968      * for use with this method, see {@link #appendZoneOrOffsetId()}.
    969      * <p>
    970      * During formatting, the zone is obtained using a mechanism equivalent
    971      * to querying the temporal with {@link TemporalQueries#zoneId()}.
    972      * It will be printed using the result of {@link ZoneId#getId()}.
    973      * If the zone cannot be obtained then an exception is thrown unless the
    974      * section of the formatter is optional.
    975      * <p>
    976      * During parsing, the text must match a known zone or offset.
    977      * There are two types of zone ID, offset-based, such as '+01:30' and
    978      * region-based, such as 'Europe/London'. These are parsed differently.
    979      * If the parse starts with '+', '-', 'UT', 'UTC' or 'GMT', then the parser
    980      * expects an offset-based zone and will not match region-based zones.
    981      * The offset ID, such as '+02:30', may be at the start of the parse,
    982      * or prefixed by  'UT', 'UTC' or 'GMT'. The offset ID parsing is
    983      * equivalent to using {@link #appendOffset(String, String)} using the
    984      * arguments 'HH:MM:ss' and the no offset string '0'.
    985      * If the parse starts with 'UT', 'UTC' or 'GMT', and the parser cannot
    986      * match a following offset ID, then {@link ZoneOffset#UTC} is selected.
    987      * In all other cases, the list of known region-based zones is used to
    988      * find the longest available match. If no match is found, and the parse
    989      * starts with 'Z', then {@code ZoneOffset.UTC} is selected.
    990      * The parser uses the {@linkplain #parseCaseInsensitive() case sensitive} setting.
    991      * <p>
    992      * For example, the following will parse:
    993      * <pre>
    994      *   "Europe/London"           -- ZoneId.of("Europe/London")
    995      *   "Z"                       -- ZoneOffset.UTC
    996      *   "UT"                      -- ZoneId.of("UT")
    997      *   "UTC"                     -- ZoneId.of("UTC")
    998      *   "GMT"                     -- ZoneId.of("GMT")
    999      *   "+01:30"                  -- ZoneOffset.of("+01:30")
   1000      *   "UT+01:30"                -- ZoneOffset.of("+01:30")
   1001      *   "UTC+01:30"               -- ZoneOffset.of("+01:30")
   1002      *   "GMT+01:30"               -- ZoneOffset.of("+01:30")
   1003      * </pre>
   1004      *
   1005      * @return this, for chaining, not null
   1006      * @see #appendZoneRegionId()
   1007      */
   1008     public DateTimeFormatterBuilder appendZoneId() {
   1009         appendInternal(new ZoneIdPrinterParser(TemporalQueries.zoneId(), "ZoneId()"));
   1010         return this;
   1011     }
   1012 
   1013     /**
   1014      * Appends the time-zone region ID, such as 'Europe/Paris', to the formatter,
   1015      * rejecting the zone ID if it is a {@code ZoneOffset}.
   1016      * <p>
   1017      * This appends an instruction to format/parse the zone ID to the builder
   1018      * only if it is a region-based ID.
   1019      * <p>
   1020      * During formatting, the zone is obtained using a mechanism equivalent
   1021      * to querying the temporal with {@link TemporalQueries#zoneId()}.
   1022      * If the zone is a {@code ZoneOffset} or it cannot be obtained then
   1023      * an exception is thrown unless the section of the formatter is optional.
   1024      * If the zone is not an offset, then the zone will be printed using
   1025      * the zone ID from {@link ZoneId#getId()}.
   1026      * <p>
   1027      * During parsing, the text must match a known zone or offset.
   1028      * There are two types of zone ID, offset-based, such as '+01:30' and
   1029      * region-based, such as 'Europe/London'. These are parsed differently.
   1030      * If the parse starts with '+', '-', 'UT', 'UTC' or 'GMT', then the parser
   1031      * expects an offset-based zone and will not match region-based zones.
   1032      * The offset ID, such as '+02:30', may be at the start of the parse,
   1033      * or prefixed by  'UT', 'UTC' or 'GMT'. The offset ID parsing is
   1034      * equivalent to using {@link #appendOffset(String, String)} using the
   1035      * arguments 'HH:MM:ss' and the no offset string '0'.
   1036      * If the parse starts with 'UT', 'UTC' or 'GMT', and the parser cannot
   1037      * match a following offset ID, then {@link ZoneOffset#UTC} is selected.
   1038      * In all other cases, the list of known region-based zones is used to
   1039      * find the longest available match. If no match is found, and the parse
   1040      * starts with 'Z', then {@code ZoneOffset.UTC} is selected.
   1041      * The parser uses the {@linkplain #parseCaseInsensitive() case sensitive} setting.
   1042      * <p>
   1043      * For example, the following will parse:
   1044      * <pre>
   1045      *   "Europe/London"           -- ZoneId.of("Europe/London")
   1046      *   "Z"                       -- ZoneOffset.UTC
   1047      *   "UT"                      -- ZoneId.of("UT")
   1048      *   "UTC"                     -- ZoneId.of("UTC")
   1049      *   "GMT"                     -- ZoneId.of("GMT")
   1050      *   "+01:30"                  -- ZoneOffset.of("+01:30")
   1051      *   "UT+01:30"                -- ZoneOffset.of("+01:30")
   1052      *   "UTC+01:30"               -- ZoneOffset.of("+01:30")
   1053      *   "GMT+01:30"               -- ZoneOffset.of("+01:30")
   1054      * </pre>
   1055      * <p>
   1056      * Note that this method is identical to {@code appendZoneId()} except
   1057      * in the mechanism used to obtain the zone.
   1058      * Note also that parsing accepts offsets, whereas formatting will never
   1059      * produce one.
   1060      *
   1061      * @return this, for chaining, not null
   1062      * @see #appendZoneId()
   1063      */
   1064     public DateTimeFormatterBuilder appendZoneRegionId() {
   1065         appendInternal(new ZoneIdPrinterParser(QUERY_REGION_ONLY, "ZoneRegionId()"));
   1066         return this;
   1067     }
   1068 
   1069     /**
   1070      * Appends the time-zone ID, such as 'Europe/Paris' or '+02:00', to
   1071      * the formatter, using the best available zone ID.
   1072      * <p>
   1073      * This appends an instruction to format/parse the best available
   1074      * zone or offset ID to the builder.
   1075      * The zone ID is obtained in a lenient manner that first attempts to
   1076      * find a true zone ID, such as that on {@code ZonedDateTime}, and
   1077      * then attempts to find an offset, such as that on {@code OffsetDateTime}.
   1078      * <p>
   1079      * During formatting, the zone is obtained using a mechanism equivalent
   1080      * to querying the temporal with {@link TemporalQueries#zone()}.
   1081      * It will be printed using the result of {@link ZoneId#getId()}.
   1082      * If the zone cannot be obtained then an exception is thrown unless the
   1083      * section of the formatter is optional.
   1084      * <p>
   1085      * During parsing, the text must match a known zone or offset.
   1086      * There are two types of zone ID, offset-based, such as '+01:30' and
   1087      * region-based, such as 'Europe/London'. These are parsed differently.
   1088      * If the parse starts with '+', '-', 'UT', 'UTC' or 'GMT', then the parser
   1089      * expects an offset-based zone and will not match region-based zones.
   1090      * The offset ID, such as '+02:30', may be at the start of the parse,
   1091      * or prefixed by  'UT', 'UTC' or 'GMT'. The offset ID parsing is
   1092      * equivalent to using {@link #appendOffset(String, String)} using the
   1093      * arguments 'HH:MM:ss' and the no offset string '0'.
   1094      * If the parse starts with 'UT', 'UTC' or 'GMT', and the parser cannot
   1095      * match a following offset ID, then {@link ZoneOffset#UTC} is selected.
   1096      * In all other cases, the list of known region-based zones is used to
   1097      * find the longest available match. If no match is found, and the parse
   1098      * starts with 'Z', then {@code ZoneOffset.UTC} is selected.
   1099      * The parser uses the {@linkplain #parseCaseInsensitive() case sensitive} setting.
   1100      * <p>
   1101      * For example, the following will parse:
   1102      * <pre>
   1103      *   "Europe/London"           -- ZoneId.of("Europe/London")
   1104      *   "Z"                       -- ZoneOffset.UTC
   1105      *   "UT"                      -- ZoneId.of("UT")
   1106      *   "UTC"                     -- ZoneId.of("UTC")
   1107      *   "GMT"                     -- ZoneId.of("GMT")
   1108      *   "+01:30"                  -- ZoneOffset.of("+01:30")
   1109      *   "UT+01:30"                -- ZoneOffset.of("UT+01:30")
   1110      *   "UTC+01:30"               -- ZoneOffset.of("UTC+01:30")
   1111      *   "GMT+01:30"               -- ZoneOffset.of("GMT+01:30")
   1112      * </pre>
   1113      * <p>
   1114      * Note that this method is identical to {@code appendZoneId()} except
   1115      * in the mechanism used to obtain the zone.
   1116      *
   1117      * @return this, for chaining, not null
   1118      * @see #appendZoneId()
   1119      */
   1120     public DateTimeFormatterBuilder appendZoneOrOffsetId() {
   1121         appendInternal(new ZoneIdPrinterParser(TemporalQueries.zone(), "ZoneOrOffsetId()"));
   1122         return this;
   1123     }
   1124 
   1125     /**
   1126      * Appends the time-zone name, such as 'British Summer Time', to the formatter.
   1127      * <p>
   1128      * This appends an instruction to format/parse the textual name of the zone to
   1129      * the builder.
   1130      * <p>
   1131      * During formatting, the zone is obtained using a mechanism equivalent
   1132      * to querying the temporal with {@link TemporalQueries#zoneId()}.
   1133      * If the zone is a {@code ZoneOffset} it will be printed using the
   1134      * result of {@link ZoneOffset#getId()}.
   1135      * If the zone is not an offset, the textual name will be looked up
   1136      * for the locale set in the {@link DateTimeFormatter}.
   1137      * If the temporal object being printed represents an instant, then the text
   1138      * will be the summer or winter time text as appropriate.
   1139      * If the lookup for text does not find any suitable result, then the
   1140      * {@link ZoneId#getId() ID} will be printed instead.
   1141      * If the zone cannot be obtained then an exception is thrown unless the
   1142      * section of the formatter is optional.
   1143      * <p>
   1144      * During parsing, either the textual zone name, the zone ID or the offset
   1145      * is accepted. Many textual zone names are not unique, such as CST can be
   1146      * for both "Central Standard Time" and "China Standard Time". In this
   1147      * situation, the zone id will be determined by the region information from
   1148      * formatter's  {@link DateTimeFormatter#getLocale() locale} and the standard
   1149      * zone id for that area, for example, America/New_York for the America Eastern
   1150      * zone. The {@link #appendZoneText(TextStyle, Set)} may be used
   1151      * to specify a set of preferred {@link ZoneId} in this situation.
   1152      *
   1153      * @param textStyle  the text style to use, not null
   1154      * @return this, for chaining, not null
   1155      */
   1156     public DateTimeFormatterBuilder appendZoneText(TextStyle textStyle) {
   1157         appendInternal(new ZoneTextPrinterParser(textStyle, null));
   1158         return this;
   1159     }
   1160 
   1161     /**
   1162      * Appends the time-zone name, such as 'British Summer Time', to the formatter.
   1163      * <p>
   1164      * This appends an instruction to format/parse the textual name of the zone to
   1165      * the builder.
   1166      * <p>
   1167      * During formatting, the zone is obtained using a mechanism equivalent
   1168      * to querying the temporal with {@link TemporalQueries#zoneId()}.
   1169      * If the zone is a {@code ZoneOffset} it will be printed using the
   1170      * result of {@link ZoneOffset#getId()}.
   1171      * If the zone is not an offset, the textual name will be looked up
   1172      * for the locale set in the {@link DateTimeFormatter}.
   1173      * If the temporal object being printed represents an instant, then the text
   1174      * will be the summer or winter time text as appropriate.
   1175      * If the lookup for text does not find any suitable result, then the
   1176      * {@link ZoneId#getId() ID} will be printed instead.
   1177      * If the zone cannot be obtained then an exception is thrown unless the
   1178      * section of the formatter is optional.
   1179      * <p>
   1180      * During parsing, either the textual zone name, the zone ID or the offset
   1181      * is accepted. Many textual zone names are not unique, such as CST can be
   1182      * for both "Central Standard Time" and "China Standard Time". In this
   1183      * situation, the zone id will be determined by the region information from
   1184      * formatter's  {@link DateTimeFormatter#getLocale() locale} and the standard
   1185      * zone id for that area, for example, America/New_York for the America Eastern
   1186      * zone. This method also allows a set of preferred {@link ZoneId} to be
   1187      * specified for parsing. The matched preferred zone id will be used if the
   1188      * textural zone name being parsed is not unique.
   1189      * <p>
   1190      * If the zone cannot be parsed then an exception is thrown unless the
   1191      * section of the formatter is optional.
   1192      *
   1193      * @param textStyle  the text style to use, not null
   1194      * @param preferredZones  the set of preferred zone ids, not null
   1195      * @return this, for chaining, not null
   1196      */
   1197     public DateTimeFormatterBuilder appendZoneText(TextStyle textStyle,
   1198                                                    Set<ZoneId> preferredZones) {
   1199         Objects.requireNonNull(preferredZones, "preferredZones");
   1200         appendInternal(new ZoneTextPrinterParser(textStyle, preferredZones));
   1201         return this;
   1202     }
   1203 
   1204     //-----------------------------------------------------------------------
   1205     /**
   1206      * Appends the chronology ID, such as 'ISO' or 'ThaiBuddhist', to the formatter.
   1207      * <p>
   1208      * This appends an instruction to format/parse the chronology ID to the builder.
   1209      * <p>
   1210      * During formatting, the chronology is obtained using a mechanism equivalent
   1211      * to querying the temporal with {@link TemporalQueries#chronology()}.
   1212      * It will be printed using the result of {@link Chronology#getId()}.
   1213      * If the chronology cannot be obtained then an exception is thrown unless the
   1214      * section of the formatter is optional.
   1215      * <p>
   1216      * During parsing, the chronology is parsed and must match one of the chronologies
   1217      * in {@link Chronology#getAvailableChronologies()}.
   1218      * If the chronology cannot be parsed then an exception is thrown unless the
   1219      * section of the formatter is optional.
   1220      * The parser uses the {@linkplain #parseCaseInsensitive() case sensitive} setting.
   1221      *
   1222      * @return this, for chaining, not null
   1223      */
   1224     public DateTimeFormatterBuilder appendChronologyId() {
   1225         appendInternal(new ChronoPrinterParser(null));
   1226         return this;
   1227     }
   1228 
   1229     /**
   1230      * Appends the chronology name to the formatter.
   1231      * <p>
   1232      * The calendar system name will be output during a format.
   1233      * If the chronology cannot be obtained then an exception will be thrown.
   1234      *
   1235      * @param textStyle  the text style to use, not null
   1236      * @return this, for chaining, not null
   1237      */
   1238     public DateTimeFormatterBuilder appendChronologyText(TextStyle textStyle) {
   1239         Objects.requireNonNull(textStyle, "textStyle");
   1240         appendInternal(new ChronoPrinterParser(textStyle));
   1241         return this;
   1242     }
   1243 
   1244     //-----------------------------------------------------------------------
   1245     /**
   1246      * Appends a localized date-time pattern to the formatter.
   1247      * <p>
   1248      * This appends a localized section to the builder, suitable for outputting
   1249      * a date, time or date-time combination. The format of the localized
   1250      * section is lazily looked up based on four items:
   1251      * <ul>
   1252      * <li>the {@code dateStyle} specified to this method
   1253      * <li>the {@code timeStyle} specified to this method
   1254      * <li>the {@code Locale} of the {@code DateTimeFormatter}
   1255      * <li>the {@code Chronology}, selecting the best available
   1256      * </ul>
   1257      * During formatting, the chronology is obtained from the temporal object
   1258      * being formatted, which may have been overridden by
   1259      * {@link DateTimeFormatter#withChronology(Chronology)}.
   1260      * <p>
   1261      * During parsing, if a chronology has already been parsed, then it is used.
   1262      * Otherwise the default from {@code DateTimeFormatter.withChronology(Chronology)}
   1263      * is used, with {@code IsoChronology} as the fallback.
   1264      * <p>
   1265      * Note that this method provides similar functionality to methods on
   1266      * {@code DateFormat} such as {@link java.text.DateFormat#getDateTimeInstance(int, int)}.
   1267      *
   1268      * @param dateStyle  the date style to use, null means no date required
   1269      * @param timeStyle  the time style to use, null means no time required
   1270      * @return this, for chaining, not null
   1271      * @throws IllegalArgumentException if both the date and time styles are null
   1272      */
   1273     public DateTimeFormatterBuilder appendLocalized(FormatStyle dateStyle, FormatStyle timeStyle) {
   1274         if (dateStyle == null && timeStyle == null) {
   1275             throw new IllegalArgumentException("Either the date or time style must be non-null");
   1276         }
   1277         appendInternal(new LocalizedPrinterParser(dateStyle, timeStyle));
   1278         return this;
   1279     }
   1280 
   1281     //-----------------------------------------------------------------------
   1282     /**
   1283      * Appends a character literal to the formatter.
   1284      * <p>
   1285      * This character will be output during a format.
   1286      *
   1287      * @param literal  the literal to append, not null
   1288      * @return this, for chaining, not null
   1289      */
   1290     public DateTimeFormatterBuilder appendLiteral(char literal) {
   1291         appendInternal(new CharLiteralPrinterParser(literal));
   1292         return this;
   1293     }
   1294 
   1295     /**
   1296      * Appends a string literal to the formatter.
   1297      * <p>
   1298      * This string will be output during a format.
   1299      * <p>
   1300      * If the literal is empty, nothing is added to the formatter.
   1301      *
   1302      * @param literal  the literal to append, not null
   1303      * @return this, for chaining, not null
   1304      */
   1305     public DateTimeFormatterBuilder appendLiteral(String literal) {
   1306         Objects.requireNonNull(literal, "literal");
   1307         if (literal.length() > 0) {
   1308             if (literal.length() == 1) {
   1309                 appendInternal(new CharLiteralPrinterParser(literal.charAt(0)));
   1310             } else {
   1311                 appendInternal(new StringLiteralPrinterParser(literal));
   1312             }
   1313         }
   1314         return this;
   1315     }
   1316 
   1317     //-----------------------------------------------------------------------
   1318     /**
   1319      * Appends all the elements of a formatter to the builder.
   1320      * <p>
   1321      * This method has the same effect as appending each of the constituent
   1322      * parts of the formatter directly to this builder.
   1323      *
   1324      * @param formatter  the formatter to add, not null
   1325      * @return this, for chaining, not null
   1326      */
   1327     public DateTimeFormatterBuilder append(DateTimeFormatter formatter) {
   1328         Objects.requireNonNull(formatter, "formatter");
   1329         appendInternal(formatter.toPrinterParser(false));
   1330         return this;
   1331     }
   1332 
   1333     /**
   1334      * Appends a formatter to the builder which will optionally format/parse.
   1335      * <p>
   1336      * This method has the same effect as appending each of the constituent
   1337      * parts directly to this builder surrounded by an {@link #optionalStart()} and
   1338      * {@link #optionalEnd()}.
   1339      * <p>
   1340      * The formatter will format if data is available for all the fields contained within it.
   1341      * The formatter will parse if the string matches, otherwise no error is returned.
   1342      *
   1343      * @param formatter  the formatter to add, not null
   1344      * @return this, for chaining, not null
   1345      */
   1346     public DateTimeFormatterBuilder appendOptional(DateTimeFormatter formatter) {
   1347         Objects.requireNonNull(formatter, "formatter");
   1348         appendInternal(formatter.toPrinterParser(true));
   1349         return this;
   1350     }
   1351 
   1352     //-----------------------------------------------------------------------
   1353     /**
   1354      * Appends the elements defined by the specified pattern to the builder.
   1355      * <p>
   1356      * All letters 'A' to 'Z' and 'a' to 'z' are reserved as pattern letters.
   1357      * The characters '#', '{' and '}' are reserved for future use.
   1358      * The characters '[' and ']' indicate optional patterns.
   1359      * The following pattern letters are defined:
   1360      * <pre>
   1361      *  Symbol  Meaning                     Presentation      Examples
   1362      *  ------  -------                     ------------      -------
   1363      *   G       era                         text              AD; Anno Domini; A
   1364      *   u       year                        year              2004; 04
   1365      *   y       year-of-era                 year              2004; 04
   1366      *   D       day-of-year                 number            189
   1367      *   M/L     month-of-year               number/text       7; 07; Jul; July; J
   1368      *   d       day-of-month                number            10
   1369      *
   1370      *   Q/q     quarter-of-year             number/text       3; 03; Q3; 3rd quarter
   1371      *   Y       week-based-year             year              1996; 96
   1372      *   w       week-of-week-based-year     number            27
   1373      *   W       week-of-month               number            4
   1374      *   E       day-of-week                 text              Tue; Tuesday; T
   1375      *   e/c     localized day-of-week       number/text       2; 02; Tue; Tuesday; T
   1376      *   F       week-of-month               number            3
   1377      *
   1378      *   a       am-pm-of-day                text              PM
   1379      *   h       clock-hour-of-am-pm (1-12)  number            12
   1380      *   K       hour-of-am-pm (0-11)        number            0
   1381      *   k       clock-hour-of-am-pm (1-24)  number            0
   1382      *
   1383      *   H       hour-of-day (0-23)          number            0
   1384      *   m       minute-of-hour              number            30
   1385      *   s       second-of-minute            number            55
   1386      *   S       fraction-of-second          fraction          978
   1387      *   A       milli-of-day                number            1234
   1388      *   n       nano-of-second              number            987654321
   1389      *   N       nano-of-day                 number            1234000000
   1390      *
   1391      *   V       time-zone ID                zone-id           America/Los_Angeles; Z; -08:30
   1392      *   z       time-zone name              zone-name         Pacific Standard Time; PST
   1393      *   O       localized zone-offset       offset-O          GMT+8; GMT+08:00; UTC-08:00;
   1394      *   X       zone-offset 'Z' for zero    offset-X          Z; -08; -0830; -08:30; -083015; -08:30:15;
   1395      *   x       zone-offset                 offset-x          +0000; -08; -0830; -08:30; -083015; -08:30:15;
   1396      *   Z       zone-offset                 offset-Z          +0000; -0800; -08:00;
   1397      *
   1398      *   p       pad next                    pad modifier      1
   1399      *
   1400      *   '       escape for text             delimiter
   1401      *   ''      single quote                literal           '
   1402      *   [       optional section start
   1403      *   ]       optional section end
   1404      *   #       reserved for future use
   1405      *   {       reserved for future use
   1406      *   }       reserved for future use
   1407      * </pre>
   1408      * <p>
   1409      * The count of pattern letters determine the format.
   1410      * See <a href="DateTimeFormatter.html#patterns">DateTimeFormatter</a> for a user-focused description of the patterns.
   1411      * The following tables define how the pattern letters map to the builder.
   1412      * <p>
   1413      * <b>Date fields</b>: Pattern letters to output a date.
   1414      * <pre>
   1415      *  Pattern  Count  Equivalent builder methods
   1416      *  -------  -----  --------------------------
   1417      *    G       1      appendText(ChronoField.ERA, TextStyle.SHORT)
   1418      *    GG      2      appendText(ChronoField.ERA, TextStyle.SHORT)
   1419      *    GGG     3      appendText(ChronoField.ERA, TextStyle.SHORT)
   1420      *    GGGG    4      appendText(ChronoField.ERA, TextStyle.FULL)
   1421      *    GGGGG   5      appendText(ChronoField.ERA, TextStyle.NARROW)
   1422      *
   1423      *    u       1      appendValue(ChronoField.YEAR, 1, 19, SignStyle.NORMAL);
   1424      *    uu      2      appendValueReduced(ChronoField.YEAR, 2, 2000);
   1425      *    uuu     3      appendValue(ChronoField.YEAR, 3, 19, SignStyle.NORMAL);
   1426      *    u..u    4..n   appendValue(ChronoField.YEAR, n, 19, SignStyle.EXCEEDS_PAD);
   1427      *    y       1      appendValue(ChronoField.YEAR_OF_ERA, 1, 19, SignStyle.NORMAL);
   1428      *    yy      2      appendValueReduced(ChronoField.YEAR_OF_ERA, 2, 2000);
   1429      *    yyy     3      appendValue(ChronoField.YEAR_OF_ERA, 3, 19, SignStyle.NORMAL);
   1430      *    y..y    4..n   appendValue(ChronoField.YEAR_OF_ERA, n, 19, SignStyle.EXCEEDS_PAD);
   1431      *    Y       1      append special localized WeekFields element for numeric week-based-year
   1432      *    YY      2      append special localized WeekFields element for reduced numeric week-based-year 2 digits;
   1433      *    YYY     3      append special localized WeekFields element for numeric week-based-year (3, 19, SignStyle.NORMAL);
   1434      *    Y..Y    4..n   append special localized WeekFields element for numeric week-based-year (n, 19, SignStyle.EXCEEDS_PAD);
   1435      *
   1436      *    Q       1      appendValue(IsoFields.QUARTER_OF_YEAR);
   1437      *    QQ      2      appendValue(IsoFields.QUARTER_OF_YEAR, 2);
   1438      *    QQQ     3      appendText(IsoFields.QUARTER_OF_YEAR, TextStyle.SHORT)
   1439      *    QQQQ    4      appendText(IsoFields.QUARTER_OF_YEAR, TextStyle.FULL)
   1440      *    QQQQQ   5      appendText(IsoFields.QUARTER_OF_YEAR, TextStyle.NARROW)
   1441      *    q       1      appendValue(IsoFields.QUARTER_OF_YEAR);
   1442      *    qq      2      appendValue(IsoFields.QUARTER_OF_YEAR, 2);
   1443      *    qqq     3      appendText(IsoFields.QUARTER_OF_YEAR, TextStyle.SHORT_STANDALONE)
   1444      *    qqqq    4      appendText(IsoFields.QUARTER_OF_YEAR, TextStyle.FULL_STANDALONE)
   1445      *    qqqqq   5      appendText(IsoFields.QUARTER_OF_YEAR, TextStyle.NARROW_STANDALONE)
   1446      *
   1447      *    M       1      appendValue(ChronoField.MONTH_OF_YEAR);
   1448      *    MM      2      appendValue(ChronoField.MONTH_OF_YEAR, 2);
   1449      *    MMM     3      appendText(ChronoField.MONTH_OF_YEAR, TextStyle.SHORT)
   1450      *    MMMM    4      appendText(ChronoField.MONTH_OF_YEAR, TextStyle.FULL)
   1451      *    MMMMM   5      appendText(ChronoField.MONTH_OF_YEAR, TextStyle.NARROW)
   1452      *    L       1      appendValue(ChronoField.MONTH_OF_YEAR);
   1453      *    LL      2      appendValue(ChronoField.MONTH_OF_YEAR, 2);
   1454      *    LLL     3      appendText(ChronoField.MONTH_OF_YEAR, TextStyle.SHORT_STANDALONE)
   1455      *    LLLL    4      appendText(ChronoField.MONTH_OF_YEAR, TextStyle.FULL_STANDALONE)
   1456      *    LLLLL   5      appendText(ChronoField.MONTH_OF_YEAR, TextStyle.NARROW_STANDALONE)
   1457      *
   1458      *    w       1      append special localized WeekFields element for numeric week-of-year
   1459      *    ww      2      append special localized WeekFields element for numeric week-of-year, zero-padded
   1460      *    W       1      append special localized WeekFields element for numeric week-of-month
   1461      *    d       1      appendValue(ChronoField.DAY_OF_MONTH)
   1462      *    dd      2      appendValue(ChronoField.DAY_OF_MONTH, 2)
   1463      *    D       1      appendValue(ChronoField.DAY_OF_YEAR)
   1464      *    DD      2      appendValue(ChronoField.DAY_OF_YEAR, 2)
   1465      *    DDD     3      appendValue(ChronoField.DAY_OF_YEAR, 3)
   1466      *    F       1      appendValue(ChronoField.ALIGNED_DAY_OF_WEEK_IN_MONTH)
   1467      *    E       1      appendText(ChronoField.DAY_OF_WEEK, TextStyle.SHORT)
   1468      *    EE      2      appendText(ChronoField.DAY_OF_WEEK, TextStyle.SHORT)
   1469      *    EEE     3      appendText(ChronoField.DAY_OF_WEEK, TextStyle.SHORT)
   1470      *    EEEE    4      appendText(ChronoField.DAY_OF_WEEK, TextStyle.FULL)
   1471      *    EEEEE   5      appendText(ChronoField.DAY_OF_WEEK, TextStyle.NARROW)
   1472      *    e       1      append special localized WeekFields element for numeric day-of-week
   1473      *    ee      2      append special localized WeekFields element for numeric day-of-week, zero-padded
   1474      *    eee     3      appendText(ChronoField.DAY_OF_WEEK, TextStyle.SHORT)
   1475      *    eeee    4      appendText(ChronoField.DAY_OF_WEEK, TextStyle.FULL)
   1476      *    eeeee   5      appendText(ChronoField.DAY_OF_WEEK, TextStyle.NARROW)
   1477      *    c       1      append special localized WeekFields element for numeric day-of-week
   1478      *    ccc     3      appendText(ChronoField.DAY_OF_WEEK, TextStyle.SHORT_STANDALONE)
   1479      *    cccc    4      appendText(ChronoField.DAY_OF_WEEK, TextStyle.FULL_STANDALONE)
   1480      *    ccccc   5      appendText(ChronoField.DAY_OF_WEEK, TextStyle.NARROW_STANDALONE)
   1481      * </pre>
   1482      * <p>
   1483      * <b>Time fields</b>: Pattern letters to output a time.
   1484      * <pre>
   1485      *  Pattern  Count  Equivalent builder methods
   1486      *  -------  -----  --------------------------
   1487      *    a       1      appendText(ChronoField.AMPM_OF_DAY, TextStyle.SHORT)
   1488      *    h       1      appendValue(ChronoField.CLOCK_HOUR_OF_AMPM)
   1489      *    hh      2      appendValue(ChronoField.CLOCK_HOUR_OF_AMPM, 2)
   1490      *    H       1      appendValue(ChronoField.HOUR_OF_DAY)
   1491      *    HH      2      appendValue(ChronoField.HOUR_OF_DAY, 2)
   1492      *    k       1      appendValue(ChronoField.CLOCK_HOUR_OF_DAY)
   1493      *    kk      2      appendValue(ChronoField.CLOCK_HOUR_OF_DAY, 2)
   1494      *    K       1      appendValue(ChronoField.HOUR_OF_AMPM)
   1495      *    KK      2      appendValue(ChronoField.HOUR_OF_AMPM, 2)
   1496      *    m       1      appendValue(ChronoField.MINUTE_OF_HOUR)
   1497      *    mm      2      appendValue(ChronoField.MINUTE_OF_HOUR, 2)
   1498      *    s       1      appendValue(ChronoField.SECOND_OF_MINUTE)
   1499      *    ss      2      appendValue(ChronoField.SECOND_OF_MINUTE, 2)
   1500      *
   1501      *    S..S    1..n   appendFraction(ChronoField.NANO_OF_SECOND, n, n, false)
   1502      *    A       1      appendValue(ChronoField.MILLI_OF_DAY)
   1503      *    A..A    2..n   appendValue(ChronoField.MILLI_OF_DAY, n)
   1504      *    n       1      appendValue(ChronoField.NANO_OF_SECOND)
   1505      *    n..n    2..n   appendValue(ChronoField.NANO_OF_SECOND, n)
   1506      *    N       1      appendValue(ChronoField.NANO_OF_DAY)
   1507      *    N..N    2..n   appendValue(ChronoField.NANO_OF_DAY, n)
   1508      * </pre>
   1509      * <p>
   1510      * <b>Zone ID</b>: Pattern letters to output {@code ZoneId}.
   1511      * <pre>
   1512      *  Pattern  Count  Equivalent builder methods
   1513      *  -------  -----  --------------------------
   1514      *    VV      2      appendZoneId()
   1515      *    z       1      appendZoneText(TextStyle.SHORT)
   1516      *    zz      2      appendZoneText(TextStyle.SHORT)
   1517      *    zzz     3      appendZoneText(TextStyle.SHORT)
   1518      *    zzzz    4      appendZoneText(TextStyle.FULL)
   1519      * </pre>
   1520      * <p>
   1521      * <b>Zone offset</b>: Pattern letters to output {@code ZoneOffset}.
   1522      * <pre>
   1523      *  Pattern  Count  Equivalent builder methods
   1524      *  -------  -----  --------------------------
   1525      *    O       1      appendLocalizedOffsetPrefixed(TextStyle.SHORT);
   1526      *    OOOO    4      appendLocalizedOffsetPrefixed(TextStyle.FULL);
   1527      *    X       1      appendOffset("+HHmm","Z")
   1528      *    XX      2      appendOffset("+HHMM","Z")
   1529      *    XXX     3      appendOffset("+HH:MM","Z")
   1530      *    XXXX    4      appendOffset("+HHMMss","Z")
   1531      *    XXXXX   5      appendOffset("+HH:MM:ss","Z")
   1532      *    x       1      appendOffset("+HHmm","+00")
   1533      *    xx      2      appendOffset("+HHMM","+0000")
   1534      *    xxx     3      appendOffset("+HH:MM","+00:00")
   1535      *    xxxx    4      appendOffset("+HHMMss","+0000")
   1536      *    xxxxx   5      appendOffset("+HH:MM:ss","+00:00")
   1537      *    Z       1      appendOffset("+HHMM","+0000")
   1538      *    ZZ      2      appendOffset("+HHMM","+0000")
   1539      *    ZZZ     3      appendOffset("+HHMM","+0000")
   1540      *    ZZZZ    4      appendLocalizedOffset(TextStyle.FULL);
   1541      *    ZZZZZ   5      appendOffset("+HH:MM:ss","Z")
   1542      * </pre>
   1543      * <p>
   1544      * <b>Modifiers</b>: Pattern letters that modify the rest of the pattern:
   1545      * <pre>
   1546      *  Pattern  Count  Equivalent builder methods
   1547      *  -------  -----  --------------------------
   1548      *    [       1      optionalStart()
   1549      *    ]       1      optionalEnd()
   1550      *    p..p    1..n   padNext(n)
   1551      * </pre>
   1552      * <p>
   1553      * Any sequence of letters not specified above, unrecognized letter or
   1554      * reserved character will throw an exception.
   1555      * Future versions may add to the set of patterns.
   1556      * It is recommended to use single quotes around all characters that you want
   1557      * to output directly to ensure that future changes do not break your application.
   1558      * <p>
   1559      * Note that the pattern string is similar, but not identical, to
   1560      * {@link java.text.SimpleDateFormat SimpleDateFormat}.
   1561      * The pattern string is also similar, but not identical, to that defined by the
   1562      * Unicode Common Locale Data Repository (CLDR/LDML).
   1563      * Pattern letters 'X' and 'u' are aligned with Unicode CLDR/LDML.
   1564      * By contrast, {@code SimpleDateFormat} uses 'u' for the numeric day of week.
   1565      * Pattern letters 'y' and 'Y' parse years of two digits and more than 4 digits differently.
   1566      * Pattern letters 'n', 'A', 'N', and 'p' are added.
   1567      * Number types will reject large numbers.
   1568      *
   1569      * @param pattern  the pattern to add, not null
   1570      * @return this, for chaining, not null
   1571      * @throws IllegalArgumentException if the pattern is invalid
   1572      */
   1573     public DateTimeFormatterBuilder appendPattern(String pattern) {
   1574         Objects.requireNonNull(pattern, "pattern");
   1575         parsePattern(pattern);
   1576         return this;
   1577     }
   1578 
   1579     private void parsePattern(String pattern) {
   1580         for (int pos = 0; pos < pattern.length(); pos++) {
   1581             char cur = pattern.charAt(pos);
   1582             if ((cur >= 'A' && cur <= 'Z') || (cur >= 'a' && cur <= 'z')) {
   1583                 int start = pos++;
   1584                 for ( ; pos < pattern.length() && pattern.charAt(pos) == cur; pos++);  // short loop
   1585                 int count = pos - start;
   1586                 // padding
   1587                 if (cur == 'p') {
   1588                     int pad = 0;
   1589                     if (pos < pattern.length()) {
   1590                         cur = pattern.charAt(pos);
   1591                         if ((cur >= 'A' && cur <= 'Z') || (cur >= 'a' && cur <= 'z')) {
   1592                             pad = count;
   1593                             start = pos++;
   1594                             for ( ; pos < pattern.length() && pattern.charAt(pos) == cur; pos++);  // short loop
   1595                             count = pos - start;
   1596                         }
   1597                     }
   1598                     if (pad == 0) {
   1599                         throw new IllegalArgumentException(
   1600                                 "Pad letter 'p' must be followed by valid pad pattern: " + pattern);
   1601                     }
   1602                     padNext(pad); // pad and continue parsing
   1603                 }
   1604                 // main rules
   1605                 TemporalField field = FIELD_MAP.get(cur);
   1606                 if (field != null) {
   1607                     parseField(cur, count, field);
   1608                 } else if (cur == 'z') {
   1609                     if (count > 4) {
   1610                         throw new IllegalArgumentException("Too many pattern letters: " + cur);
   1611                     } else if (count == 4) {
   1612                         appendZoneText(TextStyle.FULL);
   1613                     } else {
   1614                         appendZoneText(TextStyle.SHORT);
   1615                     }
   1616                 } else if (cur == 'V') {
   1617                     if (count != 2) {
   1618                         throw new IllegalArgumentException("Pattern letter count must be 2: " + cur);
   1619                     }
   1620                     appendZoneId();
   1621                 } else if (cur == 'Z') {
   1622                     if (count < 4) {
   1623                         appendOffset("+HHMM", "+0000");
   1624                     } else if (count == 4) {
   1625                         appendLocalizedOffset(TextStyle.FULL);
   1626                     } else if (count == 5) {
   1627                         appendOffset("+HH:MM:ss","Z");
   1628                     } else {
   1629                         throw new IllegalArgumentException("Too many pattern letters: " + cur);
   1630                     }
   1631                 } else if (cur == 'O') {
   1632                     if (count == 1) {
   1633                         appendLocalizedOffset(TextStyle.SHORT);
   1634                     } else if (count == 4) {
   1635                         appendLocalizedOffset(TextStyle.FULL);
   1636                     } else {
   1637                         throw new IllegalArgumentException("Pattern letter count must be 1 or 4: " + cur);
   1638                     }
   1639                 } else if (cur == 'X') {
   1640                     if (count > 5) {
   1641                         throw new IllegalArgumentException("Too many pattern letters: " + cur);
   1642                     }
   1643                     appendOffset(OffsetIdPrinterParser.PATTERNS[count + (count == 1 ? 0 : 1)], "Z");
   1644                 } else if (cur == 'x') {
   1645                     if (count > 5) {
   1646                         throw new IllegalArgumentException("Too many pattern letters: " + cur);
   1647                     }
   1648                     String zero = (count == 1 ? "+00" : (count % 2 == 0 ? "+0000" : "+00:00"));
   1649                     appendOffset(OffsetIdPrinterParser.PATTERNS[count + (count == 1 ? 0 : 1)], zero);
   1650                 } else if (cur == 'W') {
   1651                     // Fields defined by Locale
   1652                     if (count > 1) {
   1653                         throw new IllegalArgumentException("Too many pattern letters: " + cur);
   1654                     }
   1655                     appendInternal(new WeekBasedFieldPrinterParser(cur, count));
   1656                 } else if (cur == 'w') {
   1657                     // Fields defined by Locale
   1658                     if (count > 2) {
   1659                         throw new IllegalArgumentException("Too many pattern letters: " + cur);
   1660                     }
   1661                     appendInternal(new WeekBasedFieldPrinterParser(cur, count));
   1662                 } else if (cur == 'Y') {
   1663                     // Fields defined by Locale
   1664                     appendInternal(new WeekBasedFieldPrinterParser(cur, count));
   1665                 } else {
   1666                     throw new IllegalArgumentException("Unknown pattern letter: " + cur);
   1667                 }
   1668                 pos--;
   1669 
   1670             } else if (cur == '\'') {
   1671                 // parse literals
   1672                 int start = pos++;
   1673                 for ( ; pos < pattern.length(); pos++) {
   1674                     if (pattern.charAt(pos) == '\'') {
   1675                         if (pos + 1 < pattern.length() && pattern.charAt(pos + 1) == '\'') {
   1676                             pos++;
   1677                         } else {
   1678                             break;  // end of literal
   1679                         }
   1680                     }
   1681                 }
   1682                 if (pos >= pattern.length()) {
   1683                     throw new IllegalArgumentException("Pattern ends with an incomplete string literal: " + pattern);
   1684                 }
   1685                 String str = pattern.substring(start + 1, pos);
   1686                 if (str.length() == 0) {
   1687                     appendLiteral('\'');
   1688                 } else {
   1689                     appendLiteral(str.replace("''", "'"));
   1690                 }
   1691 
   1692             } else if (cur == '[') {
   1693                 optionalStart();
   1694 
   1695             } else if (cur == ']') {
   1696                 if (active.parent == null) {
   1697                     throw new IllegalArgumentException("Pattern invalid as it contains ] without previous [");
   1698                 }
   1699                 optionalEnd();
   1700 
   1701             } else if (cur == '{' || cur == '}' || cur == '#') {
   1702                 throw new IllegalArgumentException("Pattern includes reserved character: '" + cur + "'");
   1703             } else {
   1704                 appendLiteral(cur);
   1705             }
   1706         }
   1707     }
   1708 
   1709     @SuppressWarnings("fallthrough")
   1710     private void parseField(char cur, int count, TemporalField field) {
   1711         boolean standalone = false;
   1712         switch (cur) {
   1713             case 'u':
   1714             case 'y':
   1715                 if (count == 2) {
   1716                     appendValueReduced(field, 2, 2, ReducedPrinterParser.BASE_DATE);
   1717                 } else if (count < 4) {
   1718                     appendValue(field, count, 19, SignStyle.NORMAL);
   1719                 } else {
   1720                     appendValue(field, count, 19, SignStyle.EXCEEDS_PAD);
   1721                 }
   1722                 break;
   1723             case 'c':
   1724                 if (count == 2) {
   1725                     throw new IllegalArgumentException("Invalid pattern \"cc\"");
   1726                 }
   1727                 /*fallthrough*/
   1728             case 'L':
   1729             case 'q':
   1730                 standalone = true;
   1731                 /*fallthrough*/
   1732             case 'M':
   1733             case 'Q':
   1734             case 'E':
   1735             case 'e':
   1736                 switch (count) {
   1737                     case 1:
   1738                     case 2:
   1739                         if (cur == 'c' || cur == 'e') {
   1740                             appendInternal(new WeekBasedFieldPrinterParser(cur, count));
   1741                         } else if (cur == 'E') {
   1742                             appendText(field, TextStyle.SHORT);
   1743                         } else {
   1744                             if (count == 1) {
   1745                                 appendValue(field);
   1746                             } else {
   1747                                 appendValue(field, 2);
   1748                             }
   1749                         }
   1750                         break;
   1751                     case 3:
   1752                         appendText(field, standalone ? TextStyle.SHORT_STANDALONE : TextStyle.SHORT);
   1753                         break;
   1754                     case 4:
   1755                         appendText(field, standalone ? TextStyle.FULL_STANDALONE : TextStyle.FULL);
   1756                         break;
   1757                     case 5:
   1758                         appendText(field, standalone ? TextStyle.NARROW_STANDALONE : TextStyle.NARROW);
   1759                         break;
   1760                     default:
   1761                         throw new IllegalArgumentException("Too many pattern letters: " + cur);
   1762                 }
   1763                 break;
   1764             case 'a':
   1765                 if (count == 1) {
   1766                     appendText(field, TextStyle.SHORT);
   1767                 } else {
   1768                     throw new IllegalArgumentException("Too many pattern letters: " + cur);
   1769                 }
   1770                 break;
   1771             case 'G':
   1772                 switch (count) {
   1773                     case 1:
   1774                     case 2:
   1775                     case 3:
   1776                         appendText(field, TextStyle.SHORT);
   1777                         break;
   1778                     case 4:
   1779                         appendText(field, TextStyle.FULL);
   1780                         break;
   1781                     case 5:
   1782                         appendText(field, TextStyle.NARROW);
   1783                         break;
   1784                     default:
   1785                         throw new IllegalArgumentException("Too many pattern letters: " + cur);
   1786                 }
   1787                 break;
   1788             case 'S':
   1789                 appendFraction(NANO_OF_SECOND, count, count, false);
   1790                 break;
   1791             case 'F':
   1792                 if (count == 1) {
   1793                     appendValue(field);
   1794                 } else {
   1795                     throw new IllegalArgumentException("Too many pattern letters: " + cur);
   1796                 }
   1797                 break;
   1798             case 'd':
   1799             case 'h':
   1800             case 'H':
   1801             case 'k':
   1802             case 'K':
   1803             case 'm':
   1804             case 's':
   1805                 if (count == 1) {
   1806                     appendValue(field);
   1807                 } else if (count == 2) {
   1808                     appendValue(field, count);
   1809                 } else {
   1810                     throw new IllegalArgumentException("Too many pattern letters: " + cur);
   1811                 }
   1812                 break;
   1813             case 'D':
   1814                 if (count == 1) {
   1815                     appendValue(field);
   1816                 } else if (count <= 3) {
   1817                     appendValue(field, count);
   1818                 } else {
   1819                     throw new IllegalArgumentException("Too many pattern letters: " + cur);
   1820                 }
   1821                 break;
   1822             default:
   1823                 if (count == 1) {
   1824                     appendValue(field);
   1825                 } else {
   1826                     appendValue(field, count);
   1827                 }
   1828                 break;
   1829         }
   1830     }
   1831 
   1832     /** Map of letters to fields. */
   1833     private static final Map<Character, TemporalField> FIELD_MAP = new HashMap<>();
   1834     static {
   1835         // SDF = SimpleDateFormat
   1836         FIELD_MAP.put('G', ChronoField.ERA);                       // SDF, LDML (different to both for 1/2 chars)
   1837         FIELD_MAP.put('y', ChronoField.YEAR_OF_ERA);               // SDF, LDML
   1838         FIELD_MAP.put('u', ChronoField.YEAR);                      // LDML (different in SDF)
   1839         FIELD_MAP.put('Q', IsoFields.QUARTER_OF_YEAR);             // LDML (removed quarter from 310)
   1840         FIELD_MAP.put('q', IsoFields.QUARTER_OF_YEAR);             // LDML (stand-alone)
   1841         FIELD_MAP.put('M', ChronoField.MONTH_OF_YEAR);             // SDF, LDML
   1842         FIELD_MAP.put('L', ChronoField.MONTH_OF_YEAR);             // SDF, LDML (stand-alone)
   1843         FIELD_MAP.put('D', ChronoField.DAY_OF_YEAR);               // SDF, LDML
   1844         FIELD_MAP.put('d', ChronoField.DAY_OF_MONTH);              // SDF, LDML
   1845         FIELD_MAP.put('F', ChronoField.ALIGNED_DAY_OF_WEEK_IN_MONTH);  // SDF, LDML
   1846         FIELD_MAP.put('E', ChronoField.DAY_OF_WEEK);               // SDF, LDML (different to both for 1/2 chars)
   1847         FIELD_MAP.put('c', ChronoField.DAY_OF_WEEK);               // LDML (stand-alone)
   1848         FIELD_MAP.put('e', ChronoField.DAY_OF_WEEK);               // LDML (needs localized week number)
   1849         FIELD_MAP.put('a', ChronoField.AMPM_OF_DAY);               // SDF, LDML
   1850         FIELD_MAP.put('H', ChronoField.HOUR_OF_DAY);               // SDF, LDML
   1851         FIELD_MAP.put('k', ChronoField.CLOCK_HOUR_OF_DAY);         // SDF, LDML
   1852         FIELD_MAP.put('K', ChronoField.HOUR_OF_AMPM);              // SDF, LDML
   1853         FIELD_MAP.put('h', ChronoField.CLOCK_HOUR_OF_AMPM);        // SDF, LDML
   1854         FIELD_MAP.put('m', ChronoField.MINUTE_OF_HOUR);            // SDF, LDML
   1855         FIELD_MAP.put('s', ChronoField.SECOND_OF_MINUTE);          // SDF, LDML
   1856         FIELD_MAP.put('S', ChronoField.NANO_OF_SECOND);            // LDML (SDF uses milli-of-second number)
   1857         FIELD_MAP.put('A', ChronoField.MILLI_OF_DAY);              // LDML
   1858         FIELD_MAP.put('n', ChronoField.NANO_OF_SECOND);            // 310 (proposed for LDML)
   1859         FIELD_MAP.put('N', ChronoField.NANO_OF_DAY);               // 310 (proposed for LDML)
   1860         // 310 - z - time-zone names, matches LDML and SimpleDateFormat 1 to 4
   1861         // 310 - Z - matches SimpleDateFormat and LDML
   1862         // 310 - V - time-zone id, matches LDML
   1863         // 310 - p - prefix for padding
   1864         // 310 - X - matches LDML, almost matches SDF for 1, exact match 2&3, extended 4&5
   1865         // 310 - x - matches LDML
   1866         // 310 - w, W, and Y are localized forms matching LDML
   1867         // LDML - U - cycle year name, not supported by 310 yet
   1868         // LDML - l - deprecated
   1869         // LDML - j - not relevant
   1870         // LDML - g - modified-julian-day
   1871         // LDML - v,V - extended time-zone names
   1872     }
   1873 
   1874     //-----------------------------------------------------------------------
   1875     /**
   1876      * Causes the next added printer/parser to pad to a fixed width using a space.
   1877      * <p>
   1878      * This padding will pad to a fixed width using spaces.
   1879      * <p>
   1880      * During formatting, the decorated element will be output and then padded
   1881      * to the specified width. An exception will be thrown during formatting if
   1882      * the pad width is exceeded.
   1883      * <p>
   1884      * During parsing, the padding and decorated element are parsed.
   1885      * If parsing is lenient, then the pad width is treated as a maximum.
   1886      * The padding is parsed greedily. Thus, if the decorated element starts with
   1887      * the pad character, it will not be parsed.
   1888      *
   1889      * @param padWidth  the pad width, 1 or greater
   1890      * @return this, for chaining, not null
   1891      * @throws IllegalArgumentException if pad width is too small
   1892      */
   1893     public DateTimeFormatterBuilder padNext(int padWidth) {
   1894         return padNext(padWidth, ' ');
   1895     }
   1896 
   1897     /**
   1898      * Causes the next added printer/parser to pad to a fixed width.
   1899      * <p>
   1900      * This padding is intended for padding other than zero-padding.
   1901      * Zero-padding should be achieved using the appendValue methods.
   1902      * <p>
   1903      * During formatting, the decorated element will be output and then padded
   1904      * to the specified width. An exception will be thrown during formatting if
   1905      * the pad width is exceeded.
   1906      * <p>
   1907      * During parsing, the padding and decorated element are parsed.
   1908      * If parsing is lenient, then the pad width is treated as a maximum.
   1909      * If parsing is case insensitive, then the pad character is matched ignoring case.
   1910      * The padding is parsed greedily. Thus, if the decorated element starts with
   1911      * the pad character, it will not be parsed.
   1912      *
   1913      * @param padWidth  the pad width, 1 or greater
   1914      * @param padChar  the pad character
   1915      * @return this, for chaining, not null
   1916      * @throws IllegalArgumentException if pad width is too small
   1917      */
   1918     public DateTimeFormatterBuilder padNext(int padWidth, char padChar) {
   1919         if (padWidth < 1) {
   1920             throw new IllegalArgumentException("The pad width must be at least one but was " + padWidth);
   1921         }
   1922         active.padNextWidth = padWidth;
   1923         active.padNextChar = padChar;
   1924         active.valueParserIndex = -1;
   1925         return this;
   1926     }
   1927 
   1928     //-----------------------------------------------------------------------
   1929     /**
   1930      * Mark the start of an optional section.
   1931      * <p>
   1932      * The output of formatting can include optional sections, which may be nested.
   1933      * An optional section is started by calling this method and ended by calling
   1934      * {@link #optionalEnd()} or by ending the build process.
   1935      * <p>
   1936      * All elements in the optional section are treated as optional.
   1937      * During formatting, the section is only output if data is available in the
   1938      * {@code TemporalAccessor} for all the elements in the section.
   1939      * During parsing, the whole section may be missing from the parsed string.
   1940      * <p>
   1941      * For example, consider a builder setup as
   1942      * {@code builder.appendValue(HOUR_OF_DAY,2).optionalStart().appendValue(MINUTE_OF_HOUR,2)}.
   1943      * The optional section ends automatically at the end of the builder.
   1944      * During formatting, the minute will only be output if its value can be obtained from the date-time.
   1945      * During parsing, the input will be successfully parsed whether the minute is present or not.
   1946      *
   1947      * @return this, for chaining, not null
   1948      */
   1949     public DateTimeFormatterBuilder optionalStart() {
   1950         active.valueParserIndex = -1;
   1951         active = new DateTimeFormatterBuilder(active, true);
   1952         return this;
   1953     }
   1954 
   1955     /**
   1956      * Ends an optional section.
   1957      * <p>
   1958      * The output of formatting can include optional sections, which may be nested.
   1959      * An optional section is started by calling {@link #optionalStart()} and ended
   1960      * using this method (or at the end of the builder).
   1961      * <p>
   1962      * Calling this method without having previously called {@code optionalStart}
   1963      * will throw an exception.
   1964      * Calling this method immediately after calling {@code optionalStart} has no effect
   1965      * on the formatter other than ending the (empty) optional section.
   1966      * <p>
   1967      * All elements in the optional section are treated as optional.
   1968      * During formatting, the section is only output if data is available in the
   1969      * {@code TemporalAccessor} for all the elements in the section.
   1970      * During parsing, the whole section may be missing from the parsed string.
   1971      * <p>
   1972      * For example, consider a builder setup as
   1973      * {@code builder.appendValue(HOUR_OF_DAY,2).optionalStart().appendValue(MINUTE_OF_HOUR,2).optionalEnd()}.
   1974      * During formatting, the minute will only be output if its value can be obtained from the date-time.
   1975      * During parsing, the input will be successfully parsed whether the minute is present or not.
   1976      *
   1977      * @return this, for chaining, not null
   1978      * @throws IllegalStateException if there was no previous call to {@code optionalStart}
   1979      */
   1980     public DateTimeFormatterBuilder optionalEnd() {
   1981         if (active.parent == null) {
   1982             throw new IllegalStateException("Cannot call optionalEnd() as there was no previous call to optionalStart()");
   1983         }
   1984         if (active.printerParsers.size() > 0) {
   1985             CompositePrinterParser cpp = new CompositePrinterParser(active.printerParsers, active.optional);
   1986             active = active.parent;
   1987             appendInternal(cpp);
   1988         } else {
   1989             active = active.parent;
   1990         }
   1991         return this;
   1992     }
   1993 
   1994     //-----------------------------------------------------------------------
   1995     /**
   1996      * Appends a printer and/or parser to the internal list handling padding.
   1997      *
   1998      * @param pp  the printer-parser to add, not null
   1999      * @return the index into the active parsers list
   2000      */
   2001     private int appendInternal(DateTimePrinterParser pp) {
   2002         Objects.requireNonNull(pp, "pp");
   2003         if (active.padNextWidth > 0) {
   2004             if (pp != null) {
   2005                 pp = new PadPrinterParserDecorator(pp, active.padNextWidth, active.padNextChar);
   2006             }
   2007             active.padNextWidth = 0;
   2008             active.padNextChar = 0;
   2009         }
   2010         active.printerParsers.add(pp);
   2011         active.valueParserIndex = -1;
   2012         return active.printerParsers.size() - 1;
   2013     }
   2014 
   2015     //-----------------------------------------------------------------------
   2016     /**
   2017      * Completes this builder by creating the {@code DateTimeFormatter}
   2018      * using the default locale.
   2019      * <p>
   2020      * This will create a formatter with the {@linkplain Locale#getDefault(Locale.Category) default FORMAT locale}.
   2021      * Numbers will be printed and parsed using the standard DecimalStyle.
   2022      * The resolver style will be {@link ResolverStyle#SMART SMART}.
   2023      * <p>
   2024      * Calling this method will end any open optional sections by repeatedly
   2025      * calling {@link #optionalEnd()} before creating the formatter.
   2026      * <p>
   2027      * This builder can still be used after creating the formatter if desired,
   2028      * although the state may have been changed by calls to {@code optionalEnd}.
   2029      *
   2030      * @return the created formatter, not null
   2031      */
   2032     public DateTimeFormatter toFormatter() {
   2033         return toFormatter(Locale.getDefault(Locale.Category.FORMAT));
   2034     }
   2035 
   2036     /**
   2037      * Completes this builder by creating the {@code DateTimeFormatter}
   2038      * using the specified locale.
   2039      * <p>
   2040      * This will create a formatter with the specified locale.
   2041      * Numbers will be printed and parsed using the standard DecimalStyle.
   2042      * The resolver style will be {@link ResolverStyle#SMART SMART}.
   2043      * <p>
   2044      * Calling this method will end any open optional sections by repeatedly
   2045      * calling {@link #optionalEnd()} before creating the formatter.
   2046      * <p>
   2047      * This builder can still be used after creating the formatter if desired,
   2048      * although the state may have been changed by calls to {@code optionalEnd}.
   2049      *
   2050      * @param locale  the locale to use for formatting, not null
   2051      * @return the created formatter, not null
   2052      */
   2053     public DateTimeFormatter toFormatter(Locale locale) {
   2054         return toFormatter(locale, ResolverStyle.SMART, null);
   2055     }
   2056 
   2057     /**
   2058      * Completes this builder by creating the formatter.
   2059      * This uses the default locale.
   2060      *
   2061      * @param resolverStyle  the resolver style to use, not null
   2062      * @return the created formatter, not null
   2063      */
   2064     DateTimeFormatter toFormatter(ResolverStyle resolverStyle, Chronology chrono) {
   2065         return toFormatter(Locale.getDefault(Locale.Category.FORMAT), resolverStyle, chrono);
   2066     }
   2067 
   2068     /**
   2069      * Completes this builder by creating the formatter.
   2070      *
   2071      * @param locale  the locale to use for formatting, not null
   2072      * @param chrono  the chronology to use, may be null
   2073      * @return the created formatter, not null
   2074      */
   2075     private DateTimeFormatter toFormatter(Locale locale, ResolverStyle resolverStyle, Chronology chrono) {
   2076         Objects.requireNonNull(locale, "locale");
   2077         while (active.parent != null) {
   2078             optionalEnd();
   2079         }
   2080         CompositePrinterParser pp = new CompositePrinterParser(printerParsers, false);
   2081         return new DateTimeFormatter(pp, locale, DecimalStyle.STANDARD,
   2082                 resolverStyle, null, chrono, null);
   2083     }
   2084 
   2085     //-----------------------------------------------------------------------
   2086     /**
   2087      * Strategy for formatting/parsing date-time information.
   2088      * <p>
   2089      * The printer may format any part, or the whole, of the input date-time object.
   2090      * Typically, a complete format is constructed from a number of smaller
   2091      * units, each outputting a single field.
   2092      * <p>
   2093      * The parser may parse any piece of text from the input, storing the result
   2094      * in the context. Typically, each individual parser will just parse one
   2095      * field, such as the day-of-month, storing the value in the context.
   2096      * Once the parse is complete, the caller will then resolve the parsed values
   2097      * to create the desired object, such as a {@code LocalDate}.
   2098      * <p>
   2099      * The parse position will be updated during the parse. Parsing will start at
   2100      * the specified index and the return value specifies the new parse position
   2101      * for the next parser. If an error occurs, the returned index will be negative
   2102      * and will have the error position encoded using the complement operator.
   2103      *
   2104      * @implSpec
   2105      * This interface must be implemented with care to ensure other classes operate correctly.
   2106      * All implementations that can be instantiated must be final, immutable and thread-safe.
   2107      * <p>
   2108      * The context is not a thread-safe object and a new instance will be created
   2109      * for each format that occurs. The context must not be stored in an instance
   2110      * variable or shared with any other threads.
   2111      */
   2112     interface DateTimePrinterParser {
   2113 
   2114         /**
   2115          * Prints the date-time object to the buffer.
   2116          * <p>
   2117          * The context holds information to use during the format.
   2118          * It also contains the date-time information to be printed.
   2119          * <p>
   2120          * The buffer must not be mutated beyond the content controlled by the implementation.
   2121          *
   2122          * @param context  the context to format using, not null
   2123          * @param buf  the buffer to append to, not null
   2124          * @return false if unable to query the value from the date-time, true otherwise
   2125          * @throws DateTimeException if the date-time cannot be printed successfully
   2126          */
   2127         boolean format(DateTimePrintContext context, StringBuilder buf);
   2128 
   2129         /**
   2130          * Parses text into date-time information.
   2131          * <p>
   2132          * The context holds information to use during the parse.
   2133          * It is also used to store the parsed date-time information.
   2134          *
   2135          * @param context  the context to use and parse into, not null
   2136          * @param text  the input text to parse, not null
   2137          * @param position  the position to start parsing at, from 0 to the text length
   2138          * @return the new parse position, where negative means an error with the
   2139          *  error position encoded using the complement ~ operator
   2140          * @throws NullPointerException if the context or text is null
   2141          * @throws IndexOutOfBoundsException if the position is invalid
   2142          */
   2143         int parse(DateTimeParseContext context, CharSequence text, int position);
   2144     }
   2145 
   2146     //-----------------------------------------------------------------------
   2147     /**
   2148      * Composite printer and parser.
   2149      */
   2150     static final class CompositePrinterParser implements DateTimePrinterParser {
   2151         private final DateTimePrinterParser[] printerParsers;
   2152         private final boolean optional;
   2153 
   2154         CompositePrinterParser(List<DateTimePrinterParser> printerParsers, boolean optional) {
   2155             this(printerParsers.toArray(new DateTimePrinterParser[printerParsers.size()]), optional);
   2156         }
   2157 
   2158         CompositePrinterParser(DateTimePrinterParser[] printerParsers, boolean optional) {
   2159             this.printerParsers = printerParsers;
   2160             this.optional = optional;
   2161         }
   2162 
   2163         /**
   2164          * Returns a copy of this printer-parser with the optional flag changed.
   2165          *
   2166          * @param optional  the optional flag to set in the copy
   2167          * @return the new printer-parser, not null
   2168          */
   2169         public CompositePrinterParser withOptional(boolean optional) {
   2170             if (optional == this.optional) {
   2171                 return this;
   2172             }
   2173             return new CompositePrinterParser(printerParsers, optional);
   2174         }
   2175 
   2176         @Override
   2177         public boolean format(DateTimePrintContext context, StringBuilder buf) {
   2178             int length = buf.length();
   2179             if (optional) {
   2180                 context.startOptional();
   2181             }
   2182             try {
   2183                 for (DateTimePrinterParser pp : printerParsers) {
   2184                     if (pp.format(context, buf) == false) {
   2185                         buf.setLength(length);  // reset buffer
   2186                         return true;
   2187                     }
   2188                 }
   2189             } finally {
   2190                 if (optional) {
   2191                     context.endOptional();
   2192                 }
   2193             }
   2194             return true;
   2195         }
   2196 
   2197         @Override
   2198         public int parse(DateTimeParseContext context, CharSequence text, int position) {
   2199             if (optional) {
   2200                 context.startOptional();
   2201                 int pos = position;
   2202                 for (DateTimePrinterParser pp : printerParsers) {
   2203                     pos = pp.parse(context, text, pos);
   2204                     if (pos < 0) {
   2205                         context.endOptional(false);
   2206                         return position;  // return original position
   2207                     }
   2208                 }
   2209                 context.endOptional(true);
   2210                 return pos;
   2211             } else {
   2212                 for (DateTimePrinterParser pp : printerParsers) {
   2213                     position = pp.parse(context, text, position);
   2214                     if (position < 0) {
   2215                         break;
   2216                     }
   2217                 }
   2218                 return position;
   2219             }
   2220         }
   2221 
   2222         @Override
   2223         public String toString() {
   2224             StringBuilder buf = new StringBuilder();
   2225             if (printerParsers != null) {
   2226                 buf.append(optional ? "[" : "(");
   2227                 for (DateTimePrinterParser pp : printerParsers) {
   2228                     buf.append(pp);
   2229                 }
   2230                 buf.append(optional ? "]" : ")");
   2231             }
   2232             return buf.toString();
   2233         }
   2234     }
   2235 
   2236     //-----------------------------------------------------------------------
   2237     /**
   2238      * Pads the output to a fixed width.
   2239      */
   2240     static final class PadPrinterParserDecorator implements DateTimePrinterParser {
   2241         private final DateTimePrinterParser printerParser;
   2242         private final int padWidth;
   2243         private final char padChar;
   2244 
   2245         /**
   2246          * Constructor.
   2247          *
   2248          * @param printerParser  the printer, not null
   2249          * @param padWidth  the width to pad to, 1 or greater
   2250          * @param padChar  the pad character
   2251          */
   2252         PadPrinterParserDecorator(DateTimePrinterParser printerParser, int padWidth, char padChar) {
   2253             // input checked by DateTimeFormatterBuilder
   2254             this.printerParser = printerParser;
   2255             this.padWidth = padWidth;
   2256             this.padChar = padChar;
   2257         }
   2258 
   2259         @Override
   2260         public boolean format(DateTimePrintContext context, StringBuilder buf) {
   2261             int preLen = buf.length();
   2262             if (printerParser.format(context, buf) == false) {
   2263                 return false;
   2264             }
   2265             int len = buf.length() - preLen;
   2266             if (len > padWidth) {
   2267                 throw new DateTimeException(
   2268                     "Cannot print as output of " + len + " characters exceeds pad width of " + padWidth);
   2269             }
   2270             for (int i = 0; i < padWidth - len; i++) {
   2271                 buf.insert(preLen, padChar);
   2272             }
   2273             return true;
   2274         }
   2275 
   2276         @Override
   2277         public int parse(DateTimeParseContext context, CharSequence text, int position) {
   2278             // cache context before changed by decorated parser
   2279             final boolean strict = context.isStrict();
   2280             // parse
   2281             if (position > text.length()) {
   2282                 throw new IndexOutOfBoundsException();
   2283             }
   2284             if (position == text.length()) {
   2285                 return ~position;  // no more characters in the string
   2286             }
   2287             int endPos = position + padWidth;
   2288             if (endPos > text.length()) {
   2289                 if (strict) {
   2290                     return ~position;  // not enough characters in the string to meet the parse width
   2291                 }
   2292                 endPos = text.length();
   2293             }
   2294             int pos = position;
   2295             while (pos < endPos && context.charEquals(text.charAt(pos), padChar)) {
   2296                 pos++;
   2297             }
   2298             text = text.subSequence(0, endPos);
   2299             int resultPos = printerParser.parse(context, text, pos);
   2300             if (resultPos != endPos && strict) {
   2301                 return ~(position + pos);  // parse of decorated field didn't parse to the end
   2302             }
   2303             return resultPos;
   2304         }
   2305 
   2306         @Override
   2307         public String toString() {
   2308             return "Pad(" + printerParser + "," + padWidth + (padChar == ' ' ? ")" : ",'" + padChar + "')");
   2309         }
   2310     }
   2311 
   2312     //-----------------------------------------------------------------------
   2313     /**
   2314      * Enumeration to apply simple parse settings.
   2315      */
   2316     static enum SettingsParser implements DateTimePrinterParser {
   2317         SENSITIVE,
   2318         INSENSITIVE,
   2319         STRICT,
   2320         LENIENT;
   2321 
   2322         @Override
   2323         public boolean format(DateTimePrintContext context, StringBuilder buf) {
   2324             return true;  // nothing to do here
   2325         }
   2326 
   2327         @Override
   2328         public int parse(DateTimeParseContext context, CharSequence text, int position) {
   2329             // using ordinals to avoid javac synthetic inner class
   2330             switch (ordinal()) {
   2331                 case 0: context.setCaseSensitive(true); break;
   2332                 case 1: context.setCaseSensitive(false); break;
   2333                 case 2: context.setStrict(true); break;
   2334                 case 3: context.setStrict(false); break;
   2335             }
   2336             return position;
   2337         }
   2338 
   2339         @Override
   2340         public String toString() {
   2341             // using ordinals to avoid javac synthetic inner class
   2342             switch (ordinal()) {
   2343                 case 0: return "ParseCaseSensitive(true)";
   2344                 case 1: return "ParseCaseSensitive(false)";
   2345                 case 2: return "ParseStrict(true)";
   2346                 case 3: return "ParseStrict(false)";
   2347             }
   2348             throw new IllegalStateException("Unreachable");
   2349         }
   2350     }
   2351 
   2352     //-----------------------------------------------------------------------
   2353     /**
   2354      * Defaults a value into the parse if not currently present.
   2355      */
   2356     static class DefaultValueParser implements DateTimePrinterParser {
   2357         private final TemporalField field;
   2358         private final long value;
   2359 
   2360         DefaultValueParser(TemporalField field, long value) {
   2361             this.field = field;
   2362             this.value = value;
   2363         }
   2364 
   2365         public boolean format(DateTimePrintContext context, StringBuilder buf) {
   2366             return true;
   2367         }
   2368 
   2369         public int parse(DateTimeParseContext context, CharSequence text, int position) {
   2370             if (context.getParsed(field) == null) {
   2371                 context.setParsedField(field, value, position, position);
   2372             }
   2373             return position;
   2374         }
   2375     }
   2376 
   2377     //-----------------------------------------------------------------------
   2378     /**
   2379      * Prints or parses a character literal.
   2380      */
   2381     static final class CharLiteralPrinterParser implements DateTimePrinterParser {
   2382         private final char literal;
   2383 
   2384         CharLiteralPrinterParser(char literal) {
   2385             this.literal = literal;
   2386         }
   2387 
   2388         @Override
   2389         public boolean format(DateTimePrintContext context, StringBuilder buf) {
   2390             buf.append(literal);
   2391             return true;
   2392         }
   2393 
   2394         @Override
   2395         public int parse(DateTimeParseContext context, CharSequence text, int position) {
   2396             int length = text.length();
   2397             if (position == length) {
   2398                 return ~position;
   2399             }
   2400             char ch = text.charAt(position);
   2401             if (ch != literal) {
   2402                 if (context.isCaseSensitive() ||
   2403                         (Character.toUpperCase(ch) != Character.toUpperCase(literal) &&
   2404                          Character.toLowerCase(ch) != Character.toLowerCase(literal))) {
   2405                     return ~position;
   2406                 }
   2407             }
   2408             return position + 1;
   2409         }
   2410 
   2411         @Override
   2412         public String toString() {
   2413             if (literal == '\'') {
   2414                 return "''";
   2415             }
   2416             return "'" + literal + "'";
   2417         }
   2418     }
   2419 
   2420     //-----------------------------------------------------------------------
   2421     /**
   2422      * Prints or parses a string literal.
   2423      */
   2424     static final class StringLiteralPrinterParser implements DateTimePrinterParser {
   2425         private final String literal;
   2426 
   2427         StringLiteralPrinterParser(String literal) {
   2428             this.literal = literal;  // validated by caller
   2429         }
   2430 
   2431         @Override
   2432         public boolean format(DateTimePrintContext context, StringBuilder buf) {
   2433             buf.append(literal);
   2434             return true;
   2435         }
   2436 
   2437         @Override
   2438         public int parse(DateTimeParseContext context, CharSequence text, int position) {
   2439             int length = text.length();
   2440             if (position > length || position < 0) {
   2441                 throw new IndexOutOfBoundsException();
   2442             }
   2443             if (context.subSequenceEquals(text, position, literal, 0, literal.length()) == false) {
   2444                 return ~position;
   2445             }
   2446             return position + literal.length();
   2447         }
   2448 
   2449         @Override
   2450         public String toString() {
   2451             String converted = literal.replace("'", "''");
   2452             return "'" + converted + "'";
   2453         }
   2454     }
   2455 
   2456     //-----------------------------------------------------------------------
   2457     /**
   2458      * Prints and parses a numeric date-time field with optional padding.
   2459      */
   2460     static class NumberPrinterParser implements DateTimePrinterParser {
   2461 
   2462         /**
   2463          * Array of 10 to the power of n.
   2464          */
   2465         static final long[] EXCEED_POINTS = new long[] {
   2466             0L,
   2467             10L,
   2468             100L,
   2469             1000L,
   2470             10000L,
   2471             100000L,
   2472             1000000L,
   2473             10000000L,
   2474             100000000L,
   2475             1000000000L,
   2476             10000000000L,
   2477         };
   2478 
   2479         final TemporalField field;
   2480         final int minWidth;
   2481         final int maxWidth;
   2482         private final SignStyle signStyle;
   2483         final int subsequentWidth;
   2484 
   2485         /**
   2486          * Constructor.
   2487          *
   2488          * @param field  the field to format, not null
   2489          * @param minWidth  the minimum field width, from 1 to 19
   2490          * @param maxWidth  the maximum field width, from minWidth to 19
   2491          * @param signStyle  the positive/negative sign style, not null
   2492          */
   2493         NumberPrinterParser(TemporalField field, int minWidth, int maxWidth, SignStyle signStyle) {
   2494             // validated by caller
   2495             this.field = field;
   2496             this.minWidth = minWidth;
   2497             this.maxWidth = maxWidth;
   2498             this.signStyle = signStyle;
   2499             this.subsequentWidth = 0;
   2500         }
   2501 
   2502         /**
   2503          * Constructor.
   2504          *
   2505          * @param field  the field to format, not null
   2506          * @param minWidth  the minimum field width, from 1 to 19
   2507          * @param maxWidth  the maximum field width, from minWidth to 19
   2508          * @param signStyle  the positive/negative sign style, not null
   2509          * @param subsequentWidth  the width of subsequent non-negative numbers, 0 or greater,
   2510          *  -1 if fixed width due to active adjacent parsing
   2511          */
   2512         protected NumberPrinterParser(TemporalField field, int minWidth, int maxWidth, SignStyle signStyle, int subsequentWidth) {
   2513             // validated by caller
   2514             this.field = field;
   2515             this.minWidth = minWidth;
   2516             this.maxWidth = maxWidth;
   2517             this.signStyle = signStyle;
   2518             this.subsequentWidth = subsequentWidth;
   2519         }
   2520 
   2521         /**
   2522          * Returns a new instance with fixed width flag set.
   2523          *
   2524          * @return a new updated printer-parser, not null
   2525          */
   2526         NumberPrinterParser withFixedWidth() {
   2527             if (subsequentWidth == -1) {
   2528                 return this;
   2529             }
   2530             return new NumberPrinterParser(field, minWidth, maxWidth, signStyle, -1);
   2531         }
   2532 
   2533         /**
   2534          * Returns a new instance with an updated subsequent width.
   2535          *
   2536          * @param subsequentWidth  the width of subsequent non-negative numbers, 0 or greater
   2537          * @return a new updated printer-parser, not null
   2538          */
   2539         NumberPrinterParser withSubsequentWidth(int subsequentWidth) {
   2540             return new NumberPrinterParser(field, minWidth, maxWidth, signStyle, this.subsequentWidth + subsequentWidth);
   2541         }
   2542 
   2543         @Override
   2544         public boolean format(DateTimePrintContext context, StringBuilder buf) {
   2545             Long valueLong = context.getValue(field);
   2546             if (valueLong == null) {
   2547                 return false;
   2548             }
   2549             long value = getValue(context, valueLong);
   2550             DecimalStyle decimalStyle = context.getDecimalStyle();
   2551             String str = (value == Long.MIN_VALUE ? "9223372036854775808" : Long.toString(Math.abs(value)));
   2552             if (str.length() > maxWidth) {
   2553                 throw new DateTimeException("Field " + field +
   2554                     " cannot be printed as the value " + value +
   2555                     " exceeds the maximum print width of " + maxWidth);
   2556             }
   2557             str = decimalStyle.convertNumberToI18N(str);
   2558 
   2559             if (value >= 0) {
   2560                 switch (signStyle) {
   2561                     case EXCEEDS_PAD:
   2562                         if (minWidth < 19 && value >= EXCEED_POINTS[minWidth]) {
   2563                             buf.append(decimalStyle.getPositiveSign());
   2564                         }
   2565                         break;
   2566                     case ALWAYS:
   2567                         buf.append(decimalStyle.getPositiveSign());
   2568                         break;
   2569                 }
   2570             } else {
   2571                 switch (signStyle) {
   2572                     case NORMAL:
   2573                     case EXCEEDS_PAD:
   2574                     case ALWAYS:
   2575                         buf.append(decimalStyle.getNegativeSign());
   2576                         break;
   2577                     case NOT_NEGATIVE:
   2578                         throw new DateTimeException("Field " + field +
   2579                             " cannot be printed as the value " + value +
   2580                             " cannot be negative according to the SignStyle");
   2581                 }
   2582             }
   2583             for (int i = 0; i < minWidth - str.length(); i++) {
   2584                 buf.append(decimalStyle.getZeroDigit());
   2585             }
   2586             buf.append(str);
   2587             return true;
   2588         }
   2589 
   2590         /**
   2591          * Gets the value to output.
   2592          *
   2593          * @param context  the context
   2594          * @param value  the value of the field, not null
   2595          * @return the value
   2596          */
   2597         long getValue(DateTimePrintContext context, long value) {
   2598             return value;
   2599         }
   2600 
   2601         /**
   2602          * For NumberPrinterParser, the width is fixed depending on the
   2603          * minWidth, maxWidth, signStyle and whether subsequent fields are fixed.
   2604          * @param context the context
   2605          * @return true if the field is fixed width
   2606          * @see DateTimeFormatterBuilder#appendValue(java.time.temporal.TemporalField, int)
   2607          */
   2608         boolean isFixedWidth(DateTimeParseContext context) {
   2609             return subsequentWidth == -1 ||
   2610                 (subsequentWidth > 0 && minWidth == maxWidth && signStyle == SignStyle.NOT_NEGATIVE);
   2611         }
   2612 
   2613         @Override
   2614         public int parse(DateTimeParseContext context, CharSequence text, int position) {
   2615             int length = text.length();
   2616             if (position == length) {
   2617                 return ~position;
   2618             }
   2619             char sign = text.charAt(position);  // IOOBE if invalid position
   2620             boolean negative = false;
   2621             boolean positive = false;
   2622             if (sign == context.getDecimalStyle().getPositiveSign()) {
   2623                 if (signStyle.parse(true, context.isStrict(), minWidth == maxWidth) == false) {
   2624                     return ~position;
   2625                 }
   2626                 positive = true;
   2627                 position++;
   2628             } else if (sign == context.getDecimalStyle().getNegativeSign()) {
   2629                 if (signStyle.parse(false, context.isStrict(), minWidth == maxWidth) == false) {
   2630                     return ~position;
   2631                 }
   2632                 negative = true;
   2633                 position++;
   2634             } else {
   2635                 if (signStyle == SignStyle.ALWAYS && context.isStrict()) {
   2636                     return ~position;
   2637                 }
   2638             }
   2639             int effMinWidth = (context.isStrict() || isFixedWidth(context) ? minWidth : 1);
   2640             int minEndPos = position + effMinWidth;
   2641             if (minEndPos > length) {
   2642                 return ~position;
   2643             }
   2644             int effMaxWidth = (context.isStrict() || isFixedWidth(context) ? maxWidth : 9) + Math.max(subsequentWidth, 0);
   2645             long total = 0;
   2646             BigInteger totalBig = null;
   2647             int pos = position;
   2648             for (int pass = 0; pass < 2; pass++) {
   2649                 int maxEndPos = Math.min(pos + effMaxWidth, length);
   2650                 while (pos < maxEndPos) {
   2651                     char ch = text.charAt(pos++);
   2652                     int digit = context.getDecimalStyle().convertToDigit(ch);
   2653                     if (digit < 0) {
   2654                         pos--;
   2655                         if (pos < minEndPos) {
   2656                             return ~position;  // need at least min width digits
   2657                         }
   2658                         break;
   2659                     }
   2660                     if ((pos - position) > 18) {
   2661                         if (totalBig == null) {
   2662                             totalBig = BigInteger.valueOf(total);
   2663                         }
   2664                         totalBig = totalBig.multiply(BigInteger.TEN).add(BigInteger.valueOf(digit));
   2665                     } else {
   2666                         total = total * 10 + digit;
   2667                     }
   2668                 }
   2669                 if (subsequentWidth > 0 && pass == 0) {
   2670                     // re-parse now we know the correct width
   2671                     int parseLen = pos - position;
   2672                     effMaxWidth = Math.max(effMinWidth, parseLen - subsequentWidth);
   2673                     pos = position;
   2674                     total = 0;
   2675                     totalBig = null;
   2676                 } else {
   2677                     break;
   2678                 }
   2679             }
   2680             if (negative) {
   2681                 if (totalBig != null) {
   2682                     if (totalBig.equals(BigInteger.ZERO) && context.isStrict()) {
   2683                         return ~(position - 1);  // minus zero not allowed
   2684                     }
   2685                     totalBig = totalBig.negate();
   2686                 } else {
   2687                     if (total == 0 && context.isStrict()) {
   2688                         return ~(position - 1);  // minus zero not allowed
   2689                     }
   2690                     total = -total;
   2691                 }
   2692             } else if (signStyle == SignStyle.EXCEEDS_PAD && context.isStrict()) {
   2693                 int parseLen = pos - position;
   2694                 if (positive) {
   2695                     if (parseLen <= minWidth) {
   2696                         return ~(position - 1);  // '+' only parsed if minWidth exceeded
   2697                     }
   2698                 } else {
   2699                     if (parseLen > minWidth) {
   2700                         return ~position;  // '+' must be parsed if minWidth exceeded
   2701                     }
   2702                 }
   2703             }
   2704             if (totalBig != null) {
   2705                 if (totalBig.bitLength() > 63) {
   2706                     // overflow, parse 1 less digit
   2707                     totalBig = totalBig.divide(BigInteger.TEN);
   2708                     pos--;
   2709                 }
   2710                 return setValue(context, totalBig.longValue(), position, pos);
   2711             }
   2712             return setValue(context, total, position, pos);
   2713         }
   2714 
   2715         /**
   2716          * Stores the value.
   2717          *
   2718          * @param context  the context to store into, not null
   2719          * @param value  the value
   2720          * @param errorPos  the position of the field being parsed
   2721          * @param successPos  the position after the field being parsed
   2722          * @return the new position
   2723          */
   2724         int setValue(DateTimeParseContext context, long value, int errorPos, int successPos) {
   2725             return context.setParsedField(field, value, errorPos, successPos);
   2726         }
   2727 
   2728         @Override
   2729         public String toString() {
   2730             if (minWidth == 1 && maxWidth == 19 && signStyle == SignStyle.NORMAL) {
   2731                 return "Value(" + field + ")";
   2732             }
   2733             if (minWidth == maxWidth && signStyle == SignStyle.NOT_NEGATIVE) {
   2734                 return "Value(" + field + "," + minWidth + ")";
   2735             }
   2736             return "Value(" + field + "," + minWidth + "," + maxWidth + "," + signStyle + ")";
   2737         }
   2738     }
   2739 
   2740     //-----------------------------------------------------------------------
   2741     /**
   2742      * Prints and parses a reduced numeric date-time field.
   2743      */
   2744     static final class ReducedPrinterParser extends NumberPrinterParser {
   2745         /**
   2746          * The base date for reduced value parsing.
   2747          */
   2748         static final LocalDate BASE_DATE = LocalDate.of(2000, 1, 1);
   2749 
   2750         private final int baseValue;
   2751         private final ChronoLocalDate baseDate;
   2752 
   2753         /**
   2754          * Constructor.
   2755          *
   2756          * @param field  the field to format, validated not null
   2757          * @param minWidth  the minimum field width, from 1 to 10
   2758          * @param maxWidth  the maximum field width, from 1 to 10
   2759          * @param baseValue  the base value
   2760          * @param baseDate  the base date
   2761          */
   2762         ReducedPrinterParser(TemporalField field, int minWidth, int maxWidth,
   2763                 int baseValue, ChronoLocalDate baseDate) {
   2764             this(field, minWidth, maxWidth, baseValue, baseDate, 0);
   2765             if (minWidth < 1 || minWidth > 10) {
   2766                 throw new IllegalArgumentException("The minWidth must be from 1 to 10 inclusive but was " + minWidth);
   2767             }
   2768             if (maxWidth < 1 || maxWidth > 10) {
   2769                 throw new IllegalArgumentException("The maxWidth must be from 1 to 10 inclusive but was " + minWidth);
   2770             }
   2771             if (maxWidth < minWidth) {
   2772                 throw new IllegalArgumentException("Maximum width must exceed or equal the minimum width but " +
   2773                         maxWidth + " < " + minWidth);
   2774             }
   2775             if (baseDate == null) {
   2776                 if (field.range().isValidValue(baseValue) == false) {
   2777                     throw new IllegalArgumentException("The base value must be within the range of the field");
   2778                 }
   2779                 if ((((long) baseValue) + EXCEED_POINTS[maxWidth]) > Integer.MAX_VALUE) {
   2780                     throw new DateTimeException("Unable to add printer-parser as the range exceeds the capacity of an int");
   2781                 }
   2782             }
   2783         }
   2784 
   2785         /**
   2786          * Constructor.
   2787          * The arguments have already been checked.
   2788          *
   2789          * @param field  the field to format, validated not null
   2790          * @param minWidth  the minimum field width, from 1 to 10
   2791          * @param maxWidth  the maximum field width, from 1 to 10
   2792          * @param baseValue  the base value
   2793          * @param baseDate  the base date
   2794          * @param subsequentWidth the subsequentWidth for this instance
   2795          */
   2796         private ReducedPrinterParser(TemporalField field, int minWidth, int maxWidth,
   2797                 int baseValue, ChronoLocalDate baseDate, int subsequentWidth) {
   2798             super(field, minWidth, maxWidth, SignStyle.NOT_NEGATIVE, subsequentWidth);
   2799             this.baseValue = baseValue;
   2800             this.baseDate = baseDate;
   2801         }
   2802 
   2803         @Override
   2804         long getValue(DateTimePrintContext context, long value) {
   2805             long absValue = Math.abs(value);
   2806             int baseValue = this.baseValue;
   2807             if (baseDate != null) {
   2808                 Chronology chrono = Chronology.from(context.getTemporal());
   2809                 baseValue = chrono.date(baseDate).get(field);
   2810             }
   2811             if (value >= baseValue && value < baseValue + EXCEED_POINTS[minWidth]) {
   2812                 // Use the reduced value if it fits in minWidth
   2813                 return absValue % EXCEED_POINTS[minWidth];
   2814             }
   2815             // Otherwise truncate to fit in maxWidth
   2816             return absValue % EXCEED_POINTS[maxWidth];
   2817         }
   2818 
   2819         @Override
   2820         int setValue(DateTimeParseContext context, long value, int errorPos, int successPos) {
   2821             int baseValue = this.baseValue;
   2822             if (baseDate != null) {
   2823                 Chronology chrono = context.getEffectiveChronology();
   2824                 baseValue = chrono.date(baseDate).get(field);
   2825 
   2826                 // In case the Chronology is changed later, add a callback when/if it changes
   2827                 final long initialValue = value;
   2828                 context.addChronoChangedListener(
   2829                         (_unused) ->  {
   2830                             /* Repeat the set of the field using the current Chronology
   2831                              * The success/error position is ignored because the value is
   2832                              * intentionally being overwritten.
   2833                              */
   2834                             setValue(context, initialValue, errorPos, successPos);
   2835                         });
   2836             }
   2837             int parseLen = successPos - errorPos;
   2838             if (parseLen == minWidth && value >= 0) {
   2839                 long range = EXCEED_POINTS[minWidth];
   2840                 long lastPart = baseValue % range;
   2841                 long basePart = baseValue - lastPart;
   2842                 if (baseValue > 0) {
   2843                     value = basePart + value;
   2844                 } else {
   2845                     value = basePart - value;
   2846                 }
   2847                 if (value < baseValue) {
   2848                     value += range;
   2849                 }
   2850             }
   2851             return context.setParsedField(field, value, errorPos, successPos);
   2852         }
   2853 
   2854         /**
   2855          * Returns a new instance with fixed width flag set.
   2856          *
   2857          * @return a new updated printer-parser, not null
   2858          */
   2859         @Override
   2860         ReducedPrinterParser withFixedWidth() {
   2861             if (subsequentWidth == -1) {
   2862                 return this;
   2863             }
   2864             return new ReducedPrinterParser(field, minWidth, maxWidth, baseValue, baseDate, -1);
   2865         }
   2866 
   2867         /**
   2868          * Returns a new instance with an updated subsequent width.
   2869          *
   2870          * @param subsequentWidth  the width of subsequent non-negative numbers, 0 or greater
   2871          * @return a new updated printer-parser, not null
   2872          */
   2873         @Override
   2874         ReducedPrinterParser withSubsequentWidth(int subsequentWidth) {
   2875             return new ReducedPrinterParser(field, minWidth, maxWidth, baseValue, baseDate,
   2876                     this.subsequentWidth + subsequentWidth);
   2877         }
   2878 
   2879         /**
   2880          * For a ReducedPrinterParser, fixed width is false if the mode is strict,
   2881          * otherwise it is set as for NumberPrinterParser.
   2882          * @param context the context
   2883          * @return if the field is fixed width
   2884          * @see DateTimeFormatterBuilder#appendValueReduced(java.time.temporal.TemporalField, int, int, int)
   2885          */
   2886         @Override
   2887         boolean isFixedWidth(DateTimeParseContext context) {
   2888            if (context.isStrict() == false) {
   2889                return false;
   2890            }
   2891            return super.isFixedWidth(context);
   2892         }
   2893 
   2894         @Override
   2895         public String toString() {
   2896             return "ReducedValue(" + field + "," + minWidth + "," + maxWidth + "," + (baseDate != null ? baseDate : baseValue) + ")";
   2897         }
   2898     }
   2899 
   2900     //-----------------------------------------------------------------------
   2901     /**
   2902      * Prints and parses a numeric date-time field with optional padding.
   2903      */
   2904     static final class FractionPrinterParser implements DateTimePrinterParser {
   2905         private final TemporalField field;
   2906         private final int minWidth;
   2907         private final int maxWidth;
   2908         private final boolean decimalPoint;
   2909 
   2910         /**
   2911          * Constructor.
   2912          *
   2913          * @param field  the field to output, not null
   2914          * @param minWidth  the minimum width to output, from 0 to 9
   2915          * @param maxWidth  the maximum width to output, from 0 to 9
   2916          * @param decimalPoint  whether to output the localized decimal point symbol
   2917          */
   2918         FractionPrinterParser(TemporalField field, int minWidth, int maxWidth, boolean decimalPoint) {
   2919             Objects.requireNonNull(field, "field");
   2920             if (field.range().isFixed() == false) {
   2921                 throw new IllegalArgumentException("Field must have a fixed set of values: " + field);
   2922             }
   2923             if (minWidth < 0 || minWidth > 9) {
   2924                 throw new IllegalArgumentException("Minimum width must be from 0 to 9 inclusive but was " + minWidth);
   2925             }
   2926             if (maxWidth < 1 || maxWidth > 9) {
   2927                 throw new IllegalArgumentException("Maximum width must be from 1 to 9 inclusive but was " + maxWidth);
   2928             }
   2929             if (maxWidth < minWidth) {
   2930                 throw new IllegalArgumentException("Maximum width must exceed or equal the minimum width but " +
   2931                         maxWidth + " < " + minWidth);
   2932             }
   2933             this.field = field;
   2934             this.minWidth = minWidth;
   2935             this.maxWidth = maxWidth;
   2936             this.decimalPoint = decimalPoint;
   2937         }
   2938 
   2939         @Override
   2940         public boolean format(DateTimePrintContext context, StringBuilder buf) {
   2941             Long value = context.getValue(field);
   2942             if (value == null) {
   2943                 return false;
   2944             }
   2945             DecimalStyle decimalStyle = context.getDecimalStyle();
   2946             BigDecimal fraction = convertToFraction(value);
   2947             if (fraction.scale() == 0) {  // scale is zero if value is zero
   2948                 if (minWidth > 0) {
   2949                     if (decimalPoint) {
   2950                         buf.append(decimalStyle.getDecimalSeparator());
   2951                     }
   2952                     for (int i = 0; i < minWidth; i++) {
   2953                         buf.append(decimalStyle.getZeroDigit());
   2954                     }
   2955                 }
   2956             } else {
   2957                 int outputScale = Math.min(Math.max(fraction.scale(), minWidth), maxWidth);
   2958                 fraction = fraction.setScale(outputScale, RoundingMode.FLOOR);
   2959                 String str = fraction.toPlainString().substring(2);
   2960                 str = decimalStyle.convertNumberToI18N(str);
   2961                 if (decimalPoint) {
   2962                     buf.append(decimalStyle.getDecimalSeparator());
   2963                 }
   2964                 buf.append(str);
   2965             }
   2966             return true;
   2967         }
   2968 
   2969         @Override
   2970         public int parse(DateTimeParseContext context, CharSequence text, int position) {
   2971             int effectiveMin = (context.isStrict() ? minWidth : 0);
   2972             int effectiveMax = (context.isStrict() ? maxWidth : 9);
   2973             int length = text.length();
   2974             if (position == length) {
   2975                 // valid if whole field is optional, invalid if minimum width
   2976                 return (effectiveMin > 0 ? ~position : position);
   2977             }
   2978             if (decimalPoint) {
   2979                 if (text.charAt(position) != context.getDecimalStyle().getDecimalSeparator()) {
   2980                     // valid if whole field is optional, invalid if minimum width
   2981                     return (effectiveMin > 0 ? ~position : position);
   2982                 }
   2983                 position++;
   2984             }
   2985             int minEndPos = position + effectiveMin;
   2986             if (minEndPos > length) {
   2987                 return ~position;  // need at least min width digits
   2988             }
   2989             int maxEndPos = Math.min(position + effectiveMax, length);
   2990             int total = 0;  // can use int because we are only parsing up to 9 digits
   2991             int pos = position;
   2992             while (pos < maxEndPos) {
   2993                 char ch = text.charAt(pos++);
   2994                 int digit = context.getDecimalStyle().convertToDigit(ch);
   2995                 if (digit < 0) {
   2996                     if (pos < minEndPos) {
   2997                         return ~position;  // need at least min width digits
   2998                     }
   2999                     pos--;
   3000                     break;
   3001                 }
   3002                 total = total * 10 + digit;
   3003             }
   3004             BigDecimal fraction = new BigDecimal(total).movePointLeft(pos - position);
   3005             long value = convertFromFraction(fraction);
   3006             return context.setParsedField(field, value, position, pos);
   3007         }
   3008 
   3009         /**
   3010          * Converts a value for this field to a fraction between 0 and 1.
   3011          * <p>
   3012          * The fractional value is between 0 (inclusive) and 1 (exclusive).
   3013          * It can only be returned if the {@link java.time.temporal.TemporalField#range() value range} is fixed.
   3014          * The fraction is obtained by calculation from the field range using 9 decimal
   3015          * places and a rounding mode of {@link RoundingMode#FLOOR FLOOR}.
   3016          * The calculation is inaccurate if the values do not run continuously from smallest to largest.
   3017          * <p>
   3018          * For example, the second-of-minute value of 15 would be returned as 0.25,
   3019          * assuming the standard definition of 60 seconds in a minute.
   3020          *
   3021          * @param value  the value to convert, must be valid for this rule
   3022          * @return the value as a fraction within the range, from 0 to 1, not null
   3023          * @throws DateTimeException if the value cannot be converted to a fraction
   3024          */
   3025         private BigDecimal convertToFraction(long value) {
   3026             ValueRange range = field.range();
   3027             range.checkValidValue(value, field);
   3028             BigDecimal minBD = BigDecimal.valueOf(range.getMinimum());
   3029             BigDecimal rangeBD = BigDecimal.valueOf(range.getMaximum()).subtract(minBD).add(BigDecimal.ONE);
   3030             BigDecimal valueBD = BigDecimal.valueOf(value).subtract(minBD);
   3031             BigDecimal fraction = valueBD.divide(rangeBD, 9, RoundingMode.FLOOR);
   3032             // stripTrailingZeros bug
   3033             return fraction.compareTo(BigDecimal.ZERO) == 0 ? BigDecimal.ZERO : fraction.stripTrailingZeros();
   3034         }
   3035 
   3036         /**
   3037          * Converts a fraction from 0 to 1 for this field to a value.
   3038          * <p>
   3039          * The fractional value must be between 0 (inclusive) and 1 (exclusive).
   3040          * It can only be returned if the {@link java.time.temporal.TemporalField#range() value range} is fixed.
   3041          * The value is obtained by calculation from the field range and a rounding
   3042          * mode of {@link RoundingMode#FLOOR FLOOR}.
   3043          * The calculation is inaccurate if the values do not run continuously from smallest to largest.
   3044          * <p>
   3045          * For example, the fractional second-of-minute of 0.25 would be converted to 15,
   3046          * assuming the standard definition of 60 seconds in a minute.
   3047          *
   3048          * @param fraction  the fraction to convert, not null
   3049          * @return the value of the field, valid for this rule
   3050          * @throws DateTimeException if the value cannot be converted
   3051          */
   3052         private long convertFromFraction(BigDecimal fraction) {
   3053             ValueRange range = field.range();
   3054             BigDecimal minBD = BigDecimal.valueOf(range.getMinimum());
   3055             BigDecimal rangeBD = BigDecimal.valueOf(range.getMaximum()).subtract(minBD).add(BigDecimal.ONE);
   3056             BigDecimal valueBD = fraction.multiply(rangeBD).setScale(0, RoundingMode.FLOOR).add(minBD);
   3057             return valueBD.longValueExact();
   3058         }
   3059 
   3060         @Override
   3061         public String toString() {
   3062             String decimal = (decimalPoint ? ",DecimalPoint" : "");
   3063             return "Fraction(" + field + "," + minWidth + "," + maxWidth + decimal + ")";
   3064         }
   3065     }
   3066 
   3067     //-----------------------------------------------------------------------
   3068     /**
   3069      * Prints or parses field text.
   3070      */
   3071     static final class TextPrinterParser implements DateTimePrinterParser {
   3072         private final TemporalField field;
   3073         private final TextStyle textStyle;
   3074         private final DateTimeTextProvider provider;
   3075         /**
   3076          * The cached number printer parser.
   3077          * Immutable and volatile, so no synchronization needed.
   3078          */
   3079         private volatile NumberPrinterParser numberPrinterParser;
   3080 
   3081         /**
   3082          * Constructor.
   3083          *
   3084          * @param field  the field to output, not null
   3085          * @param textStyle  the text style, not null
   3086          * @param provider  the text provider, not null
   3087          */
   3088         TextPrinterParser(TemporalField field, TextStyle textStyle, DateTimeTextProvider provider) {
   3089             // validated by caller
   3090             this.field = field;
   3091             this.textStyle = textStyle;
   3092             this.provider = provider;
   3093         }
   3094 
   3095         @Override
   3096         public boolean format(DateTimePrintContext context, StringBuilder buf) {
   3097             Long value = context.getValue(field);
   3098             if (value == null) {
   3099                 return false;
   3100             }
   3101             String text;
   3102             Chronology chrono = context.getTemporal().query(TemporalQueries.chronology());
   3103             if (chrono == null || chrono == IsoChronology.INSTANCE) {
   3104                 text = provider.getText(field, value, textStyle, context.getLocale());
   3105             } else {
   3106                 text = provider.getText(chrono, field, value, textStyle, context.getLocale());
   3107             }
   3108             if (text == null) {
   3109                 return numberPrinterParser().format(context, buf);
   3110             }
   3111             buf.append(text);
   3112             return true;
   3113         }
   3114 
   3115         @Override
   3116         public int parse(DateTimeParseContext context, CharSequence parseText, int position) {
   3117             int length = parseText.length();
   3118             if (position < 0 || position > length) {
   3119                 throw new IndexOutOfBoundsException();
   3120             }
   3121             TextStyle style = (context.isStrict() ? textStyle : null);
   3122             Chronology chrono = context.getEffectiveChronology();
   3123             Iterator<Entry<String, Long>> it;
   3124             if (chrono == null || chrono == IsoChronology.INSTANCE) {
   3125                 it = provider.getTextIterator(field, style, context.getLocale());
   3126             } else {
   3127                 it = provider.getTextIterator(chrono, field, style, context.getLocale());
   3128             }
   3129             if (it != null) {
   3130                 while (it.hasNext()) {
   3131                     Entry<String, Long> entry = it.next();
   3132                     String itText = entry.getKey();
   3133                     if (context.subSequenceEquals(itText, 0, parseText, position, itText.length())) {
   3134                         return context.setParsedField(field, entry.getValue(), position, position + itText.length());
   3135                     }
   3136                 }
   3137                 if (context.isStrict()) {
   3138                     return ~position;
   3139                 }
   3140             }
   3141             return numberPrinterParser().parse(context, parseText, position);
   3142         }
   3143 
   3144         /**
   3145          * Create and cache a number printer parser.
   3146          * @return the number printer parser for this field, not null
   3147          */
   3148         private NumberPrinterParser numberPrinterParser() {
   3149             if (numberPrinterParser == null) {
   3150                 numberPrinterParser = new NumberPrinterParser(field, 1, 19, SignStyle.NORMAL);
   3151             }
   3152             return numberPrinterParser;
   3153         }
   3154 
   3155         @Override
   3156         public String toString() {
   3157             if (textStyle == TextStyle.FULL) {
   3158                 return "Text(" + field + ")";
   3159             }
   3160             return "Text(" + field + "," + textStyle + ")";
   3161         }
   3162     }
   3163 
   3164     //-----------------------------------------------------------------------
   3165     /**
   3166      * Prints or parses an ISO-8601 instant.
   3167      */
   3168     static final class InstantPrinterParser implements DateTimePrinterParser {
   3169         // days in a 400 year cycle = 146097
   3170         // days in a 10,000 year cycle = 146097 * 25
   3171         // seconds per day = 86400
   3172         private static final long SECONDS_PER_10000_YEARS = 146097L * 25L * 86400L;
   3173         private static final long SECONDS_0000_TO_1970 = ((146097L * 5L) - (30L * 365L + 7L)) * 86400L;
   3174         private final int fractionalDigits;
   3175 
   3176         InstantPrinterParser(int fractionalDigits) {
   3177             this.fractionalDigits = fractionalDigits;
   3178         }
   3179 
   3180         @Override
   3181         public boolean format(DateTimePrintContext context, StringBuilder buf) {
   3182             // use INSTANT_SECONDS, thus this code is not bound by Instant.MAX
   3183             Long inSecs = context.getValue(INSTANT_SECONDS);
   3184             Long inNanos = null;
   3185             if (context.getTemporal().isSupported(NANO_OF_SECOND)) {
   3186                 inNanos = context.getTemporal().getLong(NANO_OF_SECOND);
   3187             }
   3188             if (inSecs == null) {
   3189                 return false;
   3190             }
   3191             long inSec = inSecs;
   3192             int inNano = NANO_OF_SECOND.checkValidIntValue(inNanos != null ? inNanos : 0);
   3193             // format mostly using LocalDateTime.toString
   3194             if (inSec >= -SECONDS_0000_TO_1970) {
   3195                 // current era
   3196                 long zeroSecs = inSec - SECONDS_PER_10000_YEARS + SECONDS_0000_TO_1970;
   3197                 long hi = Math.floorDiv(zeroSecs, SECONDS_PER_10000_YEARS) + 1;
   3198                 long lo = Math.floorMod(zeroSecs, SECONDS_PER_10000_YEARS);
   3199                 LocalDateTime ldt = LocalDateTime.ofEpochSecond(lo - SECONDS_0000_TO_1970, 0, ZoneOffset.UTC);
   3200                 if (hi > 0) {
   3201                     buf.append('+').append(hi);
   3202                 }
   3203                 buf.append(ldt);
   3204                 if (ldt.getSecond() == 0) {
   3205                     buf.append(":00");
   3206                 }
   3207             } else {
   3208                 // before current era
   3209                 long zeroSecs = inSec + SECONDS_0000_TO_1970;
   3210                 long hi = zeroSecs / SECONDS_PER_10000_YEARS;
   3211                 long lo = zeroSecs % SECONDS_PER_10000_YEARS;
   3212                 LocalDateTime ldt = LocalDateTime.ofEpochSecond(lo - SECONDS_0000_TO_1970, 0, ZoneOffset.UTC);
   3213                 int pos = buf.length();
   3214                 buf.append(ldt);
   3215                 if (ldt.getSecond() == 0) {
   3216                     buf.append(":00");
   3217                 }
   3218                 if (hi < 0) {
   3219                     if (ldt.getYear() == -10_000) {
   3220                         buf.replace(pos, pos + 2, Long.toString(hi - 1));
   3221                     } else if (lo == 0) {
   3222                         buf.insert(pos, hi);
   3223                     } else {
   3224                         buf.insert(pos + 1, Math.abs(hi));
   3225                     }
   3226                 }
   3227             }
   3228             // add fraction
   3229             if ((fractionalDigits < 0 && inNano > 0) || fractionalDigits > 0) {
   3230                 buf.append('.');
   3231                 int div = 100_000_000;
   3232                 for (int i = 0; ((fractionalDigits == -1 && inNano > 0) ||
   3233                                     (fractionalDigits == -2 && (inNano > 0 || (i % 3) != 0)) ||
   3234                                     i < fractionalDigits); i++) {
   3235                     int digit = inNano / div;
   3236                     buf.append((char) (digit + '0'));
   3237                     inNano = inNano - (digit * div);
   3238                     div = div / 10;
   3239                 }
   3240             }
   3241             buf.append('Z');
   3242             return true;
   3243         }
   3244 
   3245         @Override
   3246         public int parse(DateTimeParseContext context, CharSequence text, int position) {
   3247             // new context to avoid overwriting fields like year/month/day
   3248             int minDigits = (fractionalDigits < 0 ? 0 : fractionalDigits);
   3249             int maxDigits = (fractionalDigits < 0 ? 9 : fractionalDigits);
   3250             CompositePrinterParser parser = new DateTimeFormatterBuilder()
   3251                     .append(DateTimeFormatter.ISO_LOCAL_DATE).appendLiteral('T')
   3252                     .appendValue(HOUR_OF_DAY, 2).appendLiteral(':')
   3253                     .appendValue(MINUTE_OF_HOUR, 2).appendLiteral(':')
   3254                     .appendValue(SECOND_OF_MINUTE, 2)
   3255                     .appendFraction(NANO_OF_SECOND, minDigits, maxDigits, true)
   3256                     .appendLiteral('Z')
   3257                     .toFormatter().toPrinterParser(false);
   3258             DateTimeParseContext newContext = context.copy();
   3259             int pos = parser.parse(newContext, text, position);
   3260             if (pos < 0) {
   3261                 return pos;
   3262             }
   3263             // parser restricts most fields to 2 digits, so definitely int
   3264             // correctly parsed nano is also guaranteed to be valid
   3265             long yearParsed = newContext.getParsed(YEAR);
   3266             int month = newContext.getParsed(MONTH_OF_YEAR).intValue();
   3267             int day = newContext.getParsed(DAY_OF_MONTH).intValue();
   3268             int hour = newContext.getParsed(HOUR_OF_DAY).intValue();
   3269             int min = newContext.getParsed(MINUTE_OF_HOUR).intValue();
   3270             Long secVal = newContext.getParsed(SECOND_OF_MINUTE);
   3271             Long nanoVal = newContext.getParsed(NANO_OF_SECOND);
   3272             int sec = (secVal != null ? secVal.intValue() : 0);
   3273             int nano = (nanoVal != null ? nanoVal.intValue() : 0);
   3274             int days = 0;
   3275             if (hour == 24 && min == 0 && sec == 0 && nano == 0) {
   3276                 hour = 0;
   3277                 days = 1;
   3278             } else if (hour == 23 && min == 59 && sec == 60) {
   3279                 context.setParsedLeapSecond();
   3280                 sec = 59;
   3281             }
   3282             int year = (int) yearParsed % 10_000;
   3283             long instantSecs;
   3284             try {
   3285                 LocalDateTime ldt = LocalDateTime.of(year, month, day, hour, min, sec, 0).plusDays(days);
   3286                 instantSecs = ldt.toEpochSecond(ZoneOffset.UTC);
   3287                 instantSecs += Math.multiplyExact(yearParsed / 10_000L, SECONDS_PER_10000_YEARS);
   3288             } catch (RuntimeException ex) {
   3289                 return ~position;
   3290             }
   3291             int successPos = pos;
   3292             successPos = context.setParsedField(INSTANT_SECONDS, instantSecs, position, successPos);
   3293             return context.setParsedField(NANO_OF_SECOND, nano, position, successPos);
   3294         }
   3295 
   3296         @Override
   3297         public String toString() {
   3298             return "Instant()";
   3299         }
   3300     }
   3301 
   3302     //-----------------------------------------------------------------------
   3303     /**
   3304      * Prints or parses an offset ID.
   3305      */
   3306     static final class OffsetIdPrinterParser implements DateTimePrinterParser {
   3307         static final String[] PATTERNS = new String[] {
   3308             "+HH", "+HHmm", "+HH:mm", "+HHMM", "+HH:MM", "+HHMMss", "+HH:MM:ss", "+HHMMSS", "+HH:MM:SS",
   3309         };  // order used in pattern builder
   3310         static final OffsetIdPrinterParser INSTANCE_ID_Z = new OffsetIdPrinterParser("+HH:MM:ss", "Z");
   3311         static final OffsetIdPrinterParser INSTANCE_ID_ZERO = new OffsetIdPrinterParser("+HH:MM:ss", "0");
   3312 
   3313         private final String noOffsetText;
   3314         private final int type;
   3315 
   3316         /**
   3317          * Constructor.
   3318          *
   3319          * @param pattern  the pattern
   3320          * @param noOffsetText  the text to use for UTC, not null
   3321          */
   3322         OffsetIdPrinterParser(String pattern, String noOffsetText) {
   3323             Objects.requireNonNull(pattern, "pattern");
   3324             Objects.requireNonNull(noOffsetText, "noOffsetText");
   3325             this.type = checkPattern(pattern);
   3326             this.noOffsetText = noOffsetText;
   3327         }
   3328 
   3329         private int checkPattern(String pattern) {
   3330             for (int i = 0; i < PATTERNS.length; i++) {
   3331                 if (PATTERNS[i].equals(pattern)) {
   3332                     return i;
   3333                 }
   3334             }
   3335             throw new IllegalArgumentException("Invalid zone offset pattern: " + pattern);
   3336         }
   3337 
   3338         @Override
   3339         public boolean format(DateTimePrintContext context, StringBuilder buf) {
   3340             Long offsetSecs = context.getValue(OFFSET_SECONDS);
   3341             if (offsetSecs == null) {
   3342                 return false;
   3343             }
   3344             int totalSecs = Math.toIntExact(offsetSecs);
   3345             if (totalSecs == 0) {
   3346                 buf.append(noOffsetText);
   3347             } else {
   3348                 int absHours = Math.abs((totalSecs / 3600) % 100);  // anything larger than 99 silently dropped
   3349                 int absMinutes = Math.abs((totalSecs / 60) % 60);
   3350                 int absSeconds = Math.abs(totalSecs % 60);
   3351                 int bufPos = buf.length();
   3352                 int output = absHours;
   3353                 buf.append(totalSecs < 0 ? "-" : "+")
   3354                     .append((char) (absHours / 10 + '0')).append((char) (absHours % 10 + '0'));
   3355                 if (type >= 3 || (type >= 1 && absMinutes > 0)) {
   3356                     buf.append((type % 2) == 0 ? ":" : "")
   3357                         .append((char) (absMinutes / 10 + '0')).append((char) (absMinutes % 10 + '0'));
   3358                     output += absMinutes;
   3359                     if (type >= 7 || (type >= 5 && absSeconds > 0)) {
   3360                         buf.append((type % 2) == 0 ? ":" : "")
   3361                             .append((char) (absSeconds / 10 + '0')).append((char) (absSeconds % 10 + '0'));
   3362                         output += absSeconds;
   3363                     }
   3364                 }
   3365                 if (output == 0) {
   3366                     buf.setLength(bufPos);
   3367                     buf.append(noOffsetText);
   3368                 }
   3369             }
   3370             return true;
   3371         }
   3372 
   3373         @Override
   3374         public int parse(DateTimeParseContext context, CharSequence text, int position) {
   3375             int length = text.length();
   3376             int noOffsetLen = noOffsetText.length();
   3377             if (noOffsetLen == 0) {
   3378                 if (position == length) {
   3379                     return context.setParsedField(OFFSET_SECONDS, 0, position, position);
   3380                 }
   3381             } else {
   3382                 if (position == length) {
   3383                     return ~position;
   3384                 }
   3385                 if (context.subSequenceEquals(text, position, noOffsetText, 0, noOffsetLen)) {
   3386                     return context.setParsedField(OFFSET_SECONDS, 0, position, position + noOffsetLen);
   3387                 }
   3388             }
   3389 
   3390             // parse normal plus/minus offset
   3391             char sign = text.charAt(position);  // IOOBE if invalid position
   3392             if (sign == '+' || sign == '-') {
   3393                 // starts
   3394                 int negative = (sign == '-' ? -1 : 1);
   3395                 int[] array = new int[4];
   3396                 array[0] = position + 1;
   3397                 if ((parseNumber(array, 1, text, true) ||
   3398                         parseNumber(array, 2, text, type >=3) ||
   3399                         parseNumber(array, 3, text, false)) == false) {
   3400                     // success
   3401                     long offsetSecs = negative * (array[1] * 3600L + array[2] * 60L + array[3]);
   3402                     return context.setParsedField(OFFSET_SECONDS, offsetSecs, position, array[0]);
   3403                 }
   3404             }
   3405             // handle special case of empty no offset text
   3406             if (noOffsetLen == 0) {
   3407                 return context.setParsedField(OFFSET_SECONDS, 0, position, position + noOffsetLen);
   3408             }
   3409             return ~position;
   3410         }
   3411 
   3412         /**
   3413          * Parse a two digit zero-prefixed number.
   3414          *
   3415          * @param array  the array of parsed data, 0=pos,1=hours,2=mins,3=secs, not null
   3416          * @param arrayIndex  the index to parse the value into
   3417          * @param parseText  the offset ID, not null
   3418          * @param required  whether this number is required
   3419          * @return true if an error occurred
   3420          */
   3421         private boolean parseNumber(int[] array, int arrayIndex, CharSequence parseText, boolean required) {
   3422             if ((type + 3) / 2 < arrayIndex) {
   3423                 return false;  // ignore seconds/minutes
   3424             }
   3425             int pos = array[0];
   3426             if ((type % 2) == 0 && arrayIndex > 1) {
   3427                 if (pos + 1 > parseText.length() || parseText.charAt(pos) != ':') {
   3428                     return required;
   3429                 }
   3430                 pos++;
   3431             }
   3432             if (pos + 2 > parseText.length()) {
   3433                 return required;
   3434             }
   3435             char ch1 = parseText.charAt(pos++);
   3436             char ch2 = parseText.charAt(pos++);
   3437             if (ch1 < '0' || ch1 > '9' || ch2 < '0' || ch2 > '9') {
   3438                 return required;
   3439             }
   3440             int value = (ch1 - 48) * 10 + (ch2 - 48);
   3441             if (value < 0 || value > 59) {
   3442                 return required;
   3443             }
   3444             array[arrayIndex] = value;
   3445             array[0] = pos;
   3446             return false;
   3447         }
   3448 
   3449         @Override
   3450         public String toString() {
   3451             String converted = noOffsetText.replace("'", "''");
   3452             return "Offset(" + PATTERNS[type] + ",'" + converted + "')";
   3453         }
   3454     }
   3455 
   3456     //-----------------------------------------------------------------------
   3457     /**
   3458      * Prints or parses an offset ID.
   3459      */
   3460     static final class LocalizedOffsetIdPrinterParser implements DateTimePrinterParser {
   3461         private final TextStyle style;
   3462 
   3463         /**
   3464          * Constructor.
   3465          *
   3466          * @param style  the style, not null
   3467          */
   3468         LocalizedOffsetIdPrinterParser(TextStyle style) {
   3469             this.style = style;
   3470         }
   3471 
   3472         private static StringBuilder appendHMS(StringBuilder buf, int t) {
   3473             return buf.append((char)(t / 10 + '0'))
   3474                       .append((char)(t % 10 + '0'));
   3475         }
   3476 
   3477         @Override
   3478         public boolean format(DateTimePrintContext context, StringBuilder buf) {
   3479             Long offsetSecs = context.getValue(OFFSET_SECONDS);
   3480             if (offsetSecs == null) {
   3481                 return false;
   3482             }
   3483             String gmtText = "GMT";  // TODO: get localized version of 'GMT'
   3484             if (gmtText != null) {
   3485                 buf.append(gmtText);
   3486             }
   3487             int totalSecs = Math.toIntExact(offsetSecs);
   3488             if (totalSecs != 0) {
   3489                 int absHours = Math.abs((totalSecs / 3600) % 100);  // anything larger than 99 silently dropped
   3490                 int absMinutes = Math.abs((totalSecs / 60) % 60);
   3491                 int absSeconds = Math.abs(totalSecs % 60);
   3492                 buf.append(totalSecs < 0 ? "-" : "+");
   3493                 if (style == TextStyle.FULL) {
   3494                     appendHMS(buf, absHours);
   3495                     buf.append(':');
   3496                     appendHMS(buf, absMinutes);
   3497                     if (absSeconds != 0) {
   3498                        buf.append(':');
   3499                        appendHMS(buf, absSeconds);
   3500                     }
   3501                 } else {
   3502                     if (absHours >= 10) {
   3503                         buf.append((char)(absHours / 10 + '0'));
   3504                     }
   3505                     buf.append((char)(absHours % 10 + '0'));
   3506                     if (absMinutes != 0 || absSeconds != 0) {
   3507                         buf.append(':');
   3508                         appendHMS(buf, absMinutes);
   3509                         if (absSeconds != 0) {
   3510                             buf.append(':');
   3511                             appendHMS(buf, absSeconds);
   3512                         }
   3513                     }
   3514                 }
   3515             }
   3516             return true;
   3517         }
   3518 
   3519         int getDigit(CharSequence text, int position) {
   3520             char c = text.charAt(position);
   3521             if (c < '0' || c > '9') {
   3522                 return -1;
   3523             }
   3524             return c - '0';
   3525         }
   3526 
   3527         @Override
   3528         public int parse(DateTimeParseContext context, CharSequence text, int position) {
   3529             int pos = position;
   3530             int end = pos + text.length();
   3531             String gmtText = "GMT";  // TODO: get localized version of 'GMT'
   3532             if (gmtText != null) {
   3533                 if (!context.subSequenceEquals(text, pos, gmtText, 0, gmtText.length())) {
   3534                     return ~position;
   3535                 }
   3536                 pos += gmtText.length();
   3537             }
   3538             // parse normal plus/minus offset
   3539             int negative = 0;
   3540             if (pos == end) {
   3541                 return context.setParsedField(OFFSET_SECONDS, 0, position, pos);
   3542             }
   3543             char sign = text.charAt(pos);  // IOOBE if invalid position
   3544             if (sign == '+') {
   3545                 negative = 1;
   3546             } else if (sign == '-') {
   3547                 negative = -1;
   3548             } else {
   3549                 return context.setParsedField(OFFSET_SECONDS, 0, position, pos);
   3550             }
   3551             pos++;
   3552             int h = 0;
   3553             int m = 0;
   3554             int s = 0;
   3555             if (style == TextStyle.FULL) {
   3556                 int h1 = getDigit(text, pos++);
   3557                 int h2 = getDigit(text, pos++);
   3558                 if (h1 < 0 || h2 < 0 || text.charAt(pos++) != ':') {
   3559                     return ~position;
   3560                 }
   3561                 h = h1 * 10 + h2;
   3562                 int m1 = getDigit(text, pos++);
   3563                 int m2 = getDigit(text, pos++);
   3564                 if (m1 < 0 || m2 < 0) {
   3565                     return ~position;
   3566                 }
   3567                 m = m1 * 10 + m2;
   3568                 if (pos + 2 < end && text.charAt(pos) == ':') {
   3569                     int s1 = getDigit(text, pos + 1);
   3570                     int s2 = getDigit(text, pos + 2);
   3571                     if (s1 >= 0 && s2 >= 0) {
   3572                         s = s1 * 10 + s2;
   3573                         pos += 3;
   3574                     }
   3575                 }
   3576             } else {
   3577                 h = getDigit(text, pos++);
   3578                 if (h < 0) {
   3579                     return ~position;
   3580                 }
   3581                 if (pos < end) {
   3582                     int h2 = getDigit(text, pos);
   3583                     if (h2 >=0) {
   3584                         h = h * 10 + h2;
   3585                         pos++;
   3586                     }
   3587                     if (pos + 2 < end && text.charAt(pos) == ':') {
   3588                         if (pos + 2 < end && text.charAt(pos) == ':') {
   3589                             int m1 = getDigit(text, pos + 1);
   3590                             int m2 = getDigit(text, pos + 2);
   3591                             if (m1 >= 0 && m2 >= 0) {
   3592                                 m = m1 * 10 + m2;
   3593                                 pos += 3;
   3594                                 if (pos + 2 < end && text.charAt(pos) == ':') {
   3595                                     int s1 = getDigit(text, pos + 1);
   3596                                     int s2 = getDigit(text, pos + 2);
   3597                                     if (s1 >= 0 && s2 >= 0) {
   3598                                         s = s1 * 10 + s2;
   3599                                         pos += 3;
   3600                                    }
   3601                                 }
   3602                             }
   3603                         }
   3604                     }
   3605                 }
   3606             }
   3607             long offsetSecs = negative * (h * 3600L + m * 60L + s);
   3608             return context.setParsedField(OFFSET_SECONDS, offsetSecs, position, pos);
   3609         }
   3610 
   3611         @Override
   3612         public String toString() {
   3613             return "LocalizedOffset(" + style + ")";
   3614         }
   3615     }
   3616 
   3617     //-----------------------------------------------------------------------
   3618     /**
   3619      * Prints or parses a zone ID.
   3620      */
   3621     static final class ZoneTextPrinterParser extends ZoneIdPrinterParser {
   3622 
   3623         /** The text style to output. */
   3624         private final TextStyle textStyle;
   3625 
   3626         /** The preferred zoneid map */
   3627         private Set<String> preferredZones;
   3628 
   3629         ZoneTextPrinterParser(TextStyle textStyle, Set<ZoneId> preferredZones) {
   3630             super(TemporalQueries.zone(), "ZoneText(" + textStyle + ")");
   3631             this.textStyle = Objects.requireNonNull(textStyle, "textStyle");
   3632             if (preferredZones != null && preferredZones.size() != 0) {
   3633                 this.preferredZones = new HashSet<>();
   3634                 for (ZoneId id : preferredZones) {
   3635                     this.preferredZones.add(id.getId());
   3636                 }
   3637             }
   3638         }
   3639 
   3640         private static final int STD = 0;
   3641         private static final int DST = 1;
   3642         private static final int GENERIC = 2;
   3643 
   3644         // Android-changed: List of types used by getDisplayName().
   3645         private static final TimeZoneNames.NameType[] TYPES = new TimeZoneNames.NameType[] {
   3646                 TimeZoneNames.NameType.LONG_STANDARD,
   3647                 TimeZoneNames.NameType.SHORT_STANDARD,
   3648                 TimeZoneNames.NameType.LONG_DAYLIGHT,
   3649                 TimeZoneNames.NameType.SHORT_DAYLIGHT,
   3650                 TimeZoneNames.NameType.LONG_GENERIC,
   3651                 TimeZoneNames.NameType.SHORT_GENERIC,
   3652         };
   3653 
   3654         private static final TimeZoneNames.NameType[] FULL_TYPES = new TimeZoneNames.NameType[] {
   3655                 TimeZoneNames.NameType.LONG_STANDARD,
   3656                 TimeZoneNames.NameType.LONG_DAYLIGHT,
   3657                 TimeZoneNames.NameType.LONG_GENERIC,
   3658         };
   3659 
   3660         private static final TimeZoneNames.NameType[] SHORT_TYPES = new TimeZoneNames.NameType[] {
   3661                 TimeZoneNames.NameType.SHORT_STANDARD,
   3662                 TimeZoneNames.NameType.SHORT_DAYLIGHT,
   3663                 TimeZoneNames.NameType.SHORT_GENERIC,
   3664         };
   3665 
   3666         private static final Map<String, SoftReference<Map<Locale, String[]>>> cache =
   3667             new ConcurrentHashMap<>();
   3668 
   3669         private String getDisplayName(String id, int type, Locale locale) {
   3670             if (textStyle == TextStyle.NARROW) {
   3671                 return null;
   3672             }
   3673             String[] names;
   3674             SoftReference<Map<Locale, String[]>> ref = cache.get(id);
   3675             Map<Locale, String[]> perLocale = null;
   3676             if (ref == null || (perLocale = ref.get()) == null ||
   3677                     (names = perLocale.get(locale)) == null) {
   3678                 // Android-changed: use ICU TimeZoneNames instead of TimeZoneNameUtility.
   3679                 TimeZoneNames timeZoneNames = TimeZoneNames.getInstance(locale);
   3680                 names = new String[TYPES.length + 1];
   3681                 // Zeroth index used for id, other indexes based on NameType constant + 1.
   3682                 names[0] = id;
   3683                 String canonicalId = ZoneMeta.getCanonicalCLDRID(id);
   3684                 timeZoneNames.getDisplayNames(canonicalId, TYPES, System.currentTimeMillis(),
   3685                         /* dest */ names, /* destoffset */ 1);
   3686                 if (names == null) {
   3687                     return null;
   3688                 }
   3689                 if (names[1] == null || names[2] == null || names[3] == null || names[4] == null) {
   3690                     // Use "GMT+XX:XX" analogous to java.util.TimeZone.getDisplayName()
   3691                     TimeZone tz = TimeZone.getTimeZone(id);
   3692                     String stdString = TimeZone.createGmtOffsetString(
   3693                             /* includeGmt */ true, /* includeMinuteSeparator */ true,
   3694                             tz.getRawOffset());
   3695                     String dstString = TimeZone.createGmtOffsetString(
   3696                             /* includeGmt */ true, /* includeMinuteSeparator */ true,
   3697                             tz.getRawOffset() + tz.getDSTSavings());
   3698                     names[1] = names[1] != null ? names[1] : stdString;
   3699                     names[2] = names[2] != null ? names[2] : stdString;
   3700                     names[3] = names[3] != null ? names[3] : dstString;
   3701                     names[4] = names[4] != null ? names[4] : dstString;
   3702                 }
   3703                 if (names[5] == null) {
   3704                     names[5] = names[0]; // use the id
   3705                 }
   3706                 if (names[6] == null) {
   3707                     names[6] = names[0];
   3708                 }
   3709                 if (perLocale == null) {
   3710                     perLocale = new ConcurrentHashMap<>();
   3711                 }
   3712                 perLocale.put(locale, names);
   3713                 cache.put(id, new SoftReference<>(perLocale));
   3714             }
   3715             switch (type) {
   3716             case STD:
   3717                 return names[textStyle.zoneNameStyleIndex() + 1];
   3718             case DST:
   3719                 return names[textStyle.zoneNameStyleIndex() + 3];
   3720             }
   3721             return names[textStyle.zoneNameStyleIndex() + 5];
   3722         }
   3723 
   3724         @Override
   3725         public boolean format(DateTimePrintContext context, StringBuilder buf) {
   3726             ZoneId zone = context.getValue(TemporalQueries.zoneId());
   3727             if (zone == null) {
   3728                 return false;
   3729             }
   3730             String zname = zone.getId();
   3731             if (!(zone instanceof ZoneOffset)) {
   3732                 TemporalAccessor dt = context.getTemporal();
   3733                 String name = getDisplayName(zname,
   3734                                              dt.isSupported(ChronoField.INSTANT_SECONDS)
   3735                                              ? (zone.getRules().isDaylightSavings(Instant.from(dt)) ? DST : STD)
   3736                                              : GENERIC,
   3737                                              context.getLocale());
   3738                 if (name != null) {
   3739                     zname = name;
   3740                 }
   3741             }
   3742             buf.append(zname);
   3743             return true;
   3744         }
   3745 
   3746         // cache per instance for now
   3747         private final Map<Locale, Entry<Integer, SoftReference<PrefixTree>>>
   3748             cachedTree = new HashMap<>();
   3749         private final Map<Locale, Entry<Integer, SoftReference<PrefixTree>>>
   3750             cachedTreeCI = new HashMap<>();
   3751 
   3752         @Override
   3753         protected PrefixTree getTree(DateTimeParseContext context) {
   3754             if (textStyle == TextStyle.NARROW) {
   3755                 return super.getTree(context);
   3756             }
   3757             Locale locale = context.getLocale();
   3758             boolean isCaseSensitive = context.isCaseSensitive();
   3759             Set<String> regionIds = ZoneRulesProvider.getAvailableZoneIds();
   3760             int regionIdsSize = regionIds.size();
   3761 
   3762             Map<Locale, Entry<Integer, SoftReference<PrefixTree>>> cached =
   3763                 isCaseSensitive ? cachedTree : cachedTreeCI;
   3764 
   3765             Entry<Integer, SoftReference<PrefixTree>> entry;
   3766             PrefixTree tree;
   3767             if ((entry = cached.get(locale)) == null ||
   3768                 (entry.getKey() != regionIdsSize ||
   3769                 (tree = entry.getValue().get()) == null)) {
   3770                 tree = PrefixTree.newTree(context);
   3771                 // Android-changed: use ICU TimeZoneNames to get Zone names.
   3772                 TimeZoneNames timeZoneNames = TimeZoneNames.getInstance(locale);
   3773                 long now = System.currentTimeMillis();
   3774                 TimeZoneNames.NameType[] types =
   3775                         textStyle == TextStyle.FULL ? FULL_TYPES : SHORT_TYPES;
   3776                 String[] names = new String[types.length];
   3777                 for (String zid : regionIds) {
   3778                     tree.add(zid, zid);    // don't convert zid -> metazone
   3779                     zid = ZoneName.toZid(zid, locale);
   3780                     timeZoneNames.getDisplayNames(zid, types, now, names, 0);
   3781                     for (int i = 0; i < names.length; i++) {
   3782                         if (names[i] != null) {
   3783                             tree.add(names[i], zid);
   3784                         }
   3785                     }
   3786                 }
   3787                 // if we have a set of preferred zones, need a copy and
   3788                 // add the preferred zones again to overwrite
   3789                 if (preferredZones != null) {
   3790                     for (String zid : regionIds) {
   3791                         if (!preferredZones.contains(zid)) {
   3792                             continue;
   3793                         }
   3794                         String canonicalId = ZoneName.toZid(zid, locale);
   3795                         timeZoneNames.getDisplayNames(canonicalId, types, now, names, 0);
   3796                         for (int i = 0; i < names.length; i++) {
   3797                             if (names[i] != null) {
   3798                                 tree.add(names[i], zid);
   3799                             }
   3800                         }
   3801                     }
   3802                 }
   3803                 cached.put(locale, new SimpleImmutableEntry<>(regionIdsSize, new SoftReference<>(tree)));
   3804             }
   3805             return tree;
   3806         }
   3807     }
   3808 
   3809     //-----------------------------------------------------------------------
   3810     /**
   3811      * Prints or parses a zone ID.
   3812      */
   3813     static class ZoneIdPrinterParser implements DateTimePrinterParser {
   3814         private final TemporalQuery<ZoneId> query;
   3815         private final String description;
   3816 
   3817         ZoneIdPrinterParser(TemporalQuery<ZoneId> query, String description) {
   3818             this.query = query;
   3819             this.description = description;
   3820         }
   3821 
   3822         @Override
   3823         public boolean format(DateTimePrintContext context, StringBuilder buf) {
   3824             ZoneId zone = context.getValue(query);
   3825             if (zone == null) {
   3826                 return false;
   3827             }
   3828             buf.append(zone.getId());
   3829             return true;
   3830         }
   3831 
   3832         /**
   3833          * The cached tree to speed up parsing.
   3834          */
   3835         private static volatile Entry<Integer, PrefixTree> cachedPrefixTree;
   3836         private static volatile Entry<Integer, PrefixTree> cachedPrefixTreeCI;
   3837 
   3838         protected PrefixTree getTree(DateTimeParseContext context) {
   3839             // prepare parse tree
   3840             Set<String> regionIds = ZoneRulesProvider.getAvailableZoneIds();
   3841             final int regionIdsSize = regionIds.size();
   3842             Entry<Integer, PrefixTree> cached = context.isCaseSensitive()
   3843                                                 ? cachedPrefixTree : cachedPrefixTreeCI;
   3844             if (cached == null || cached.getKey() != regionIdsSize) {
   3845                 synchronized (this) {
   3846                     cached = context.isCaseSensitive() ? cachedPrefixTree : cachedPrefixTreeCI;
   3847                     if (cached == null || cached.getKey() != regionIdsSize) {
   3848                         cached = new SimpleImmutableEntry<>(regionIdsSize, PrefixTree.newTree(regionIds, context));
   3849                         if (context.isCaseSensitive()) {
   3850                             cachedPrefixTree = cached;
   3851                         } else {
   3852                             cachedPrefixTreeCI = cached;
   3853                         }
   3854                     }
   3855                 }
   3856             }
   3857             return cached.getValue();
   3858         }
   3859 
   3860         /**
   3861          * This implementation looks for the longest matching string.
   3862          * For example, parsing Etc/GMT-2 will return Etc/GMC-2 rather than just
   3863          * Etc/GMC although both are valid.
   3864          */
   3865         @Override
   3866         public int parse(DateTimeParseContext context, CharSequence text, int position) {
   3867             int length = text.length();
   3868             if (position > length) {
   3869                 throw new IndexOutOfBoundsException();
   3870             }
   3871             if (position == length) {
   3872                 return ~position;
   3873             }
   3874 
   3875             // handle fixed time-zone IDs
   3876             char nextChar = text.charAt(position);
   3877             if (nextChar == '+' || nextChar == '-') {
   3878                 return parseOffsetBased(context, text, position, position, OffsetIdPrinterParser.INSTANCE_ID_Z);
   3879             } else if (length >= position + 2) {
   3880                 char nextNextChar = text.charAt(position + 1);
   3881                 if (context.charEquals(nextChar, 'U') && context.charEquals(nextNextChar, 'T')) {
   3882                     if (length >= position + 3 && context.charEquals(text.charAt(position + 2), 'C')) {
   3883                         return parseOffsetBased(context, text, position, position + 3, OffsetIdPrinterParser.INSTANCE_ID_ZERO);
   3884                     }
   3885                     return parseOffsetBased(context, text, position, position + 2, OffsetIdPrinterParser.INSTANCE_ID_ZERO);
   3886                 } else if (context.charEquals(nextChar, 'G') && length >= position + 3 &&
   3887                         context.charEquals(nextNextChar, 'M') && context.charEquals(text.charAt(position + 2), 'T')) {
   3888                     return parseOffsetBased(context, text, position, position + 3, OffsetIdPrinterParser.INSTANCE_ID_ZERO);
   3889                 }
   3890             }
   3891 
   3892             // parse
   3893             PrefixTree tree = getTree(context);
   3894             ParsePosition ppos = new ParsePosition(position);
   3895             String parsedZoneId = tree.match(text, ppos);
   3896             if (parsedZoneId == null) {
   3897                 if (context.charEquals(nextChar, 'Z')) {
   3898                     context.setParsed(ZoneOffset.UTC);
   3899                     return position + 1;
   3900                 }
   3901                 return ~position;
   3902             }
   3903             context.setParsed(ZoneId.of(parsedZoneId));
   3904             return ppos.getIndex();
   3905         }
   3906 
   3907         /**
   3908          * Parse an offset following a prefix and set the ZoneId if it is valid.
   3909          * To matching the parsing of ZoneId.of the values are not normalized
   3910          * to ZoneOffsets.
   3911          *
   3912          * @param context the parse context
   3913          * @param text the input text
   3914          * @param prefixPos start of the prefix
   3915          * @param position start of text after the prefix
   3916          * @param parser parser for the value after the prefix
   3917          * @return the position after the parse
   3918          */
   3919         private int parseOffsetBased(DateTimeParseContext context, CharSequence text, int prefixPos, int position, OffsetIdPrinterParser parser) {
   3920             String prefix = text.toString().substring(prefixPos, position).toUpperCase();
   3921             if (position >= text.length()) {
   3922                 context.setParsed(ZoneId.of(prefix));
   3923                 return position;
   3924             }
   3925 
   3926             // Android-changed: "GMT0" is considered a valid ZoneId.
   3927             if (text.charAt(position) == '0' && prefix.equals("GMT")) {
   3928                 context.setParsed(ZoneId.of("GMT0"));
   3929                 return position + 1;
   3930             }
   3931 
   3932             // '0' or 'Z' after prefix is not part of a valid ZoneId; use bare prefix
   3933             if (text.charAt(position) == '0' ||
   3934                 context.charEquals(text.charAt(position), 'Z')) {
   3935                 context.setParsed(ZoneId.of(prefix));
   3936                 return position;
   3937             }
   3938 
   3939             DateTimeParseContext newContext = context.copy();
   3940             int endPos = parser.parse(newContext, text, position);
   3941             try {
   3942                 if (endPos < 0) {
   3943                     if (parser == OffsetIdPrinterParser.INSTANCE_ID_Z) {
   3944                         return ~prefixPos;
   3945                     }
   3946                     context.setParsed(ZoneId.of(prefix));
   3947                     return position;
   3948                 }
   3949                 int offset = (int) newContext.getParsed(OFFSET_SECONDS).longValue();
   3950                 ZoneOffset zoneOffset = ZoneOffset.ofTotalSeconds(offset);
   3951                 context.setParsed(ZoneId.ofOffset(prefix, zoneOffset));
   3952                 return endPos;
   3953             } catch (DateTimeException dte) {
   3954                 return ~prefixPos;
   3955             }
   3956         }
   3957 
   3958         @Override
   3959         public String toString() {
   3960             return description;
   3961         }
   3962     }
   3963 
   3964     //-----------------------------------------------------------------------
   3965     /**
   3966      * A String based prefix tree for parsing time-zone names.
   3967      */
   3968     static class PrefixTree {
   3969         protected String key;
   3970         protected String value;
   3971         protected char c0;    // performance optimization to avoid the
   3972                               // boundary check cost of key.charat(0)
   3973         protected PrefixTree child;
   3974         protected PrefixTree sibling;
   3975 
   3976         private PrefixTree(String k, String v, PrefixTree child) {
   3977             this.key = k;
   3978             this.value = v;
   3979             this.child = child;
   3980             if (k.length() == 0){
   3981                 c0 = 0xffff;
   3982             } else {
   3983                 c0 = key.charAt(0);
   3984             }
   3985         }
   3986 
   3987         /**
   3988          * Creates a new prefix parsing tree based on parse context.
   3989          *
   3990          * @param context  the parse context
   3991          * @return the tree, not null
   3992          */
   3993         public static PrefixTree newTree(DateTimeParseContext context) {
   3994             //if (!context.isStrict()) {
   3995             //    return new LENIENT("", null, null);
   3996             //}
   3997             if (context.isCaseSensitive()) {
   3998                 return new PrefixTree("", null, null);
   3999             }
   4000             return new CI("", null, null);
   4001         }
   4002 
   4003         /**
   4004          * Creates a new prefix parsing tree.
   4005          *
   4006          * @param keys  a set of strings to build the prefix parsing tree, not null
   4007          * @param context  the parse context
   4008          * @return the tree, not null
   4009          */
   4010         public static  PrefixTree newTree(Set<String> keys, DateTimeParseContext context) {
   4011             PrefixTree tree = newTree(context);
   4012             for (String k : keys) {
   4013                 tree.add0(k, k);
   4014             }
   4015             return tree;
   4016         }
   4017 
   4018         /**
   4019          * Clone a copy of this tree
   4020          */
   4021         public PrefixTree copyTree() {
   4022             PrefixTree copy = new PrefixTree(key, value, null);
   4023             if (child != null) {
   4024                 copy.child = child.copyTree();
   4025             }
   4026             if (sibling != null) {
   4027                 copy.sibling = sibling.copyTree();
   4028             }
   4029             return copy;
   4030         }
   4031 
   4032 
   4033         /**
   4034          * Adds a pair of {key, value} into the prefix tree.
   4035          *
   4036          * @param k  the key, not null
   4037          * @param v  the value, not null
   4038          * @return  true if the pair is added successfully
   4039          */
   4040         public boolean add(String k, String v) {
   4041             return add0(k, v);
   4042         }
   4043 
   4044         private boolean add0(String k, String v) {
   4045             k = toKey(k);
   4046             int prefixLen = prefixLength(k);
   4047             if (prefixLen == key.length()) {
   4048                 if (prefixLen < k.length()) {  // down the tree
   4049                     String subKey = k.substring(prefixLen);
   4050                     PrefixTree c = child;
   4051                     while (c != null) {
   4052                         if (isEqual(c.c0, subKey.charAt(0))) {
   4053                             return c.add0(subKey, v);
   4054                         }
   4055                         c = c.sibling;
   4056                     }
   4057                     // add the node as the child of the current node
   4058                     c = newNode(subKey, v, null);
   4059                     c.sibling = child;
   4060                     child = c;
   4061                     return true;
   4062                 }
   4063                 // have an existing <key, value> already, overwrite it
   4064                 // if (value != null) {
   4065                 //    return false;
   4066                 //}
   4067                 value = v;
   4068                 return true;
   4069             }
   4070             // split the existing node
   4071             PrefixTree n1 = newNode(key.substring(prefixLen), value, child);
   4072             key = k.substring(0, prefixLen);
   4073             child = n1;
   4074             if (prefixLen < k.length()) {
   4075                 PrefixTree n2 = newNode(k.substring(prefixLen), v, null);
   4076                 child.sibling = n2;
   4077                 value = null;
   4078             } else {
   4079                 value = v;
   4080             }
   4081             return true;
   4082         }
   4083 
   4084         /**
   4085          * Match text with the prefix tree.
   4086          *
   4087          * @param text  the input text to parse, not null
   4088          * @param off  the offset position to start parsing at
   4089          * @param end  the end position to stop parsing
   4090          * @return the resulting string, or null if no match found.
   4091          */
   4092         public String match(CharSequence text, int off, int end) {
   4093             if (!prefixOf(text, off, end)){
   4094                 return null;
   4095             }
   4096             if (child != null && (off += key.length()) != end) {
   4097                 PrefixTree c = child;
   4098                 do {
   4099                     if (isEqual(c.c0, text.charAt(off))) {
   4100                         String found = c.match(text, off, end);
   4101                         if (found != null) {
   4102                             return found;
   4103                         }
   4104                         return value;
   4105                     }
   4106                     c = c.sibling;
   4107                 } while (c != null);
   4108             }
   4109             return value;
   4110         }
   4111 
   4112         /**
   4113          * Match text with the prefix tree.
   4114          *
   4115          * @param text  the input text to parse, not null
   4116          * @param pos  the position to start parsing at, from 0 to the text
   4117          *  length. Upon return, position will be updated to the new parse
   4118          *  position, or unchanged, if no match found.
   4119          * @return the resulting string, or null if no match found.
   4120          */
   4121         public String match(CharSequence text, ParsePosition pos) {
   4122             int off = pos.getIndex();
   4123             int end = text.length();
   4124             if (!prefixOf(text, off, end)){
   4125                 return null;
   4126             }
   4127             off += key.length();
   4128             if (child != null && off != end) {
   4129                 PrefixTree c = child;
   4130                 do {
   4131                     if (isEqual(c.c0, text.charAt(off))) {
   4132                         pos.setIndex(off);
   4133                         String found = c.match(text, pos);
   4134                         if (found != null) {
   4135                             return found;
   4136                         }
   4137                         break;
   4138                     }
   4139                     c = c.sibling;
   4140                 } while (c != null);
   4141             }
   4142             pos.setIndex(off);
   4143             return value;
   4144         }
   4145 
   4146         protected String toKey(String k) {
   4147             return k;
   4148         }
   4149 
   4150         protected PrefixTree newNode(String k, String v, PrefixTree child) {
   4151             return new PrefixTree(k, v, child);
   4152         }
   4153 
   4154         protected boolean isEqual(char c1, char c2) {
   4155             return c1 == c2;
   4156         }
   4157 
   4158         protected boolean prefixOf(CharSequence text, int off, int end) {
   4159             if (text instanceof String) {
   4160                 return ((String)text).startsWith(key, off);
   4161             }
   4162             int len = key.length();
   4163             if (len > end - off) {
   4164                 return false;
   4165             }
   4166             int off0 = 0;
   4167             while (len-- > 0) {
   4168                 if (!isEqual(key.charAt(off0++), text.charAt(off++))) {
   4169                     return false;
   4170                 }
   4171             }
   4172             return true;
   4173         }
   4174 
   4175         private int prefixLength(String k) {
   4176             int off = 0;
   4177             while (off < k.length() && off < key.length()) {
   4178                 if (!isEqual(k.charAt(off), key.charAt(off))) {
   4179                     return off;
   4180                 }
   4181                 off++;
   4182             }
   4183             return off;
   4184         }
   4185 
   4186         /**
   4187          * Case Insensitive prefix tree.
   4188          */
   4189         private static class CI extends PrefixTree {
   4190 
   4191             private CI(String k, String v, PrefixTree child) {
   4192                 super(k, v, child);
   4193             }
   4194 
   4195             @Override
   4196             protected CI newNode(String k, String v, PrefixTree child) {
   4197                 return new CI(k, v, child);
   4198             }
   4199 
   4200             @Override
   4201             protected boolean isEqual(char c1, char c2) {
   4202                 return DateTimeParseContext.charEqualsIgnoreCase(c1, c2);
   4203             }
   4204 
   4205             @Override
   4206             protected boolean prefixOf(CharSequence text, int off, int end) {
   4207                 int len = key.length();
   4208                 if (len > end - off) {
   4209                     return false;
   4210                 }
   4211                 int off0 = 0;
   4212                 while (len-- > 0) {
   4213                     if (!isEqual(key.charAt(off0++), text.charAt(off++))) {
   4214                         return false;
   4215                     }
   4216                 }
   4217                 return true;
   4218             }
   4219         }
   4220 
   4221         /**
   4222          * Lenient prefix tree. Case insensitive and ignores characters
   4223          * like space, underscore and slash.
   4224          */
   4225         private static class LENIENT extends CI {
   4226 
   4227             private LENIENT(String k, String v, PrefixTree child) {
   4228                 super(k, v, child);
   4229             }
   4230 
   4231             @Override
   4232             protected CI newNode(String k, String v, PrefixTree child) {
   4233                 return new LENIENT(k, v, child);
   4234             }
   4235 
   4236             private boolean isLenientChar(char c) {
   4237                 return c == ' ' || c == '_' || c == '/';
   4238             }
   4239 
   4240             protected String toKey(String k) {
   4241                 for (int i = 0; i < k.length(); i++) {
   4242                     if (isLenientChar(k.charAt(i))) {
   4243                         StringBuilder sb = new StringBuilder(k.length());
   4244                         sb.append(k, 0, i);
   4245                         i++;
   4246                         while (i < k.length()) {
   4247                             if (!isLenientChar(k.charAt(i))) {
   4248                                 sb.append(k.charAt(i));
   4249                             }
   4250                             i++;
   4251                         }
   4252                         return sb.toString();
   4253                     }
   4254                 }
   4255                 return k;
   4256             }
   4257 
   4258             @Override
   4259             public String match(CharSequence text, ParsePosition pos) {
   4260                 int off = pos.getIndex();
   4261                 int end = text.length();
   4262                 int len = key.length();
   4263                 int koff = 0;
   4264                 while (koff < len && off < end) {
   4265                     if (isLenientChar(text.charAt(off))) {
   4266                         off++;
   4267                         continue;
   4268                     }
   4269                     if (!isEqual(key.charAt(koff++), text.charAt(off++))) {
   4270                         return null;
   4271                     }
   4272                 }
   4273                 if (koff != len) {
   4274                     return null;
   4275                 }
   4276                 if (child != null && off != end) {
   4277                     int off0 = off;
   4278                     while (off0 < end && isLenientChar(text.charAt(off0))) {
   4279                         off0++;
   4280                     }
   4281                     if (off0 < end) {
   4282                         PrefixTree c = child;
   4283                         do {
   4284                             if (isEqual(c.c0, text.charAt(off0))) {
   4285                                 pos.setIndex(off0);
   4286                                 String found = c.match(text, pos);
   4287                                 if (found != null) {
   4288                                     return found;
   4289                                 }
   4290                                 break;
   4291                             }
   4292                             c = c.sibling;
   4293                         } while (c != null);
   4294                     }
   4295                 }
   4296                 pos.setIndex(off);
   4297                 return value;
   4298             }
   4299         }
   4300     }
   4301 
   4302     //-----------------------------------------------------------------------
   4303     /**
   4304      * Prints or parses a chronology.
   4305      */
   4306     static final class ChronoPrinterParser implements DateTimePrinterParser {
   4307         /** The text style to output, null means the ID. */
   4308         private final TextStyle textStyle;
   4309 
   4310         ChronoPrinterParser(TextStyle textStyle) {
   4311             // validated by caller
   4312             this.textStyle = textStyle;
   4313         }
   4314 
   4315         @Override
   4316         public boolean format(DateTimePrintContext context, StringBuilder buf) {
   4317             Chronology chrono = context.getValue(TemporalQueries.chronology());
   4318             if (chrono == null) {
   4319                 return false;
   4320             }
   4321             if (textStyle == null) {
   4322                 buf.append(chrono.getId());
   4323             } else {
   4324                 buf.append(getChronologyName(chrono, context.getLocale()));
   4325             }
   4326             return true;
   4327         }
   4328 
   4329         @Override
   4330         public int parse(DateTimeParseContext context, CharSequence text, int position) {
   4331             // simple looping parser to find the chronology
   4332             if (position < 0 || position > text.length()) {
   4333                 throw new IndexOutOfBoundsException();
   4334             }
   4335             Set<Chronology> chronos = Chronology.getAvailableChronologies();
   4336             Chronology bestMatch = null;
   4337             int matchLen = -1;
   4338             for (Chronology chrono : chronos) {
   4339                 String name;
   4340                 if (textStyle == null) {
   4341                     name = chrono.getId();
   4342                 } else {
   4343                     name = getChronologyName(chrono, context.getLocale());
   4344                 }
   4345                 int nameLen = name.length();
   4346                 if (nameLen > matchLen && context.subSequenceEquals(text, position, name, 0, nameLen)) {
   4347                     bestMatch = chrono;
   4348                     matchLen = nameLen;
   4349                 }
   4350             }
   4351             if (bestMatch == null) {
   4352                 return ~position;
   4353             }
   4354             context.setParsed(bestMatch);
   4355             return position + matchLen;
   4356         }
   4357 
   4358         /**
   4359          * Returns the chronology name of the given chrono in the given locale
   4360          * if available, or the chronology Id otherwise. The regular ResourceBundle
   4361          * search path is used for looking up the chronology name.
   4362          *
   4363          * @param chrono  the chronology, not null
   4364          * @param locale  the locale, not null
   4365          * @return the chronology name of chrono in locale, or the id if no name is available
   4366          * @throws NullPointerException if chrono or locale is null
   4367          */
   4368         private String getChronologyName(Chronology chrono, Locale locale) {
   4369             // Android-changed: Use ICU LocaleDisplayNames.
   4370             LocaleDisplayNames displayNames = LocaleDisplayNames.getInstance(ULocale.forLocale(locale));
   4371             String name = displayNames.keyValueDisplayName("calendar", chrono.getCalendarType());
   4372             return name != null ? name : chrono.getId();
   4373         }
   4374     }
   4375 
   4376     //-----------------------------------------------------------------------
   4377     /**
   4378      * Prints or parses a localized pattern.
   4379      */
   4380     static final class LocalizedPrinterParser implements DateTimePrinterParser {
   4381         /** Cache of formatters. */
   4382         private static final ConcurrentMap<String, DateTimeFormatter> FORMATTER_CACHE = new ConcurrentHashMap<>(16, 0.75f, 2);
   4383 
   4384         private final FormatStyle dateStyle;
   4385         private final FormatStyle timeStyle;
   4386 
   4387         /**
   4388          * Constructor.
   4389          *
   4390          * @param dateStyle  the date style to use, may be null
   4391          * @param timeStyle  the time style to use, may be null
   4392          */
   4393         LocalizedPrinterParser(FormatStyle dateStyle, FormatStyle timeStyle) {
   4394             // validated by caller
   4395             this.dateStyle = dateStyle;
   4396             this.timeStyle = timeStyle;
   4397         }
   4398 
   4399         @Override
   4400         public boolean format(DateTimePrintContext context, StringBuilder buf) {
   4401             Chronology chrono = Chronology.from(context.getTemporal());
   4402             return formatter(context.getLocale(), chrono).toPrinterParser(false).format(context, buf);
   4403         }
   4404 
   4405         @Override
   4406         public int parse(DateTimeParseContext context, CharSequence text, int position) {
   4407             Chronology chrono = context.getEffectiveChronology();
   4408             return formatter(context.getLocale(), chrono).toPrinterParser(false).parse(context, text, position);
   4409         }
   4410 
   4411         /**
   4412          * Gets the formatter to use.
   4413          * <p>
   4414          * The formatter will be the most appropriate to use for the date and time style in the locale.
   4415          * For example, some locales will use the month name while others will use the number.
   4416          *
   4417          * @param locale  the locale to use, not null
   4418          * @param chrono  the chronology to use, not null
   4419          * @return the formatter, not null
   4420          * @throws IllegalArgumentException if the formatter cannot be found
   4421          */
   4422         private DateTimeFormatter formatter(Locale locale, Chronology chrono) {
   4423             String key = chrono.getId() + '|' + locale.toString() + '|' + dateStyle + timeStyle;
   4424             DateTimeFormatter formatter = FORMATTER_CACHE.get(key);
   4425             if (formatter == null) {
   4426                 String pattern = getLocalizedDateTimePattern(dateStyle, timeStyle, chrono, locale);
   4427                 formatter = new DateTimeFormatterBuilder().appendPattern(pattern).toFormatter(locale);
   4428                 DateTimeFormatter old = FORMATTER_CACHE.putIfAbsent(key, formatter);
   4429                 if (old != null) {
   4430                     formatter = old;
   4431                 }
   4432             }
   4433             return formatter;
   4434         }
   4435 
   4436         @Override
   4437         public String toString() {
   4438             return "Localized(" + (dateStyle != null ? dateStyle : "") + "," +
   4439                 (timeStyle != null ? timeStyle : "") + ")";
   4440         }
   4441     }
   4442 
   4443     //-----------------------------------------------------------------------
   4444     /**
   4445      * Prints or parses a localized pattern from a localized field.
   4446      * The specific formatter and parameters is not selected until the
   4447      * the field is to be printed or parsed.
   4448      * The locale is needed to select the proper WeekFields from which
   4449      * the field for day-of-week, week-of-month, or week-of-year is selected.
   4450      */
   4451     static final class WeekBasedFieldPrinterParser implements DateTimePrinterParser {
   4452         private char chr;
   4453         private int count;
   4454 
   4455         /**
   4456          * Constructor.
   4457          *
   4458          * @param chr the pattern format letter that added this PrinterParser.
   4459          * @param count the repeat count of the format letter
   4460          */
   4461         WeekBasedFieldPrinterParser(char chr, int count) {
   4462             this.chr = chr;
   4463             this.count = count;
   4464         }
   4465 
   4466         @Override
   4467         public boolean format(DateTimePrintContext context, StringBuilder buf) {
   4468             return printerParser(context.getLocale()).format(context, buf);
   4469         }
   4470 
   4471         @Override
   4472         public int parse(DateTimeParseContext context, CharSequence text, int position) {
   4473             return printerParser(context.getLocale()).parse(context, text, position);
   4474         }
   4475 
   4476         /**
   4477          * Gets the printerParser to use based on the field and the locale.
   4478          *
   4479          * @param locale  the locale to use, not null
   4480          * @return the formatter, not null
   4481          * @throws IllegalArgumentException if the formatter cannot be found
   4482          */
   4483         private DateTimePrinterParser printerParser(Locale locale) {
   4484             WeekFields weekDef = WeekFields.of(locale);
   4485             TemporalField field = null;
   4486             switch (chr) {
   4487                 case 'Y':
   4488                     field = weekDef.weekBasedYear();
   4489                     if (count == 2) {
   4490                         return new ReducedPrinterParser(field, 2, 2, 0, ReducedPrinterParser.BASE_DATE, 0);
   4491                     } else {
   4492                         return new NumberPrinterParser(field, count, 19,
   4493                                 (count < 4) ? SignStyle.NORMAL : SignStyle.EXCEEDS_PAD, -1);
   4494                     }
   4495                 case 'e':
   4496                 case 'c':
   4497                     field = weekDef.dayOfWeek();
   4498                     break;
   4499                 case 'w':
   4500                     field = weekDef.weekOfWeekBasedYear();
   4501                     break;
   4502                 case 'W':
   4503                     field = weekDef.weekOfMonth();
   4504                     break;
   4505                 default:
   4506                     throw new IllegalStateException("unreachable");
   4507             }
   4508             return new NumberPrinterParser(field, (count == 2 ? 2 : 1), 2, SignStyle.NOT_NEGATIVE);
   4509         }
   4510 
   4511         @Override
   4512         public String toString() {
   4513             StringBuilder sb = new StringBuilder(30);
   4514             sb.append("Localized(");
   4515             if (chr == 'Y') {
   4516                 if (count == 1) {
   4517                     sb.append("WeekBasedYear");
   4518                 } else if (count == 2) {
   4519                     sb.append("ReducedValue(WeekBasedYear,2,2,2000-01-01)");
   4520                 } else {
   4521                     sb.append("WeekBasedYear,").append(count).append(",")
   4522                             .append(19).append(",")
   4523                             .append((count < 4) ? SignStyle.NORMAL : SignStyle.EXCEEDS_PAD);
   4524                 }
   4525             } else {
   4526                 switch (chr) {
   4527                     case 'c':
   4528                     case 'e':
   4529                         sb.append("DayOfWeek");
   4530                         break;
   4531                     case 'w':
   4532                         sb.append("WeekOfWeekBasedYear");
   4533                         break;
   4534                     case 'W':
   4535                         sb.append("WeekOfMonth");
   4536                         break;
   4537                     default:
   4538                         break;
   4539                 }
   4540                 sb.append(",");
   4541                 sb.append(count);
   4542             }
   4543             sb.append(")");
   4544             return sb.toString();
   4545         }
   4546     }
   4547 
   4548     //-------------------------------------------------------------------------
   4549     /**
   4550      * Length comparator.
   4551      */
   4552     static final Comparator<String> LENGTH_SORT = new Comparator<String>() {
   4553         @Override
   4554         public int compare(String str1, String str2) {
   4555             return str1.length() == str2.length() ? str1.compareTo(str2) : str1.length() - str2.length();
   4556         }
   4557     };
   4558 }
   4559