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