001/*
002 * Copyright (c) 2007-2013, Stephen Colebourne & Michael Nascimento Santos
003 *
004 * All rights reserved.
005 *
006 * Redistribution and use in source and binary forms, with or without
007 * modification, are permitted provided that the following conditions are met:
008 *
009 *  * Redistributions of source code must retain the above copyright notice,
010 *    this list of conditions and the following disclaimer.
011 *
012 *  * Redistributions in binary form must reproduce the above copyright notice,
013 *    this list of conditions and the following disclaimer in the documentation
014 *    and/or other materials provided with the distribution.
015 *
016 *  * Neither the name of JSR-310 nor the names of its contributors
017 *    may be used to endorse or promote products derived from this software
018 *    without specific prior written permission.
019 *
020 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
021 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
022 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
023 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
024 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
025 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
026 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
027 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
028 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
029 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
030 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
031 */
032package org.threeten.bp.format;
033
034import static org.threeten.bp.temporal.ChronoField.DAY_OF_MONTH;
035import static org.threeten.bp.temporal.ChronoField.HOUR_OF_DAY;
036import static org.threeten.bp.temporal.ChronoField.INSTANT_SECONDS;
037import static org.threeten.bp.temporal.ChronoField.MINUTE_OF_HOUR;
038import static org.threeten.bp.temporal.ChronoField.MONTH_OF_YEAR;
039import static org.threeten.bp.temporal.ChronoField.NANO_OF_SECOND;
040import static org.threeten.bp.temporal.ChronoField.OFFSET_SECONDS;
041import static org.threeten.bp.temporal.ChronoField.SECOND_OF_MINUTE;
042import static org.threeten.bp.temporal.ChronoField.YEAR;
043
044import java.math.BigDecimal;
045import java.math.BigInteger;
046import java.math.RoundingMode;
047import java.util.AbstractMap.SimpleImmutableEntry;
048import java.util.ArrayList;
049import java.util.Collections;
050import java.util.Comparator;
051import java.util.HashMap;
052import java.util.Iterator;
053import java.util.LinkedHashMap;
054import java.util.List;
055import java.util.Locale;
056import java.util.Map;
057import java.util.Map.Entry;
058import java.util.Objects;
059import java.util.Set;
060
061import org.threeten.bp.DateTimeException;
062import org.threeten.bp.LocalDateTime;
063import org.threeten.bp.ZoneId;
064import org.threeten.bp.ZoneOffset;
065import org.threeten.bp.format.SimpleDateTimeTextProvider.LocaleStore;
066import org.threeten.bp.jdk8.Jdk8Methods;
067import org.threeten.bp.temporal.Chrono;
068import org.threeten.bp.temporal.ChronoField;
069import org.threeten.bp.temporal.ISOChrono;
070import org.threeten.bp.temporal.ISOFields;
071import org.threeten.bp.temporal.TemporalAccessor;
072import org.threeten.bp.temporal.TemporalField;
073import org.threeten.bp.temporal.TemporalQueries;
074import org.threeten.bp.temporal.TemporalQuery;
075import org.threeten.bp.temporal.ValueRange;
076import org.threeten.bp.zone.ZoneRulesProvider;
077
078/**
079 * Builder to create date-time formatters.
080 * <p>
081 * This allows a {@code DateTimeFormatter} to be created.
082 * All date-time formatters are created ultimately using this builder.
083 * <p>
084 * The basic elements of date-time can all be added:
085 * <p><ul>
086 * <li>Value - a numeric value</li>
087 * <li>Fraction - a fractional value including the decimal place. Always use this when
088 * outputting fractions to ensure that the fraction is parsed correctly</li>
089 * <li>Text - the textual equivalent for the value</li>
090 * <li>OffsetId/Offset - the {@linkplain ZoneOffset zone offset}</li>
091 * <li>ZoneId - the {@linkplain ZoneId time-zone} id</li>
092 * <li>ZoneText - the name of the time-zone</li>
093 * <li>Literal - a text literal</li>
094 * <li>Nested and Optional - formats can be nested or made optional</li>
095 * <li>Other - the printer and parser interfaces can be used to add user supplied formatting</li>
096 * </ul><p>
097 * In addition, any of the elements may be decorated by padding, either with spaces or any other character.
098 * <p>
099 * Finally, a shorthand pattern, mostly compatible with {@code java.text.SimpleDateFormat SimpleDateFormat}
100 * can be used, see {@link #appendPattern(String)}.
101 * In practice, this simply parses the pattern and calls other methods on the builder.
102 *
103 * <h3>Specification for implementors</h3>
104 * This class is a mutable builder intended for use from a single thread.
105 */
106public final class DateTimeFormatterBuilder {
107
108    /**
109     * Query for a time-zone that is region-only.
110     */
111    private static final TemporalQuery<ZoneId> QUERY_REGION_ONLY = new TemporalQuery<ZoneId>() {
112        public ZoneId queryFrom(TemporalAccessor temporal) {
113            ZoneId zone = temporal.query(TemporalQueries.zoneId());
114            return (zone != null && zone instanceof ZoneOffset == false ? zone : null);
115        }
116    };
117
118    /**
119     * The currently active builder, used by the outermost builder.
120     */
121    private DateTimeFormatterBuilder active = this;
122    /**
123     * The parent builder, null for the outermost builder.
124     */
125    private final DateTimeFormatterBuilder parent;
126    /**
127     * The list of printers that will be used.
128     */
129    private final List<DateTimePrinterParser> printerParsers = new ArrayList<>();
130    /**
131     * Whether this builder produces an optional formatter.
132     */
133    private final boolean optional;
134    /**
135     * The width to pad the next field to.
136     */
137    private int padNextWidth;
138    /**
139     * The character to pad the next field with.
140     */
141    private char padNextChar;
142    /**
143     * The index of the last variable width value parser.
144     */
145    private int valueParserIndex = -1;
146
147    /**
148     * Constructs a new instance of the builder.
149     */
150    public DateTimeFormatterBuilder() {
151        super();
152        parent = null;
153        optional = false;
154    }
155
156    /**
157     * Constructs a new instance of the builder.
158     *
159     * @param parent  the parent builder, not null
160     * @param optional  whether the formatter is optional, not null
161     */
162    private DateTimeFormatterBuilder(DateTimeFormatterBuilder parent, boolean optional) {
163        super();
164        this.parent = parent;
165        this.optional = optional;
166    }
167
168    //-----------------------------------------------------------------------
169    /**
170     * Changes the parse style to be case sensitive for the remainder of the formatter.
171     * <p>
172     * Parsing can be case sensitive or insensitive - by default it is case sensitive.
173     * This method allows the case sensitivity setting of parsing to be changed.
174     * <p>
175     * Calling this method changes the state of the builder such that all
176     * subsequent builder method calls will parse text in case sensitive mode.
177     * See {@link #parseCaseInsensitive} for the opposite setting.
178     * The parse case sensitive/insensitive methods may be called at any point
179     * in the builder, thus the parser can swap between case parsing modes
180     * multiple times during the parse.
181     * <p>
182     * Since the default is case sensitive, this method should only be used after
183     * a previous call to {@code #parseCaseInsensitive}.
184     *
185     * @return this, for chaining, not null
186     */
187    public DateTimeFormatterBuilder parseCaseSensitive() {
188        appendInternal(SettingsParser.SENSITIVE);
189        return this;
190    }
191
192    /**
193     * Changes the parse style to be case insensitive for the remainder of the formatter.
194     * <p>
195     * Parsing can be case sensitive or insensitive - by default it is case sensitive.
196     * This method allows the case sensitivity setting of parsing to be changed.
197     * <p>
198     * Calling this method changes the state of the builder such that all
199     * subsequent builder method calls will parse text in case sensitive mode.
200     * See {@link #parseCaseSensitive()} for the opposite setting.
201     * The parse case sensitive/insensitive methods may be called at any point
202     * in the builder, thus the parser can swap between case parsing modes
203     * multiple times during the parse.
204     *
205     * @return this, for chaining, not null
206     */
207    public DateTimeFormatterBuilder parseCaseInsensitive() {
208        appendInternal(SettingsParser.INSENSITIVE);
209        return this;
210    }
211
212    //-----------------------------------------------------------------------
213    /**
214     * Changes the parse style to be strict for the remainder of the formatter.
215     * <p>
216     * Parsing can be strict or lenient - by default its strict.
217     * This controls the degree of flexibility in matching the text and sign styles.
218     * <p>
219     * When used, this method changes the parsing to be strict from this point onwards.
220     * As strict is the default, this is normally only needed after calling {@link #parseLenient()}.
221     * The change will remain in force until the end of the formatter that is eventually
222     * constructed or until {@code parseLenient} is called.
223     *
224     * @return this, for chaining, not null
225     */
226    public DateTimeFormatterBuilder parseStrict() {
227        appendInternal(SettingsParser.STRICT);
228        return this;
229    }
230
231    /**
232     * Changes the parse style to be lenient for the remainder of the formatter.
233     * Note that case sensitivity is set separately to this method.
234     * <p>
235     * Parsing can be strict or lenient - by default its strict.
236     * This controls the degree of flexibility in matching the text and sign styles.
237     * Applications calling this method should typically also call {@link #parseCaseInsensitive()}.
238     * <p>
239     * When used, this method changes the parsing to be strict from this point onwards.
240     * The change will remain in force until the end of the formatter that is eventually
241     * constructed or until {@code parseStrict} is called.
242     *
243     * @return this, for chaining, not null
244     */
245    public DateTimeFormatterBuilder parseLenient() {
246        appendInternal(SettingsParser.LENIENT);
247        return this;
248    }
249
250    //-----------------------------------------------------------------------
251    /**
252     * Appends the value of a date-time field to the formatter using a normal
253     * output style.
254     * <p>
255     * The value of the field will be output during a print.
256     * If the value cannot be obtained then an exception will be thrown.
257     * <p>
258     * The value will be printed as per the normal print of an integer value.
259     * Only negative numbers will be signed. No padding will be added.
260     * <p>
261     * The parser for a variable width value such as this normally behaves greedily,
262     * requiring one digit, but accepting as many digits as possible.
263     * This behavior can be affected by 'adjacent value parsing'.
264     * See {@link #appendValue(TemporalField, int)} for full details.
265     *
266     * @param field  the field to append, not null
267     * @return this, for chaining, not null
268     */
269    public DateTimeFormatterBuilder appendValue(TemporalField field) {
270        Objects.requireNonNull(field, "field");
271        active.valueParserIndex = appendInternal(new NumberPrinterParser(field, 1, 19, SignStyle.NORMAL));
272        return this;
273    }
274
275    /**
276     * Appends the value of a date-time field to the formatter using a fixed
277     * width, zero-padded approach.
278     * <p>
279     * The value of the field will be output during a print.
280     * If the value cannot be obtained then an exception will be thrown.
281     * <p>
282     * The value will be zero-padded on the left. If the size of the value
283     * means that it cannot be printed within the width then an exception is thrown.
284     * If the value of the field is negative then an exception is thrown during printing.
285     * <p>
286     * This method supports a special technique of parsing known as 'adjacent value parsing'.
287     * This technique solves the problem where a variable length value is followed by one or more
288     * fixed length values. The standard parser is greedy, and thus it would normally
289     * steal the digits that are needed by the fixed width value parsers that follow the
290     * variable width one.
291     * <p>
292     * No action is required to initiate 'adjacent value parsing'.
293     * When a call to {@code appendValue} with a variable width is made, the builder
294     * enters adjacent value parsing setup mode. If the immediately subsequent method
295     * call or calls on the same builder are to this method, then the parser will reserve
296     * space so that the fixed width values can be parsed.
297     * <p>
298     * For example, consider {@code builder.appendValue(YEAR).appendValue(MONTH_OF_YEAR, 2);}
299     * The year is a variable width parse of between 1 and 19 digits.
300     * The month is a fixed width parse of 2 digits.
301     * Because these were appended to the same builder immediately after one another,
302     * the year parser will reserve two digits for the month to parse.
303     * Thus, the text '201106' will correctly parse to a year of 2011 and a month of 6.
304     * Without adjacent value parsing, the year would greedily parse all six digits and leave
305     * nothing for the month.
306     * <p>
307     * Adjacent value parsing applies to each set of fixed width not-negative values in the parser
308     * that immediately follow any kind of variable width value.
309     * Calling any other append method will end the setup of adjacent value parsing.
310     * Thus, in the unlikely event that you need to avoid adjacent value parsing behavior,
311     * simply add the {@code appendValue} to another {@code DateTimeFormatterBuilder}
312     * and add that to this builder.
313     * <p>
314     * If adjacent parsing is active, then parsing must match exactly the specified
315     * number of digits in both strict and lenient modes.
316     * In addition, no positive or negative sign is permitted.
317     *
318     * @param field  the field to append, not null
319     * @param width  the width of the printed field, from 1 to 19
320     * @return this, for chaining, not null
321     * @throws IllegalArgumentException if the width is invalid
322     */
323    public DateTimeFormatterBuilder appendValue(TemporalField field, int width) {
324        Objects.requireNonNull(field, "field");
325        if (width < 1 || width > 19) {
326            throw new IllegalArgumentException("The width must be from 1 to 19 inclusive but was " + width);
327        }
328        NumberPrinterParser pp = new NumberPrinterParser(field, width, width, SignStyle.NOT_NEGATIVE);
329        return appendFixedWidth(width, pp);
330    }
331
332    /**
333     * Appends the value of a date-time field to the formatter providing full
334     * control over printing.
335     * <p>
336     * The value of the field will be output during a print.
337     * If the value cannot be obtained then an exception will be thrown.
338     * <p>
339     * This method provides full control of the numeric formatting, including
340     * zero-padding and the positive/negative sign.
341     * <p>
342     * The parser for a variable width value such as this normally behaves greedily,
343     * accepting as many digits as possible.
344     * This behavior can be affected by 'adjacent value parsing'.
345     * See {@link #appendValue(TemporalField, int)} for full details.
346     * <p>
347     * In strict parsing mode, the minimum number of parsed digits is {@code minWidth}.
348     * In lenient parsing mode, the minimum number of parsed digits is one.
349     * <p>
350     * If this method is invoked with equal minimum and maximum widths and a sign style of
351     * {@code NOT_NEGATIVE} then it delegates to {@code appendValue(TemporalField,int)}.
352     * In this scenario, the printing and parsing behavior described there occur.
353     *
354     * @param field  the field to append, not null
355     * @param minWidth  the minimum field width of the printed field, from 1 to 19
356     * @param maxWidth  the maximum field width of the printed field, from 1 to 19
357     * @param signStyle  the positive/negative output style, not null
358     * @return this, for chaining, not null
359     * @throws IllegalArgumentException if the widths are invalid
360     */
361    public DateTimeFormatterBuilder appendValue(
362            TemporalField field, int minWidth, int maxWidth, SignStyle signStyle) {
363        if (minWidth == maxWidth && signStyle == SignStyle.NOT_NEGATIVE) {
364            return appendValue(field, maxWidth);
365        }
366        Objects.requireNonNull(field, "field");
367        Objects.requireNonNull(signStyle, "signStyle");
368        if (minWidth < 1 || minWidth > 19) {
369            throw new IllegalArgumentException("The minimum width must be from 1 to 19 inclusive but was " + minWidth);
370        }
371        if (maxWidth < 1 || maxWidth > 19) {
372            throw new IllegalArgumentException("The maximum width must be from 1 to 19 inclusive but was " + maxWidth);
373        }
374        if (maxWidth < minWidth) {
375            throw new IllegalArgumentException("The maximum width must exceed or equal the minimum width but " +
376                    maxWidth + " < " + minWidth);
377        }
378        NumberPrinterParser pp = new NumberPrinterParser(field, minWidth, maxWidth, signStyle);
379        if (minWidth == maxWidth) {
380            appendInternal(pp);
381        } else {
382            active.valueParserIndex = appendInternal(pp);
383        }
384        return this;
385    }
386
387    //-----------------------------------------------------------------------
388    /**
389     * Appends the reduced value of a date-time field to the formatter.
390     * <p>
391     * This is typically used for printing and parsing a two digit year.
392     * The {@code width} is the printed and parsed width.
393     * The {@code baseValue} is used during parsing to determine the valid range.
394     * <p>
395     * For printing, the width is used to determine the number of characters to print.
396     * The rightmost characters are output to match the width, left padding with zero.
397     * <p>
398     * For parsing, exactly the number of characters specified by the width are parsed.
399     * This is incomplete information however, so the base value is used to complete the parse.
400     * The base value is the first valid value in a range of ten to the power of width.
401     * <p>
402     * For example, a base value of {@code 1980} and a width of {@code 2} will have
403     * valid values from {@code 1980} to {@code 2079}.
404     * During parsing, the text {@code "12"} will result in the value {@code 2012} as that
405     * is the value within the range where the last two digits are "12".
406     * <p>
407     * This is a fixed width parser operating using 'adjacent value parsing'.
408     * See {@link #appendValue(TemporalField, int)} for full details.
409     *
410     * @param field  the field to append, not null
411     * @param width  the width of the printed and parsed field, from 1 to 18
412     * @param baseValue  the base value of the range of valid values
413     * @return this, for chaining, not null
414     * @throws IllegalArgumentException if the width or base value is invalid
415     */
416    public DateTimeFormatterBuilder appendValueReduced(
417            TemporalField field, int width, int baseValue) {
418        Objects.requireNonNull(field, "field");
419        ReducedPrinterParser pp = new ReducedPrinterParser(field, width, baseValue);
420        appendFixedWidth(width, pp);
421        return this;
422    }
423
424    /**
425     * Appends a fixed width printer-parser.
426     *
427     * @param width  the width
428     * @param pp  the printer-parser, not null
429     * @return this, for chaining, not null
430     */
431    private DateTimeFormatterBuilder appendFixedWidth(int width, NumberPrinterParser pp) {
432        if (active.valueParserIndex >= 0) {
433            // adjacent parsing mode, update setting in previous parsers
434            NumberPrinterParser basePP = (NumberPrinterParser) active.printerParsers.get(active.valueParserIndex);
435            basePP = basePP.withSubsequentWidth(width);
436            int activeValueParser = active.valueParserIndex;
437            active.printerParsers.set(active.valueParserIndex, basePP);
438            appendInternal(pp.withFixedWidth());
439            active.valueParserIndex = activeValueParser;
440        } else {
441            // not adjacent parsing
442            appendInternal(pp);
443        }
444        return this;
445    }
446
447    //-----------------------------------------------------------------------
448    /**
449     * Appends the fractional value of a date-time field to the formatter.
450     * <p>
451     * The fractional value of the field will be output including the
452     * preceding decimal point. The preceding value is not output.
453     * For example, the second-of-minute value of 15 would be output as {@code .25}.
454     * <p>
455     * The width of the printed fraction can be controlled. Setting the
456     * minimum width to zero will cause no output to be generated.
457     * The printed fraction will have the minimum width necessary between
458     * the minimum and maximum widths - trailing zeroes are omitted.
459     * No rounding occurs due to the maximum width - digits are simply dropped.
460     * <p>
461     * When parsing in strict mode, the number of parsed digits must be between
462     * the minimum and maximum width. When parsing in lenient mode, the minimum
463     * width is considered to be zero and the maximum is nine.
464     * <p>
465     * If the value cannot be obtained then an exception will be thrown.
466     * If the value is negative an exception will be thrown.
467     * If the field does not have a fixed set of valid values then an
468     * exception will be thrown.
469     * If the field value in the date-time to be printed is invalid it
470     * cannot be printed and an exception will be thrown.
471     *
472     * @param field  the field to append, not null
473     * @param minWidth  the minimum width of the field excluding the decimal point, from 0 to 9
474     * @param maxWidth  the maximum width of the field excluding the decimal point, from 1 to 9
475     * @param decimalPoint  whether to output the localized decimal point symbol
476     * @return this, for chaining, not null
477     * @throws IllegalArgumentException if the field has a variable set of valid values or
478     *  either width is invalid
479     */
480    public DateTimeFormatterBuilder appendFraction(
481            TemporalField field, int minWidth, int maxWidth, boolean decimalPoint) {
482        appendInternal(new FractionPrinterParser(field, minWidth, maxWidth, decimalPoint));
483        return this;
484    }
485
486    //-----------------------------------------------------------------------
487    /**
488     * Appends the text of a date-time field to the formatter using the full
489     * text style.
490     * <p>
491     * The text of the field will be output during a print.
492     * The value must be within the valid range of the field.
493     * If the value cannot be obtained then an exception will be thrown.
494     * If the field has no textual representation, then the numeric value will be used.
495     * <p>
496     * The value will be printed as per the normal print of an integer value.
497     * Only negative numbers will be signed. No padding will be added.
498     *
499     * @param field  the field to append, not null
500     * @return this, for chaining, not null
501     */
502    public DateTimeFormatterBuilder appendText(TemporalField field) {
503        return appendText(field, TextStyle.FULL);
504    }
505
506    /**
507     * Appends the text of a date-time field to the formatter.
508     * <p>
509     * The text of the field will be output during a print.
510     * The value must be within the valid range of the field.
511     * If the value cannot be obtained then an exception will be thrown.
512     * If the field has no textual representation, then the numeric value will be used.
513     * <p>
514     * The value will be printed as per the normal print of an integer value.
515     * Only negative numbers will be signed. No padding will be added.
516     *
517     * @param field  the field to append, not null
518     * @param textStyle  the text style to use, not null
519     * @return this, for chaining, not null
520     */
521    public DateTimeFormatterBuilder appendText(TemporalField field, TextStyle textStyle) {
522        Objects.requireNonNull(field, "field");
523        Objects.requireNonNull(textStyle, "textStyle");
524        appendInternal(new TextPrinterParser(field, textStyle, DateTimeTextProvider.getInstance()));
525        return this;
526    }
527
528    /**
529     * Appends the text of a date-time field to the formatter using the specified
530     * map to supply the text.
531     * <p>
532     * The standard text outputting methods use the localized text in the JDK.
533     * This method allows that text to be specified directly.
534     * The supplied map is not validated by the builder to ensure that printing or
535     * parsing is possible, thus an invalid map may throw an error during later use.
536     * <p>
537     * Supplying the map of text provides considerable flexibility in printing and parsing.
538     * For example, a legacy application might require or supply the months of the
539     * year as "JNY", "FBY", "MCH" etc. These do not match the standard set of text
540     * for localized month names. Using this method, a map can be created which
541     * defines the connection between each value and the text:
542     * <pre>
543     * Map&lt;Long, String&gt; map = new HashMap&lt;&gt;();
544     * map.put(1, "JNY");
545     * map.put(2, "FBY");
546     * map.put(3, "MCH");
547     * ...
548     * builder.appendText(MONTH_OF_YEAR, map);
549     * </pre>
550     * <p>
551     * Other uses might be to output the value with a suffix, such as "1st", "2nd", "3rd",
552     * or as Roman numerals "I", "II", "III", "IV".
553     * <p>
554     * During printing, the value is obtained and checked that it is in the valid range.
555     * If text is not available for the value then it is output as a number.
556     * During parsing, the parser will match against the map of text and numeric values.
557     *
558     * @param field  the field to append, not null
559     * @param textLookup  the map from the value to the text
560     * @return this, for chaining, not null
561     */
562    public DateTimeFormatterBuilder appendText(TemporalField field, Map<Long, String> textLookup) {
563        Objects.requireNonNull(field, "field");
564        Objects.requireNonNull(textLookup, "textLookup");
565        Map<Long, String> copy = new LinkedHashMap<>(textLookup);
566        Map<TextStyle, Map<Long, String>> map = Collections.singletonMap(TextStyle.FULL, copy);
567        final LocaleStore store = new LocaleStore(map);
568        DateTimeTextProvider provider = new DateTimeTextProvider() {
569            @Override
570            public String getText(TemporalField field, long value, TextStyle style, Locale locale) {
571                return store.getText(value, style);
572            }
573            @Override
574            public Iterator<Entry<String, Long>> getTextIterator(TemporalField field, TextStyle style, Locale locale) {
575                return store.getTextIterator(style);
576            }
577            @Override
578            public Locale[] getAvailableLocales() {
579                throw new UnsupportedOperationException();
580            }
581        };
582        appendInternal(new TextPrinterParser(field, TextStyle.FULL, provider));
583        return this;
584    }
585
586    //-----------------------------------------------------------------------
587    /**
588     * Appends an instant using ISO-8601 to the formatter.
589     * <p>
590     * Instants have a fixed output format.
591     * They are converted to a date-time with a zone-offset of UTC and printed
592     * using the standard ISO-8601 format.
593     * <p>
594     * An alternative to this method is to print/parse the instant as a single
595     * epoch-seconds value. That is achieved using {@code appendValue(INSTANT_SECONDS)}.
596     *
597     * @return this, for chaining, not null
598     */
599    public DateTimeFormatterBuilder appendInstant() {
600        appendInternal(new InstantPrinterParser());
601        return this;
602    }
603
604    /**
605     * Appends the zone offset, such as '+01:00', to the formatter.
606     * <p>
607     * This appends an instruction to print/parse the offset ID to the builder.
608     * This is equivalent to calling {@code appendOffset("HH:MM:ss", "Z")}.
609     *
610     * @return this, for chaining, not null
611     */
612    public DateTimeFormatterBuilder appendOffsetId() {
613        appendInternal(OffsetIdPrinterParser.INSTANCE_ID);
614        return this;
615    }
616
617    /**
618     * Appends the zone offset, such as '+01:00', to the formatter.
619     * <p>
620     * This appends an instruction to print/parse the offset ID to the builder.
621     * <p>
622     * During printing, the offset is obtained using a mechanism equivalent
623     * to querying the temporal with {@link TemporalQueries#offset()}.
624     * It will be printed using the format defined below.
625     * If the offset cannot be obtained then an exception is thrown unless the
626     * section of the formatter is optional.
627     * <p>
628     * During parsing, the offset is parsed using the format defined below.
629     * If the offset cannot be parsed then an exception is thrown unless the
630     * section of the formatter is optional.
631     * <p>
632     * The format of the offset is controlled by a pattern which must be one
633     * of the following:
634     * <p><ul>
635     * <li>{@code +HH} - hour only, ignoring any minute
636     * <li>{@code +HHMM} - hour and minute, no colon
637     * <li>{@code +HH:MM} - hour and minute, with colon
638     * <li>{@code +HHMMss} - hour and minute, with second if non-zero and no colon
639     * <li>{@code +HH:MM:ss} - hour and minute, with second if non-zero and colon
640     * <li>{@code +HHMMSS} - hour, minute and second, no colon
641     * <li>{@code +HH:MM:SS} - hour, minute and second, with colon
642     * </ul><p>
643     * The "no offset" text controls what text is printed when the offset is zero.
644     * Example values would be 'Z', '+00:00', 'UTC' or 'GMT'.
645     * Three formats are accepted for parsing UTC - the "no offset" text, and the
646     * plus and minus versions of zero defined by the pattern.
647     *
648     * @param pattern  the pattern to use, not null
649     * @param noOffsetText  the text to use when the offset is zero, not null
650     * @return this, for chaining, not null
651     */
652    public DateTimeFormatterBuilder appendOffset(String pattern, String noOffsetText) {
653        appendInternal(new OffsetIdPrinterParser(noOffsetText, pattern));
654        return this;
655    }
656
657    //-----------------------------------------------------------------------
658    /**
659     * Appends the time-zone ID, such as 'Europe/Paris' or '+02:00', to the formatter.
660     * <p>
661     * This appends an instruction to print/parse the zone ID to the builder.
662     * The zone ID is obtained in a strict manner suitable for {@code ZonedDateTime}.
663     * By contrast, {@code OffsetDateTime} does not have a zone ID suitable
664     * for use with this method, see {@link #appendZoneOrOffsetId()}.
665     * <p>
666     * During printing, the zone is obtained using a mechanism equivalent
667     * to querying the temporal with {@link TemporalQueries#zoneId()}.
668     * It will be printed using the result of {@link ZoneId#getId()}.
669     * If the zone cannot be obtained then an exception is thrown unless the
670     * section of the formatter is optional.
671     * <p>
672     * During parsing, the zone is parsed and must match a known zone or offset.
673     * If the zone cannot be parsed then an exception is thrown unless the
674     * section of the formatter is optional.
675     *
676     * @return this, for chaining, not null
677     * @see #appendZoneRegionId()
678     */
679    public DateTimeFormatterBuilder appendZoneId() {
680        appendInternal(new ZoneIdPrinterParser(TemporalQueries.zoneId(), "ZoneId()"));
681        return this;
682    }
683
684    /**
685     * Appends the time-zone region ID, such as 'Europe/Paris', to the formatter,
686     * rejecting the zone ID if it is a {@code ZoneOffset}.
687     * <p>
688     * This appends an instruction to print/parse the zone ID to the builder
689     * only if it is a region-based ID.
690     * <p>
691     * During printing, the zone is obtained using a mechanism equivalent
692     * to querying the temporal with {@link TemporalQueries#zoneId()}.
693     * If the zone is a {@code ZoneOffset} or it cannot be obtained then
694     * an exception is thrown unless the section of the formatter is optional.
695     * If the zone is not an offset, then the zone will be printed using
696     * the zone ID from {@link ZoneId#getId()}.
697     * <p>
698     * During parsing, the zone is parsed and must match a known zone or offset.
699     * If the zone cannot be parsed then an exception is thrown unless the
700     * section of the formatter is optional.
701     * Note that parsing accepts offsets, whereas printing will never produce
702     * one, thus parsing is equivalent to {@code appendZoneId}.
703     *
704     * @return this, for chaining, not null
705     * @see #appendZoneId()
706     */
707    public DateTimeFormatterBuilder appendZoneRegionId() {
708        appendInternal(new ZoneIdPrinterParser(QUERY_REGION_ONLY, "ZoneRegionId()"));
709        return this;
710    }
711
712    /**
713     * Appends the time-zone ID, such as 'Europe/Paris' or '+02:00', to
714     * the formatter, using the best available zone ID.
715     * <p>
716     * This appends an instruction to print/parse the best available
717     * zone or offset ID to the builder.
718     * The zone ID is obtained in a lenient manner that first attempts to
719     * find a true zone ID, such as that on {@code ZonedDateTime}, and
720     * then attempts to find an offset, such as that on {@code OffsetDateTime}.
721     * <p>
722     * During printing, the zone is obtained using a mechanism equivalent
723     * to querying the temporal with {@link TemporalQueries#zone()}.
724     * It will be printed using the result of {@link ZoneId#getId()}.
725     * If the zone cannot be obtained then an exception is thrown unless the
726     * section of the formatter is optional.
727     * <p>
728     * During parsing, the zone is parsed and must match a known zone or offset.
729     * If the zone cannot be parsed then an exception is thrown unless the
730     * section of the formatter is optional.
731     * <p>
732     * This method is is identical to {@code appendZoneId()} except in the
733     * mechanism used to obtain the zone.
734     *
735     * @return this, for chaining, not null
736     * @see #appendZoneId()
737     */
738    public DateTimeFormatterBuilder appendZoneOrOffsetId() {
739        appendInternal(new ZoneIdPrinterParser(TemporalQueries.zone(), "ZoneOrOffsetId()"));
740        return this;
741    }
742
743    /**
744     * Appends the time-zone name, such as 'British Summer Time', to the formatter.
745     * <p>
746     * This appends an instruction to print the textual name of the zone to the builder.
747     * <p>
748     * During printing, the zone is obtained using a mechanism equivalent
749     * to querying the temporal with {@link TemporalQueries#zoneId()}.
750     * If the zone is a {@code ZoneOffset} it will be printed using the
751     * result of {@link ZoneOffset#getId()}.
752     * If the zone is not an offset, the textual name will be looked up
753     * for the locale set in the {@link DateTimeFormatter}.
754     * If the temporal object being printed represents an instant, then the text
755     * will be the summer or winter time text as appropriate.
756     * If the lookup for text does not find any suitable reuslt, then the
757     * {@link ZoneId#getId() ID} will be printed instead.
758     * If the zone cannot be obtained then an exception is thrown unless the
759     * section of the formatter is optional.
760     * <p>
761     * Parsing is not currently supported.
762     *
763     * @param textStyle  the text style to use, not null
764     * @return this, for chaining, not null
765     */
766    public DateTimeFormatterBuilder appendZoneText(TextStyle textStyle) {
767        appendInternal(new ZoneTextPrinterParser(textStyle));
768        return this;
769    }
770
771    //-----------------------------------------------------------------------
772    /**
773     * Appends the chronology ID to the formatter.
774     * <p>
775     * The chronology ID will be output during a print.
776     * If the chronology cannot be obtained then an exception will be thrown.
777     *
778     * @return this, for chaining, not null
779     */
780    public DateTimeFormatterBuilder appendChronoId() {
781        appendInternal(new ChronoPrinterParser(null));
782        return this;
783    }
784
785    /**
786     * Appends the chronology name to the formatter.
787     * <p>
788     * The calendar system name will be output during a print.
789     * If the chronology cannot be obtained then an exception will be thrown.
790     * The calendar system name is obtained from the formatting symbols.
791     *
792     * @param textStyle  the text style to use, not null
793     * @return this, for chaining, not null
794     */
795    public DateTimeFormatterBuilder appendChronoText(TextStyle textStyle) {
796        Objects.requireNonNull(textStyle, "textStyle");
797        appendInternal(new ChronoPrinterParser(textStyle));
798        return this;
799    }
800
801    //-----------------------------------------------------------------------
802    /**
803     * Appends a localized date-time pattern to the formatter.
804     * <p>
805     * The pattern is resolved lazily using the locale being used during the print/parse
806     * (stored in {@link DateTimeFormatter}.
807     * <p>
808     * The pattern can vary by chronology, although typically it doesn't.
809     * This method uses the standard ISO chronology patterns.
810     *
811     * @param dateStyle  the date style to use, null means no date required
812     * @param timeStyle  the time style to use, null means no time required
813     * @return this, for chaining, not null
814     */
815    public DateTimeFormatterBuilder appendLocalized(FormatStyle dateStyle, FormatStyle timeStyle) {
816        return appendLocalized(dateStyle, timeStyle, ISOChrono.INSTANCE);
817    }
818
819    /**
820     * Appends a localized date-time pattern to the formatter.
821     * <p>
822     * The pattern is resolved lazily using the locale being used during the print/parse,
823     * stored in {@link DateTimeFormatter}.
824     * <p>
825     * The pattern can vary by chronology, although typically it doesn't.
826     * This method allows the chronology to be specified.
827     *
828     * @param dateStyle  the date style to use, null means no date required
829     * @param timeStyle  the time style to use, null means no time required
830     * @param chrono  the chronology to use, not null
831     * @return this, for chaining, not null
832     */
833    public DateTimeFormatterBuilder appendLocalized(FormatStyle dateStyle, FormatStyle timeStyle, Chrono<?> chrono) {
834        Objects.requireNonNull(chrono, "chrono");
835        if (dateStyle != null || timeStyle != null) {
836            appendInternal(new LocalizedPrinterParser(dateStyle, timeStyle, chrono));
837        }
838        return this;
839    }
840
841    //-----------------------------------------------------------------------
842    /**
843     * Appends a character literal to the formatter.
844     * <p>
845     * This character will be output during a print.
846     *
847     * @param literal  the literal to append, not null
848     * @return this, for chaining, not null
849     */
850    public DateTimeFormatterBuilder appendLiteral(char literal) {
851        appendInternal(new CharLiteralPrinterParser(literal));
852        return this;
853    }
854
855    /**
856     * Appends a string literal to the formatter.
857     * <p>
858     * This string will be output during a print.
859     * <p>
860     * If the literal is empty, nothing is added to the formatter.
861     *
862     * @param literal  the literal to append, not null
863     * @return this, for chaining, not null
864     */
865    public DateTimeFormatterBuilder appendLiteral(String literal) {
866        Objects.requireNonNull(literal, "literal");
867        if (literal.length() > 0) {
868            if (literal.length() == 1) {
869                appendInternal(new CharLiteralPrinterParser(literal.charAt(0)));
870            } else {
871                appendInternal(new StringLiteralPrinterParser(literal));
872            }
873        }
874        return this;
875    }
876
877    //-----------------------------------------------------------------------
878    /**
879     * Appends all the elements of a formatter to the builder.
880     * <p>
881     * This method has the same effect as appending each of the constituent
882     * parts of the formatter directly to this builder.
883     *
884     * @param formatter  the formatter to add, not null
885     * @return this, for chaining, not null
886     */
887    public DateTimeFormatterBuilder append(DateTimeFormatter formatter) {
888        Objects.requireNonNull(formatter, "formatter");
889        appendInternal(formatter.toPrinterParser(false));
890        return this;
891    }
892
893    /**
894     * Appends a formatter to the builder which will optionally print/parse.
895     * <p>
896     * This method has the same effect as appending each of the constituent
897     * parts directly to this builder surrounded by an {@link #optionalStart()} and
898     * {@link #optionalEnd()}.
899     * <p>
900     * The formatter will print if data is available for all the fields contained within it.
901     * The formatter will parse if the string matches, otherwise no error is returned.
902     *
903     * @param formatter  the formatter to add, not null
904     * @return this, for chaining, not null
905     */
906    public DateTimeFormatterBuilder appendOptional(DateTimeFormatter formatter) {
907        Objects.requireNonNull(formatter, "formatter");
908        appendInternal(formatter.toPrinterParser(true));
909        return this;
910    }
911
912    //-----------------------------------------------------------------------
913    /**
914     * Appends the elements defined by the specified pattern to the builder.
915     * <p>
916     * All letters 'A' to 'Z' and 'a' to 'z' are reserved as pattern letters.
917     * The characters '{' and '}' are reserved for future use.
918     * The characters '[' and ']' indicate optional patterns.
919     * The following pattern letters are defined:
920     * <pre>
921     *  Symbol  Meaning                     Presentation      Examples
922     *  ------  -------                     ------------      -------
923     *   G       era                         number/text       1; 01; AD; Anno Domini
924     *   y       year                        year              2004; 04
925     *   D       day-of-year                 number            189
926     *   M       month-of-year               number/text       7; 07; Jul; July; J
927     *   d       day-of-month                number            10
928     *
929     *   Q       quarter-of-year             number/text       3; 03; Q3
930     *   Y       week-based-year             year              1996; 96
931     *   w       week-of-year                number            27
932     *   W       week-of-month               number            27
933     *   e       localized day-of-week       number            2; Tue; Tuesday; T
934     *   E       day-of-week                 number/text       2; Tue; Tuesday; T
935     *   F       week-of-month               number            3
936     *
937     *   a       am-pm-of-day                text              PM
938     *   h       clock-hour-of-am-pm (1-12)  number            12
939     *   K       hour-of-am-pm (0-11)        number            0
940     *   k       clock-hour-of-am-pm (1-24)  number            0
941     *
942     *   H       hour-of-day (0-23)          number            0
943     *   m       minute-of-hour              number            30
944     *   s       second-of-minute            number            55
945     *   S       fraction-of-second          fraction          978
946     *   A       milli-of-day                number            1234
947     *   n       nano-of-second              number            987654321
948     *   N       nano-of-day                 number            1234000000
949     *
950     *   I       time-zone ID                zoneId            America/Los_Angeles
951     *   z       time-zone name              text              Pacific Standard Time; PST
952     *   Z       zone-offset                 offset-Z          +0000; -0800; -08:00;
953     *   X       zone-offset 'Z' for zero    offset-X          Z; -08; -0830; -08:30; -083015; -08:30:15;
954     *
955     *   p       pad next                    pad modifier      1
956     *
957     *   '       escape for text             delimiter
958     *   ''      single quote                literal           '
959     *   [       optional section start
960     *   ]       optional section end
961     *   {}      reserved for future use
962     * </pre>
963     * <p>
964     * The count of pattern letters determine the format.
965     * <p>
966     * <b>Text</b>: The text style is determined based on the number of pattern letters used.
967     * Less than 4 pattern letters will use the {@link TextStyle#SHORT short form}.
968     * Exactly 4 pattern letters will use the {@link TextStyle#FULL full form}.
969     * Exactly 5 pattern letters will use the {@link TextStyle#NARROW narrow form}.
970     * <p>
971     * <b>Number</b>: If the count of letters is one, then the value is printed using the minimum number
972     * of digits and without padding as per {@link #appendValue(TemporalField)}. Otherwise, the
973     * count of digits is used as the width of the output field as per {@link #appendValue(TemporalField, int)}.
974     * <p>
975     * <b>Number/Text</b>: If the count of pattern letters is 3 or greater, use the Text rules above.
976     * Otherwise use the Number rules above.
977     * <p>
978     * <b>Fraction</b>: Outputs the nano-of-second field as a fraction-of-second.
979     * The nano-of-second value has nine digits, thus the count of pattern letters is from 1 to 9.
980     * If it is less than 9, then the nano-of-second value is truncated, with only the most
981     * significant digits being output.
982     * When parsing in strict mode, the number of parsed digits must match the count of pattern letters.
983     * When parsing in lenient mode, the number of parsed digits must be at least the count of pattern
984     * letters, up to 9 digits.
985     * <p>
986     * <b>Year</b>: The count of letters determines the minimum field width below which padding is used.
987     * If the count of letters is two, then a {@link #appendValueReduced reduced} two digit form is used.
988     * For printing, this outputs the rightmost two digits. For parsing, this will parse using the
989     * base value of 2000, resulting in a year within the range 2000 to 2099 inclusive.
990     * If the count of letters is less than four (but not two), then the sign is only output for negative
991     * years as per {@link SignStyle#NORMAL}.
992     * Otherwise, the sign is output if the pad width is exceeded, as per {@link SignStyle#EXCEEDS_PAD}
993     * <p>
994     * <b>ZoneId</b>: 'I' outputs the zone ID, such as 'Europe/Paris'.
995     * <p>
996     * <b>Offset X</b>: This formats the offset using 'Z' when the offset is zero.
997     * One letter outputs just the hour', such as '+01'
998     * Two letters outputs the hour and minute, without a colon, such as '+0130'.
999     * Three letters outputs the hour and minute, with a colon, such as '+01:30'.
1000     * Four letters outputs the hour and minute and optional second, without a colon, such as '+013015'.
1001     * Five letters outputs the hour and minute and optional second, with a colon, such as '+01:30:15'.
1002     * <p>
1003     * <b>Offset Z</b>: This formats the offset using '+0000' or '+00:00' when the offset is zero.
1004     * One or two letters outputs the hour and minute, without a colon, such as '+0130'.
1005     * Three letters outputs the hour and minute, with a colon, such as '+01:30'.
1006     * <p>
1007     * <b>Zone names</b>: Time zone names ('z') cannot be parsed.
1008     * <p>
1009     * <b>Optional section</b>: The optional section markers work exactly like calling {@link #optionalStart()}
1010     * and {@link #optionalEnd()}.
1011     * <p>
1012     * <b>Pad modifier</b>: Modifies the pattern that immediately follows to be padded with spaces.
1013     * The pad width is determined by the number of pattern letters.
1014     * This is the same as calling {@link #padNext(int)}.
1015     * <p>
1016     * For example, 'ppH' outputs the hour-of-day padded on the left with spaces to a width of 2.
1017     * <p>
1018     * Any unrecognized letter is an error.
1019     * Any non-letter character, other than '[', ']', '{', '}' and the single quote will be output directly.
1020     * Despite this, it is recommended to use single quotes around all characters that you want to
1021     * output directly to ensure that future changes do not break your application.
1022     * <p>
1023     * The pattern string is similar, but not identical, to {@link java.text.SimpleDateFormat SimpleDateFormat}.
1024     * Pattern letters 'E' and 'u' are merged, which changes the meaning of "E" and "EE" to be numeric.
1025     * Pattern letters 'Z' and 'X' are extended.
1026     * Pattern letter 'y' and 'Y' parse years of two digits and more than 4 digits differently.
1027     * Pattern letters 'n', 'A', 'N', 'I' and 'p' are added.
1028     * Number types will reject large numbers.
1029     * The pattern string is also similar, but not identical, to that defined by the
1030     * Unicode Common Locale Data Repository (CLDR).
1031     *
1032     * @param pattern  the pattern to add, not null
1033     * @return this, for chaining, not null
1034     * @throws IllegalArgumentException if the pattern is invalid
1035     */
1036    public DateTimeFormatterBuilder appendPattern(String pattern) {
1037        Objects.requireNonNull(pattern, "pattern");
1038        parsePattern(pattern);
1039        return this;
1040    }
1041
1042    private void parsePattern(String pattern) {
1043        for (int pos = 0; pos < pattern.length(); pos++) {
1044            char cur = pattern.charAt(pos);
1045            if ((cur >= 'A' && cur <= 'Z') || (cur >= 'a' && cur <= 'z')) {
1046                int start = pos++;
1047                for ( ; pos < pattern.length() && pattern.charAt(pos) == cur; pos++);  // short loop
1048                int count = pos - start;
1049                // padding
1050                if (cur == 'p') {
1051                    int pad = 0;
1052                    if (pos < pattern.length()) {
1053                        cur = pattern.charAt(pos);
1054                        if ((cur >= 'A' && cur <= 'Z') || (cur >= 'a' && cur <= 'z')) {
1055                            pad = count;
1056                            start = pos++;
1057                            for ( ; pos < pattern.length() && pattern.charAt(pos) == cur; pos++);  // short loop
1058                            count = pos - start;
1059                        }
1060                    }
1061                    if (pad == 0) {
1062                        throw new IllegalArgumentException(
1063                                "Pad letter 'p' must be followed by valid pad pattern: " + pattern);
1064                    }
1065                    padNext(pad); // pad and continue parsing
1066                }
1067                // main rules
1068                TemporalField field = FIELD_MAP.get(cur);
1069                if (field != null) {
1070                    parseField(cur, count, field);
1071                } else if (cur == 'z') {
1072                    if (count < 4) {
1073                        appendZoneText(TextStyle.SHORT);
1074                    } else {
1075                        appendZoneText(TextStyle.FULL);
1076                    }
1077                } else if (cur == 'I') {
1078                    appendZoneId();
1079                } else if (cur == 'Z') {
1080                    if (count > 3) {
1081                        throw new IllegalArgumentException("Too many pattern letters: " + cur);
1082                    }
1083                    if (count < 3) {
1084                        appendOffset("+HHMM", "+0000");
1085                    } else {
1086                        appendOffset("+HH:MM", "+00:00");
1087                    }
1088                } else if (cur == 'X') {
1089                    if (count > 5) {
1090                        throw new IllegalArgumentException("Too many pattern letters: " + cur);
1091                    }
1092                    appendOffset(OffsetIdPrinterParser.PATTERNS[count - 1], "Z");
1093                } else {
1094                    throw new IllegalArgumentException("Unknown pattern letter: " + cur);
1095                }
1096                pos--;
1097
1098            } else if (cur == '\'') {
1099                // parse literals
1100                int start = pos++;
1101                for ( ; pos < pattern.length(); pos++) {
1102                    if (pattern.charAt(pos) == '\'') {
1103                        if (pos + 1 < pattern.length() && pattern.charAt(pos + 1) == '\'') {
1104                            pos++;
1105                        } else {
1106                            break;  // end of literal
1107                        }
1108                    }
1109                }
1110                if (pos >= pattern.length()) {
1111                    throw new IllegalArgumentException("Pattern ends with an incomplete string literal: " + pattern);
1112                }
1113                String str = pattern.substring(start + 1, pos);
1114                if (str.length() == 0) {
1115                    appendLiteral('\'');
1116                } else {
1117                    appendLiteral(str.replace("''", "'"));
1118                }
1119
1120            } else if (cur == '[') {
1121                optionalStart();
1122
1123            } else if (cur == ']') {
1124                if (active.parent == null) {
1125                    throw new IllegalArgumentException("Pattern invalid as it contains ] without previous [");
1126                }
1127                optionalEnd();
1128
1129            } else if (cur == '{' || cur == '}') {
1130                throw new IllegalArgumentException("Pattern includes reserved character: '" + cur + "'");
1131            } else {
1132                appendLiteral(cur);
1133            }
1134        }
1135    }
1136
1137    private void parseField(char cur, int count, TemporalField field) {
1138        switch (cur) {
1139            case 'y':
1140            case 'Y':
1141                if (count == 2) {
1142                    appendValueReduced(field, 2, 2000);
1143                } else if (count < 4) {
1144                    appendValue(field, count, 19, SignStyle.NORMAL);
1145                } else {
1146                    appendValue(field, count, 19, SignStyle.EXCEEDS_PAD);
1147                }
1148                break;
1149            case 'G':
1150            case 'M':
1151            case 'Q':
1152            case 'E':
1153                switch (count) {
1154                    case 1:
1155                        appendValue(field);
1156                        break;
1157                    case 2:
1158                        appendValue(field, 2);
1159                        break;
1160                    case 3:
1161                        appendText(field, TextStyle.SHORT);
1162                        break;
1163                    case 4:
1164                        appendText(field, TextStyle.FULL);
1165                        break;
1166                    case 5:
1167                        appendText(field, TextStyle.NARROW);
1168                        break;
1169                    default:
1170                        throw new IllegalArgumentException("Too many pattern letters: " + cur);
1171                }
1172                break;
1173            case 'a':
1174                switch (count) {
1175                    case 1:
1176                    case 2:
1177                    case 3:
1178                        appendText(field, TextStyle.SHORT);
1179                        break;
1180                    case 4:
1181                        appendText(field, TextStyle.FULL);
1182                        break;
1183                    case 5:
1184                        appendText(field, TextStyle.NARROW);
1185                        break;
1186                    default:
1187                        throw new IllegalArgumentException("Too many pattern letters: " + cur);
1188                }
1189                break;
1190            case 'S':
1191                appendFraction(NANO_OF_SECOND, count, count, false);
1192                break;
1193            default:
1194                if (count == 1) {
1195                    appendValue(field);
1196                } else {
1197                    appendValue(field, count);
1198                }
1199                break;
1200        }
1201    }
1202
1203    /** Map of letters to fields. */
1204    private static final Map<Character, TemporalField> FIELD_MAP = new HashMap<>();
1205    static {
1206        FIELD_MAP.put('G', ChronoField.ERA);                       // Java, CLDR (different to both for 1/2 chars)
1207        FIELD_MAP.put('y', ChronoField.YEAR);                      // CLDR
1208        // FIELD_MAP.put('y', ChronoField.YEAR_OF_ERA);            // Java, CLDR  // TODO redefine from above
1209        // FIELD_MAP.put('u', ChronoField.YEAR);                   // CLDR  // TODO
1210        // FIELD_MAP.put('Y', ISODateTimeField.WEEK_BASED_YEAR);          // Java7, CLDR (needs localized week number)  // TODO
1211        FIELD_MAP.put('Q', ISOFields.QUARTER_OF_YEAR);             // CLDR (removed quarter from 310)
1212        FIELD_MAP.put('M', ChronoField.MONTH_OF_YEAR);             // Java, CLDR
1213        // FIELD_MAP.put('w', WeekFields.weekOfYear());            // Java, CLDR (needs localized week number)
1214        // FIELD_MAP.put('W', WeekFields.weekOfMonth());           // Java, CLDR (needs localized week number)
1215        FIELD_MAP.put('D', ChronoField.DAY_OF_YEAR);               // Java, CLDR
1216        FIELD_MAP.put('d', ChronoField.DAY_OF_MONTH);              // Java, CLDR
1217        FIELD_MAP.put('F', ChronoField.ALIGNED_WEEK_OF_MONTH);     // Java, CLDR
1218        FIELD_MAP.put('E', ChronoField.DAY_OF_WEEK);               // Java, CLDR (different to both for 1/2 chars)
1219        // FIELD_MAP.put('e', WeekFields.dayOfWeek());             // CLDR (needs localized week number)
1220        FIELD_MAP.put('a', ChronoField.AMPM_OF_DAY);               // Java, CLDR
1221        FIELD_MAP.put('H', ChronoField.HOUR_OF_DAY);               // Java, CLDR
1222        FIELD_MAP.put('k', ChronoField.CLOCK_HOUR_OF_DAY);         // Java, CLDR
1223        FIELD_MAP.put('K', ChronoField.HOUR_OF_AMPM);              // Java, CLDR
1224        FIELD_MAP.put('h', ChronoField.CLOCK_HOUR_OF_AMPM);        // Java, CLDR
1225        FIELD_MAP.put('m', ChronoField.MINUTE_OF_HOUR);            // Java, CLDR
1226        FIELD_MAP.put('s', ChronoField.SECOND_OF_MINUTE);          // Java, CLDR
1227        FIELD_MAP.put('S', ChronoField.NANO_OF_SECOND);            // CLDR (Java uses milli-of-second number)
1228        FIELD_MAP.put('A', ChronoField.MILLI_OF_DAY);              // CLDR
1229        FIELD_MAP.put('n', ChronoField.NANO_OF_SECOND);            // 310
1230        FIELD_MAP.put('N', ChronoField.NANO_OF_DAY);               // 310
1231        // reserved - z,Z,X,I,p
1232        // Java - X - compatible, but extended to 4 and 5 letters
1233        // Java - u - clashes with CLDR, go with CLDR (year-proleptic) here
1234        // CLDR - U - cycle year name, not supported by 310 yet
1235        // CLDR - l - deprecated
1236        // CLDR - j - not relevant
1237        // CLDR - g - modified-julian-day
1238        // CLDR - z - time-zone names  // TODO properly
1239        // CLDR - Z - different approach here  // TODO bring 310 in line with CLDR
1240        // CLDR - v,V - extended time-zone names
1241        // CLDR - q/c/L - standalone quarter/day-of-week/month
1242        //  310 - I - time-zone id
1243        //  310 - p - prefix for padding
1244    }
1245
1246    //-----------------------------------------------------------------------
1247    /**
1248     * Causes the next added printer/parser to pad to a fixed width using a space.
1249     * <p>
1250     * This padding will pad to a fixed width using spaces.
1251     * <p>
1252     * An exception will be thrown during printing if the pad width
1253     * is exceeded.
1254     *
1255     * @param padWidth  the pad width, 1 or greater
1256     * @return this, for chaining, not null
1257     * @throws IllegalArgumentException if pad width is too small
1258     */
1259    public DateTimeFormatterBuilder padNext(int padWidth) {
1260        return padNext(padWidth, ' ');
1261    }
1262
1263    /**
1264     * Causes the next added printer/parser to pad to a fixed width.
1265     * <p>
1266     * This padding is intended for padding other than zero-padding.
1267     * Zero-padding should be achieved using the appendValue methods.
1268     * <p>
1269     * An exception will be thrown during printing if the pad width
1270     * is exceeded.
1271     *
1272     * @param padWidth  the pad width, 1 or greater
1273     * @param padChar  the pad character
1274     * @return this, for chaining, not null
1275     * @throws IllegalArgumentException if pad width is too small
1276     */
1277    public DateTimeFormatterBuilder padNext(int padWidth, char padChar) {
1278        if (padWidth < 1) {
1279            throw new IllegalArgumentException("The pad width must be at least one but was " + padWidth);
1280        }
1281        active.padNextWidth = padWidth;
1282        active.padNextChar = padChar;
1283        active.valueParserIndex = -1;
1284        return this;
1285    }
1286
1287    //-----------------------------------------------------------------------
1288    /**
1289     * Mark the start of an optional section.
1290     * <p>
1291     * The output of printing can include optional sections, which may be nested.
1292     * An optional section is started by calling this method and ended by calling
1293     * {@link #optionalEnd()} or by ending the build process.
1294     * <p>
1295     * All elements in the optional section are treated as optional.
1296     * During printing, the section is only output if data is available in the
1297     * {@code TemporalAccessor} for all the elements in the section.
1298     * During parsing, the whole section may be missing from the parsed string.
1299     * <p>
1300     * For example, consider a builder setup as
1301     * {@code builder.appendValue(HOUR_OF_DAY,2).optionalStart().appendValue(MINUTE_OF_HOUR,2)}.
1302     * The optional section ends automatically at the end of the builder.
1303     * During printing, the minute will only be output if its value can be obtained from the date-time.
1304     * During parsing, the input will be successfully parsed whether the minute is present or not.
1305     *
1306     * @return this, for chaining, not null
1307     */
1308    public DateTimeFormatterBuilder optionalStart() {
1309        active.valueParserIndex = -1;
1310        active = new DateTimeFormatterBuilder(active, true);
1311        return this;
1312    }
1313
1314    /**
1315     * Ends an optional section.
1316     * <p>
1317     * The output of printing can include optional sections, which may be nested.
1318     * An optional section is started by calling {@link #optionalStart()} and ended
1319     * using this method (or at the end of the builder).
1320     * <p>
1321     * Calling this method without having previously called {@code optionalStart}
1322     * will throw an exception.
1323     * Calling this method immediately after calling {@code optionalStart} has no effect
1324     * on the formatter other than ending the (empty) optional section.
1325     * <p>
1326     * All elements in the optional section are treated as optional.
1327     * During printing, the section is only output if data is available in the
1328     * {@code TemporalAccessor} for all the elements in the section.
1329     * During parsing, the whole section may be missing from the parsed string.
1330     * <p>
1331     * For example, consider a builder setup as
1332     * {@code builder.appendValue(HOUR_OF_DAY,2).optionalStart().appendValue(MINUTE_OF_HOUR,2).optionalEnd()}.
1333     * During printing, the minute will only be output if its value can be obtained from the date-time.
1334     * During parsing, the input will be successfully parsed whether the minute is present or not.
1335     *
1336     * @return this, for chaining, not null
1337     * @throws IllegalStateException if there was no previous call to {@code optionalStart}
1338     */
1339    public DateTimeFormatterBuilder optionalEnd() {
1340        if (active.parent == null) {
1341            throw new IllegalStateException("Cannot call optionalEnd() as there was no previous call to optionalStart()");
1342        }
1343        if (active.printerParsers.size() > 0) {
1344            CompositePrinterParser cpp = new CompositePrinterParser(active.printerParsers, active.optional);
1345            active = active.parent;
1346            appendInternal(cpp);
1347        } else {
1348            active = active.parent;
1349        }
1350        return this;
1351    }
1352
1353    //-----------------------------------------------------------------------
1354    /**
1355     * Appends a printer and/or parser to the internal list handling padding.
1356     *
1357     * @param pp  the printer-parser to add, not null
1358     * @return the index into the active parsers list
1359     */
1360    private int appendInternal(DateTimePrinterParser pp) {
1361        Objects.requireNonNull(pp, "pp");
1362        if (active.padNextWidth > 0) {
1363            if (pp != null) {
1364                pp = new PadPrinterParserDecorator(pp, active.padNextWidth, active.padNextChar);
1365            }
1366            active.padNextWidth = 0;
1367            active.padNextChar = 0;
1368        }
1369        active.printerParsers.add(pp);
1370        active.valueParserIndex = -1;
1371        return active.printerParsers.size() - 1;
1372    }
1373
1374    //-----------------------------------------------------------------------
1375    /**
1376     * Completes this builder by creating the DateTimeFormatter using the default locale.
1377     * <p>
1378     * This will create a formatter with the default locale.
1379     * Numbers will be printed and parsed using the standard non-localized set of symbols.
1380     * <p>
1381     * Calling this method will end any open optional sections by repeatedly
1382     * calling {@link #optionalEnd()} before creating the formatter.
1383     * <p>
1384     * This builder can still be used after creating the formatter if desired,
1385     * although the state may have been changed by calls to {@code optionalEnd}.
1386     *
1387     * @return the created formatter, not null
1388     */
1389    public DateTimeFormatter toFormatter() {
1390        return toFormatter(Locale.getDefault());
1391    }
1392
1393    /**
1394     * Completes this builder by creating the DateTimeFormatter using the specified locale.
1395     * <p>
1396     * This will create a formatter with the specified locale.
1397     * Numbers will be printed and parsed using the standard non-localized set of symbols.
1398     * <p>
1399     * Calling this method will end any open optional sections by repeatedly
1400     * calling {@link #optionalEnd()} before creating the formatter.
1401     * <p>
1402     * This builder can still be used after creating the formatter if desired,
1403     * although the state may have been changed by calls to {@code optionalEnd}.
1404     *
1405     * @param locale  the locale to use for formatting, not null
1406     * @return the created formatter, not null
1407     */
1408    public DateTimeFormatter toFormatter(Locale locale) {
1409        Objects.requireNonNull(locale, "locale");
1410        while (active.parent != null) {
1411            optionalEnd();
1412        }
1413        CompositePrinterParser pp = new CompositePrinterParser(printerParsers, false);
1414        return new DateTimeFormatter(pp, locale, DateTimeFormatSymbols.STANDARD, null, null);
1415    }
1416
1417    //-----------------------------------------------------------------------
1418    /**
1419     * Strategy for printing/parsing date-time information.
1420     * <p>
1421     * The printer may print any part, or the whole, of the input date-time object.
1422     * Typically, a complete print is constructed from a number of smaller
1423     * units, each outputting a single field.
1424     * <p>
1425     * The parser may parse any piece of text from the input, storing the result
1426     * in the context. Typically, each individual parser will just parse one
1427     * field, such as the day-of-month, storing the value in the context.
1428     * Once the parse is complete, the caller will then convert the context
1429     * to a {@link DateTimeBuilder} to merge the parsed values to create the
1430     * desired object, such as a {@code LocalDate}.
1431     * <p>
1432     * The parse position will be updated during the parse. Parsing will start at
1433     * the specified index and the return value specifies the new parse position
1434     * for the next parser. If an error occurs, the returned index will be negative
1435     * and will have the error position encoded using the complement operator.
1436     *
1437     * <h3>Specification for implementors</h3>
1438     * This interface must be implemented with care to ensure other classes operate correctly.
1439     * All implementations that can be instantiated must be final, immutable and thread-safe.
1440     * <p>
1441     * The context is not a thread-safe object and a new instance will be created
1442     * for each print that occurs. The context must not be stored in an instance
1443     * variable or shared with any other threads.
1444     */
1445    interface DateTimePrinterParser {
1446
1447        /**
1448         * Prints the date-time object to the buffer.
1449         * <p>
1450         * The context holds information to use during the print.
1451         * It also contains the date-time information to be printed.
1452         * <p>
1453         * The buffer must not be mutated beyond the content controlled by the implementation.
1454         *
1455         * @param context  the context to print using, not null
1456         * @param buf  the buffer to append to, not null
1457         * @return false if unable to query the value from the date-time, true otherwise
1458         * @throws DateTimeException if the date-time cannot be printed successfully
1459         */
1460        boolean print(DateTimePrintContext context, StringBuilder buf);
1461
1462        /**
1463         * Parses text into date-time information.
1464         * <p>
1465         * The context holds information to use during the parse.
1466         * It is also used to store the parsed date-time information.
1467         *
1468         * @param context  the context to use and parse into, not null
1469         * @param text  the input text to parse, not null
1470         * @param position  the position to start parsing at, from 0 to the text length
1471         * @return the new parse position, where negative means an error with the
1472         *  error position encoded using the complement ~ operator
1473         * @throws NullPointerException if the context or text is null
1474         * @throws IndexOutOfBoundsException if the position is invalid
1475         */
1476        int parse(DateTimeParseContext context, CharSequence text, int position);
1477    }
1478
1479    //-----------------------------------------------------------------------
1480    /**
1481     * Composite printer and parser.
1482     */
1483    static final class CompositePrinterParser implements DateTimePrinterParser {
1484        private final DateTimePrinterParser[] printerParsers;
1485        private final boolean optional;
1486
1487        CompositePrinterParser(List<DateTimePrinterParser> printerParsers, boolean optional) {
1488            this(printerParsers.toArray(new DateTimePrinterParser[printerParsers.size()]), optional);
1489        }
1490
1491        CompositePrinterParser(DateTimePrinterParser[] printerParsers, boolean optional) {
1492            this.printerParsers = printerParsers;
1493            this.optional = optional;
1494        }
1495
1496        /**
1497         * Returns a copy of this printer-parser with the optional flag changed.
1498         *
1499         * @param optional  the optional flag to set in the copy
1500         * @return the new printer-parser, not null
1501         */
1502        public CompositePrinterParser withOptional(boolean optional) {
1503            if (optional == this.optional) {
1504                return this;
1505            }
1506            return new CompositePrinterParser(printerParsers, optional);
1507        }
1508
1509        @Override
1510        public boolean print(DateTimePrintContext context, StringBuilder buf) {
1511            int length = buf.length();
1512            if (optional) {
1513                context.startOptional();
1514            }
1515            try {
1516                for (DateTimePrinterParser pp : printerParsers) {
1517                    if (pp.print(context, buf) == false) {
1518                        buf.setLength(length);  // reset buffer
1519                        return true;
1520                    }
1521                }
1522            } finally {
1523                if (optional) {
1524                    context.endOptional();
1525                }
1526            }
1527            return true;
1528        }
1529
1530        @Override
1531        public int parse(DateTimeParseContext context, CharSequence text, int position) {
1532            if (optional) {
1533                context.startOptional();
1534                int pos = position;
1535                for (DateTimePrinterParser pp : printerParsers) {
1536                    pos = pp.parse(context, text, pos);
1537                    if (pos < 0) {
1538                        context.endOptional(false);
1539                        return position;  // return original position
1540                    }
1541                }
1542                context.endOptional(true);
1543                return pos;
1544            } else {
1545                for (DateTimePrinterParser pp : printerParsers) {
1546                    position = pp.parse(context, text, position);
1547                    if (position < 0) {
1548                        break;
1549                    }
1550                }
1551                return position;
1552            }
1553        }
1554
1555        @Override
1556        public String toString() {
1557            StringBuilder buf = new StringBuilder();
1558            if (printerParsers != null) {
1559                buf.append(optional ? "[" : "(");
1560                for (DateTimePrinterParser pp : printerParsers) {
1561                    buf.append(pp);
1562                }
1563                buf.append(optional ? "]" : ")");
1564            }
1565            return buf.toString();
1566        }
1567    }
1568
1569    //-----------------------------------------------------------------------
1570    /**
1571     * Pads the output to a fixed width.
1572     */
1573    static final class PadPrinterParserDecorator implements DateTimePrinterParser {
1574        private final DateTimePrinterParser printerParser;
1575        private final int padWidth;
1576        private final char padChar;
1577
1578        /**
1579         * Constructor.
1580         *
1581         * @param printerParser  the printer, not null
1582         * @param padWidth  the width to pad to, 1 or greater
1583         * @param padChar  the pad character
1584         */
1585        PadPrinterParserDecorator(DateTimePrinterParser printerParser, int padWidth, char padChar) {
1586            // input checked by DateTimeFormatterBuilder
1587            this.printerParser = printerParser;
1588            this.padWidth = padWidth;
1589            this.padChar = padChar;
1590        }
1591
1592        @Override
1593        public boolean print(DateTimePrintContext context, StringBuilder buf) {
1594            int preLen = buf.length();
1595            if (printerParser.print(context, buf) == false) {
1596                return false;
1597            }
1598            int len = buf.length() - preLen;
1599            if (len > padWidth) {
1600                throw new DateTimePrintException(
1601                    "Cannot print as output of " + len + " characters exceeds pad width of " + padWidth);
1602            }
1603            for (int i = 0; i < padWidth - len; i++) {
1604                buf.insert(preLen, padChar);
1605            }
1606            return true;
1607        }
1608
1609        @Override
1610        public int parse(DateTimeParseContext context, CharSequence text, int position) {
1611            if (position > text.length()) {
1612                throw new IndexOutOfBoundsException();
1613            }
1614            int endPos = position + padWidth;
1615            if (endPos > text.length()) {
1616                return ~position;  // not enough characters in the string to meet the parse width
1617            }
1618            int pos = position;
1619            while (pos < endPos && text.charAt(pos) == padChar) {
1620                pos++;
1621            }
1622            text = text.subSequence(0, endPos);
1623            int firstError = 0;
1624            while (pos >= position) {
1625                int resultPos = printerParser.parse(context, text, pos);
1626                if (resultPos < 0) {
1627                    // parse of decorated field had an error
1628                    if (firstError == 0) {
1629                        firstError = resultPos;
1630                    }
1631                    // loop around in case the decorated parser can handle the padChar at the start
1632                    pos--;
1633                    continue;
1634                }
1635                if (resultPos != endPos) {
1636                    return ~position;  // parse of decorated field didn't parse to the end
1637                }
1638                return resultPos;
1639            }
1640            // loop runs at least once, so firstError must be set by the time we get here
1641            return firstError;  // return error from first parse of decorated field
1642        }
1643
1644        @Override
1645        public String toString() {
1646            return "Pad(" + printerParser + "," + padWidth + (padChar == ' ' ? ")" : ",'" + padChar + "')");
1647        }
1648    }
1649
1650    //-----------------------------------------------------------------------
1651    /**
1652     * Enumeration to apply simple parse settings.
1653     */
1654    static enum SettingsParser implements DateTimePrinterParser {
1655        SENSITIVE,
1656        INSENSITIVE,
1657        STRICT,
1658        LENIENT;
1659
1660        @Override
1661        public boolean print(DateTimePrintContext context, StringBuilder buf) {
1662            return true;  // nothing to do here
1663        }
1664
1665        @Override
1666        public int parse(DateTimeParseContext context, CharSequence text, int position) {
1667            // using ordinals to avoid javac synthetic inner class
1668            switch (ordinal()) {
1669                case 0: context.setCaseSensitive(true); break;
1670                case 1: context.setCaseSensitive(false); break;
1671                case 2: context.setStrict(true); break;
1672                case 3: context.setStrict(false); break;
1673            }
1674            return position;
1675        }
1676
1677        @Override
1678        public String toString() {
1679            // using ordinals to avoid javac synthetic inner class
1680            switch (ordinal()) {
1681                case 0: return "ParseCaseSensitive(true)";
1682                case 1: return "ParseCaseSensitive(false)";
1683                case 2: return "ParseStrict(true)";
1684                case 3: return "ParseStrict(false)";
1685            }
1686            throw new IllegalStateException("Unreachable");
1687        }
1688    }
1689
1690    //-----------------------------------------------------------------------
1691    /**
1692     * Prints or parses a character literal.
1693     */
1694    static final class CharLiteralPrinterParser implements DateTimePrinterParser {
1695        private final char literal;
1696
1697        CharLiteralPrinterParser(char literal) {
1698            this.literal = literal;
1699        }
1700
1701        @Override
1702        public boolean print(DateTimePrintContext context, StringBuilder buf) {
1703            buf.append(literal);
1704            return true;
1705        }
1706
1707        @Override
1708        public int parse(DateTimeParseContext context, CharSequence text, int position) {
1709            int length = text.length();
1710            if (position == length) {
1711                return ~position;
1712            }
1713            char ch = text.charAt(position);
1714            if (ch != literal) {
1715                if (context.isCaseSensitive() ||
1716                        (Character.toUpperCase(ch) != Character.toUpperCase(literal) &&
1717                         Character.toLowerCase(ch) != Character.toLowerCase(literal))) {
1718                    return ~position;
1719                }
1720            }
1721            return position + 1;
1722        }
1723
1724        @Override
1725        public String toString() {
1726            if (literal == '\'') {
1727                return "''";
1728            }
1729            return "'" + literal + "'";
1730        }
1731    }
1732
1733    //-----------------------------------------------------------------------
1734    /**
1735     * Prints or parses a string literal.
1736     */
1737    static final class StringLiteralPrinterParser implements DateTimePrinterParser {
1738        private final String literal;
1739
1740        StringLiteralPrinterParser(String literal) {
1741            this.literal = literal;  // validated by caller
1742        }
1743
1744        @Override
1745        public boolean print(DateTimePrintContext context, StringBuilder buf) {
1746            buf.append(literal);
1747            return true;
1748        }
1749
1750        @Override
1751        public int parse(DateTimeParseContext context, CharSequence text, int position) {
1752            int length = text.length();
1753            if (position > length || position < 0) {
1754                throw new IndexOutOfBoundsException();
1755            }
1756            if (context.subSequenceEquals(text, position, literal, 0, literal.length()) == false) {
1757                return ~position;
1758            }
1759            return position + literal.length();
1760        }
1761
1762        @Override
1763        public String toString() {
1764            String converted = literal.replace("'", "''");
1765            return "'" + converted + "'";
1766        }
1767    }
1768
1769    //-----------------------------------------------------------------------
1770    /**
1771     * Prints and parses a numeric date-time field with optional padding.
1772     */
1773    static class NumberPrinterParser implements DateTimePrinterParser {
1774
1775        /**
1776         * Array of 10 to the power of n.
1777         */
1778        static final int[] EXCEED_POINTS = new int[] {
1779            0,
1780            10,
1781            100,
1782            1000,
1783            10000,
1784            100000,
1785            1000000,
1786            10000000,
1787            100000000,
1788            1000000000,
1789        };
1790
1791        final TemporalField field;
1792        final int minWidth;
1793        private final int maxWidth;
1794        private final SignStyle signStyle;
1795        private final int subsequentWidth;
1796
1797        /**
1798         * Constructor.
1799         *
1800         * @param field  the field to print, not null
1801         * @param minWidth  the minimum field width, from 1 to 19
1802         * @param maxWidth  the maximum field width, from minWidth to 19
1803         * @param signStyle  the positive/negative sign style, not null
1804         */
1805        NumberPrinterParser(TemporalField field, int minWidth, int maxWidth, SignStyle signStyle) {
1806            // validated by caller
1807            this.field = field;
1808            this.minWidth = minWidth;
1809            this.maxWidth = maxWidth;
1810            this.signStyle = signStyle;
1811            this.subsequentWidth = 0;
1812        }
1813
1814        /**
1815         * Constructor.
1816         *
1817         * @param field  the field to print, not null
1818         * @param minWidth  the minimum field width, from 1 to 19
1819         * @param maxWidth  the maximum field width, from minWidth to 19
1820         * @param signStyle  the positive/negative sign style, not null
1821         * @param subsequentWidth  the width of subsequent non-negative numbers, 0 or greater,
1822         *  -1 if fixed width due to active adjacent parsing
1823         */
1824        private NumberPrinterParser(TemporalField field, int minWidth, int maxWidth, SignStyle signStyle, int subsequentWidth) {
1825            // validated by caller
1826            this.field = field;
1827            this.minWidth = minWidth;
1828            this.maxWidth = maxWidth;
1829            this.signStyle = signStyle;
1830            this.subsequentWidth = subsequentWidth;
1831        }
1832
1833        /**
1834         * Returns a new instance with fixed width flag set.
1835         *
1836         * @return a new updated printer-parser, not null
1837         */
1838        NumberPrinterParser withFixedWidth() {
1839            return new NumberPrinterParser(field, minWidth, maxWidth, signStyle, -1);
1840        }
1841
1842        /**
1843         * Returns a new instance with an updated subsequent width.
1844         *
1845         * @param subsequentWidth  the width of subsequent non-negative numbers, 0 or greater
1846         * @return a new updated printer-parser, not null
1847         */
1848        NumberPrinterParser withSubsequentWidth(int subsequentWidth) {
1849            return new NumberPrinterParser(field, minWidth, maxWidth, signStyle, this.subsequentWidth + subsequentWidth);
1850        }
1851
1852        @Override
1853        public boolean print(DateTimePrintContext context, StringBuilder buf) {
1854            Long valueLong = context.getValue(field);
1855            if (valueLong == null) {
1856                return false;
1857            }
1858            long value = getValue(valueLong);
1859            DateTimeFormatSymbols symbols = context.getSymbols();
1860            String str = (value == Long.MIN_VALUE ? "9223372036854775808" : Long.toString(Math.abs(value)));
1861            if (str.length() > maxWidth) {
1862                throw new DateTimePrintException("Field " + field.getName() +
1863                    " cannot be printed as the value " + value +
1864                    " exceeds the maximum print width of " + maxWidth);
1865            }
1866            str = symbols.convertNumberToI18N(str);
1867
1868            if (value >= 0) {
1869                switch (signStyle) {
1870                    case EXCEEDS_PAD:
1871                        if (minWidth < 19 && value >= EXCEED_POINTS[minWidth]) {
1872                            buf.append(symbols.getPositiveSign());
1873                        }
1874                        break;
1875                    case ALWAYS:
1876                        buf.append(symbols.getPositiveSign());
1877                        break;
1878                }
1879            } else {
1880                switch (signStyle) {
1881                    case NORMAL:
1882                    case EXCEEDS_PAD:
1883                    case ALWAYS:
1884                        buf.append(symbols.getNegativeSign());
1885                        break;
1886                    case NOT_NEGATIVE:
1887                        throw new DateTimePrintException("Field " + field.getName() +
1888                            " cannot be printed as the value " + value +
1889                            " cannot be negative according to the SignStyle");
1890                }
1891            }
1892            for (int i = 0; i < minWidth - str.length(); i++) {
1893                buf.append(symbols.getZeroDigit());
1894            }
1895            buf.append(str);
1896            return true;
1897        }
1898
1899        /**
1900         * Gets the value to output.
1901         *
1902         * @param value  the base value of the field, not null
1903         * @return the value
1904         */
1905        long getValue(long value) {
1906            return value;
1907        }
1908
1909        boolean isFixedWidth() {
1910            return subsequentWidth == -1;
1911        }
1912
1913        @Override
1914        public int parse(DateTimeParseContext context, CharSequence text, int position) {
1915            int length = text.length();
1916            if (position == length) {
1917                return ~position;
1918            }
1919            char sign = text.charAt(position);  // IOOBE if invalid position
1920            boolean negative = false;
1921            boolean positive = false;
1922            if (sign == context.getSymbols().getPositiveSign()) {
1923                if (signStyle.parse(true, context.isStrict(), minWidth == maxWidth) == false) {
1924                    return ~position;
1925                }
1926                positive = true;
1927                position++;
1928            } else if (sign == context.getSymbols().getNegativeSign()) {
1929                if (signStyle.parse(false, context.isStrict(), minWidth == maxWidth) == false) {
1930                    return ~position;
1931                }
1932                negative = true;
1933                position++;
1934            } else {
1935                if (signStyle == SignStyle.ALWAYS && context.isStrict()) {
1936                    return ~position;
1937                }
1938            }
1939            int effMinWidth = (context.isStrict() || isFixedWidth() ? minWidth : 1);
1940            int minEndPos = position + effMinWidth;
1941            if (minEndPos > length) {
1942                return ~position;
1943            }
1944            int effMaxWidth = maxWidth + Math.max(subsequentWidth, 0);
1945            long total = 0;
1946            BigInteger totalBig = null;
1947            int pos = position;
1948            for (int pass = 0; pass < 2; pass++) {
1949                int maxEndPos = Math.min(pos + effMaxWidth, length);
1950                while (pos < maxEndPos) {
1951                    char ch = text.charAt(pos++);
1952                    int digit = context.getSymbols().convertToDigit(ch);
1953                    if (digit < 0) {
1954                        pos--;
1955                        if (pos < minEndPos) {
1956                            return ~position;  // need at least min width digits
1957                        }
1958                        break;
1959                    }
1960                    if ((pos - position) > 18) {
1961                        if (totalBig == null) {
1962                            totalBig = BigInteger.valueOf(total);
1963                        }
1964                        totalBig = totalBig.multiply(BigInteger.TEN).add(BigInteger.valueOf(digit));
1965                    } else {
1966                        total = total * 10 + digit;
1967                    }
1968                }
1969                if (subsequentWidth > 0 && pass == 0) {
1970                    // re-parse now we know the correct width
1971                    int parseLen = pos - position;
1972                    effMaxWidth = Math.max(effMinWidth, parseLen - subsequentWidth);
1973                    pos = position;
1974                    total = 0;
1975                    totalBig = null;
1976                } else {
1977                    break;
1978                }
1979            }
1980            if (negative) {
1981                if (totalBig != null) {
1982                    if (totalBig.equals(BigInteger.ZERO) && context.isStrict()) {
1983                        return ~(position - 1);  // minus zero not allowed
1984                    }
1985                    totalBig = totalBig.negate();
1986                } else {
1987                    if (total == 0 && context.isStrict()) {
1988                        return ~(position - 1);  // minus zero not allowed
1989                    }
1990                    total = -total;
1991                }
1992            } else if (signStyle == SignStyle.EXCEEDS_PAD && context.isStrict()) {
1993                int parseLen = pos - position;
1994                if (positive) {
1995                    if (parseLen <= minWidth) {
1996                        return ~(position - 1);  // '+' only parsed if minWidth exceeded
1997                    }
1998                } else {
1999                    if (parseLen > minWidth) {
2000                        return ~position;  // '+' must be parsed if minWidth exceeded
2001                    }
2002                }
2003            }
2004            if (totalBig != null) {
2005                if (totalBig.bitLength() > 63) {
2006                    // overflow, parse 1 less digit
2007                    totalBig = totalBig.divide(BigInteger.TEN);
2008                    pos--;
2009                }
2010                setValue(context, totalBig.longValue());
2011            } else {
2012                setValue(context, total);
2013            }
2014            return pos;
2015        }
2016
2017        /**
2018         * Stores the value.
2019         *
2020         * @param context  the context to store into, not null
2021         * @param value  the value
2022         */
2023        void setValue(DateTimeParseContext context, long value) {
2024            context.setParsedField(field, value);
2025        }
2026
2027        @Override
2028        public String toString() {
2029            if (minWidth == 1 && maxWidth == 19 && signStyle == SignStyle.NORMAL) {
2030                return "Value(" + field.getName() + ")";
2031            }
2032            if (minWidth == maxWidth && signStyle == SignStyle.NOT_NEGATIVE) {
2033                return "Value(" + field.getName() + "," + minWidth + ")";
2034            }
2035            return "Value(" + field.getName() + "," + minWidth + "," + maxWidth + "," + signStyle + ")";
2036        }
2037    }
2038
2039    //-----------------------------------------------------------------------
2040    /**
2041     * Prints and parses a reduced numeric date-time field.
2042     */
2043    static final class ReducedPrinterParser extends NumberPrinterParser {
2044        private final int baseValue;
2045        private final int range;
2046
2047        /**
2048         * Constructor.
2049         *
2050         * @param field  the field to print, validated not null
2051         * @param width  the field width, from 1 to 18
2052         * @param baseValue  the base value
2053         */
2054        ReducedPrinterParser(TemporalField field, int width, int baseValue) {
2055            super(field, width, width, SignStyle.NOT_NEGATIVE);
2056            if (width < 1 || width > 18) {
2057                throw new IllegalArgumentException("The width must be from 1 to 18 inclusive but was " + width);
2058            }
2059            if (field.range().isValidValue(baseValue) == false) {
2060                throw new IllegalArgumentException("The base value must be within the range of the field");
2061            }
2062            this.baseValue = baseValue;
2063            this.range = EXCEED_POINTS[width];
2064            if ((((long) baseValue) + range) > Integer.MAX_VALUE) {
2065                throw new DateTimeException("Unable to add printer-parser as the range exceeds the capacity of an int");
2066            }
2067        }
2068
2069        @Override
2070        long getValue(long value) {
2071            return Math.abs(value % range);
2072        }
2073
2074        @Override
2075        void setValue(DateTimeParseContext context, long value) {
2076            int lastPart = baseValue % range;
2077            if (baseValue > 0) {
2078                value = baseValue - lastPart + value;
2079            } else {
2080                value = baseValue - lastPart - value;
2081            }
2082            if (value < baseValue) {
2083                value += range;
2084            }
2085            context.setParsedField(field, value);
2086        }
2087
2088        @Override
2089        NumberPrinterParser withFixedWidth() {
2090            return this;
2091        }
2092
2093        @Override
2094        boolean isFixedWidth() {
2095            return true;
2096        }
2097
2098        @Override
2099        public String toString() {
2100            return "ReducedValue(" + field.getName() + "," + minWidth + "," + baseValue + ")";
2101        }
2102    }
2103
2104    //-----------------------------------------------------------------------
2105    /**
2106     * Prints and parses a numeric date-time field with optional padding.
2107     */
2108    static final class FractionPrinterParser implements DateTimePrinterParser {
2109        private final TemporalField field;
2110        private final int minWidth;
2111        private final int maxWidth;
2112        private final boolean decimalPoint;
2113
2114        /**
2115         * Constructor.
2116         *
2117         * @param field  the field to output, not null
2118         * @param minWidth  the minimum width to output, from 0 to 9
2119         * @param maxWidth  the maximum width to output, from 0 to 9
2120         * @param decimalPoint  whether to output the localized decimal point symbol
2121         */
2122        FractionPrinterParser(TemporalField field, int minWidth, int maxWidth, boolean decimalPoint) {
2123            Objects.requireNonNull(field, "field");
2124            if (field.range().isFixed() == false) {
2125                throw new IllegalArgumentException("Field must have a fixed set of values: " + field.getName());
2126            }
2127            if (minWidth < 0 || minWidth > 9) {
2128                throw new IllegalArgumentException("Minimum width must be from 0 to 9 inclusive but was " + minWidth);
2129            }
2130            if (maxWidth < 1 || maxWidth > 9) {
2131                throw new IllegalArgumentException("Maximum width must be from 1 to 9 inclusive but was " + maxWidth);
2132            }
2133            if (maxWidth < minWidth) {
2134                throw new IllegalArgumentException("Maximum width must exceed or equal the minimum width but " +
2135                        maxWidth + " < " + minWidth);
2136            }
2137            this.field = field;
2138            this.minWidth = minWidth;
2139            this.maxWidth = maxWidth;
2140            this.decimalPoint = decimalPoint;
2141        }
2142
2143        @Override
2144        public boolean print(DateTimePrintContext context, StringBuilder buf) {
2145            Long value = context.getValue(field);
2146            if (value == null) {
2147                return false;
2148            }
2149            DateTimeFormatSymbols symbols = context.getSymbols();
2150            BigDecimal fraction = convertToFraction(value);
2151            if (fraction.scale() == 0) {  // scale is zero if value is zero
2152                if (minWidth > 0) {
2153                    if (decimalPoint) {
2154                        buf.append(symbols.getDecimalSeparator());
2155                    }
2156                    for (int i = 0; i < minWidth; i++) {
2157                        buf.append(symbols.getZeroDigit());
2158                    }
2159                }
2160            } else {
2161                int outputScale = Math.min(Math.max(fraction.scale(), minWidth), maxWidth);
2162                fraction = fraction.setScale(outputScale, RoundingMode.FLOOR);
2163                String str = fraction.toPlainString().substring(2);
2164                str = symbols.convertNumberToI18N(str);
2165                if (decimalPoint) {
2166                    buf.append(symbols.getDecimalSeparator());
2167                }
2168                buf.append(str);
2169            }
2170            return true;
2171        }
2172
2173        @Override
2174        public int parse(DateTimeParseContext context, CharSequence text, int position) {
2175            int effectiveMin = (context.isStrict() ? minWidth : 0);
2176            int effectiveMax = (context.isStrict() ? maxWidth : 9);
2177            int length = text.length();
2178            if (position == length) {
2179                // valid if whole field is optional, invalid if minimum width
2180                return (effectiveMin > 0 ? ~position : position);
2181            }
2182            if (decimalPoint) {
2183                if (text.charAt(position) != context.getSymbols().getDecimalSeparator()) {
2184                    // valid if whole field is optional, invalid if minimum width
2185                    return (effectiveMin > 0 ? ~position : position);
2186                }
2187                position++;
2188            }
2189            int minEndPos = position + effectiveMin;
2190            if (minEndPos > length) {
2191                return ~position;  // need at least min width digits
2192            }
2193            int maxEndPos = Math.min(position + effectiveMax, length);
2194            int total = 0;  // can use int because we are only parsing up to 9 digits
2195            int pos = position;
2196            while (pos < maxEndPos) {
2197                char ch = text.charAt(pos++);
2198                int digit = context.getSymbols().convertToDigit(ch);
2199                if (digit < 0) {
2200                    if (pos < minEndPos) {
2201                        return ~position;  // need at least min width digits
2202                    }
2203                    pos--;
2204                    break;
2205                }
2206                total = total * 10 + digit;
2207            }
2208            BigDecimal fraction = new BigDecimal(total).movePointLeft(pos - position);
2209            long value = convertFromFraction(fraction);
2210            context.setParsedField(field, value);
2211            return pos;
2212        }
2213
2214        /**
2215         * Converts a value for this field to a fraction between 0 and 1.
2216         * <p>
2217         * The fractional value is between 0 (inclusive) and 1 (exclusive).
2218         * It can only be returned if the {@link TemporalField#range() value range} is fixed.
2219         * The fraction is obtained by calculation from the field range using 9 decimal
2220         * places and a rounding mode of {@link RoundingMode#FLOOR FLOOR}.
2221         * The calculation is inaccurate if the values do not run continuously from smallest to largest.
2222         * <p>
2223         * For example, the second-of-minute value of 15 would be returned as 0.25,
2224         * assuming the standard definition of 60 seconds in a minute.
2225         *
2226         * @param value  the value to convert, must be valid for this rule
2227         * @return the value as a fraction within the range, from 0 to 1, not null
2228         * @throws DateTimeException if the value cannot be converted to a fraction
2229         */
2230        private BigDecimal convertToFraction(long value) {
2231            ValueRange range = field.range();
2232            range.checkValidValue(value, field);
2233            BigDecimal minBD = BigDecimal.valueOf(range.getMinimum());
2234            BigDecimal rangeBD = BigDecimal.valueOf(range.getMaximum()).subtract(minBD).add(BigDecimal.ONE);
2235            BigDecimal valueBD = BigDecimal.valueOf(value).subtract(minBD);
2236            BigDecimal fraction = valueBD.divide(rangeBD, 9, RoundingMode.FLOOR);
2237            // stripTrailingZeros bug
2238            return fraction.compareTo(BigDecimal.ZERO) == 0 ? BigDecimal.ZERO : fraction.stripTrailingZeros();
2239        }
2240
2241        /**
2242         * Converts a fraction from 0 to 1 for this field to a value.
2243         * <p>
2244         * The fractional value must be between 0 (inclusive) and 1 (exclusive).
2245         * It can only be returned if the {@link TemporalField#range() value range} is fixed.
2246         * The value is obtained by calculation from the field range and a rounding
2247         * mode of {@link RoundingMode#FLOOR FLOOR}.
2248         * The calculation is inaccurate if the values do not run continuously from smallest to largest.
2249         * <p>
2250         * For example, the fractional second-of-minute of 0.25 would be converted to 15,
2251         * assuming the standard definition of 60 seconds in a minute.
2252         *
2253         * @param fraction  the fraction to convert, not null
2254         * @return the value of the field, valid for this rule
2255         * @throws DateTimeException if the value cannot be converted
2256         */
2257        private long convertFromFraction(BigDecimal fraction) {
2258            ValueRange range = field.range();
2259            BigDecimal minBD = BigDecimal.valueOf(range.getMinimum());
2260            BigDecimal rangeBD = BigDecimal.valueOf(range.getMaximum()).subtract(minBD).add(BigDecimal.ONE);
2261            BigDecimal valueBD = fraction.multiply(rangeBD).setScale(0, RoundingMode.FLOOR).add(minBD);
2262            return valueBD.longValueExact();
2263        }
2264
2265        @Override
2266        public String toString() {
2267            String decimal = (decimalPoint ? ",DecimalPoint" : "");
2268            return "Fraction(" + field.getName() + "," + minWidth + "," + maxWidth + decimal + ")";
2269        }
2270    }
2271
2272    //-----------------------------------------------------------------------
2273    /**
2274     * Prints or parses field text.
2275     */
2276    static final class TextPrinterParser implements DateTimePrinterParser {
2277        private final TemporalField field;
2278        private final TextStyle textStyle;
2279        private final DateTimeTextProvider provider;
2280        /**
2281         * The cached number printer parser.
2282         * Immutable and volatile, so no synchronization needed.
2283         */
2284        private volatile NumberPrinterParser numberPrinterParser;
2285
2286        /**
2287         * Constructor.
2288         *
2289         * @param field  the field to output, not null
2290         * @param textStyle  the text style, not null
2291         * @param provider  the text provider, not null
2292         */
2293        TextPrinterParser(TemporalField field, TextStyle textStyle, DateTimeTextProvider provider) {
2294            // validated by caller
2295            this.field = field;
2296            this.textStyle = textStyle;
2297            this.provider = provider;
2298        }
2299
2300        @Override
2301        public boolean print(DateTimePrintContext context, StringBuilder buf) {
2302            Long value = context.getValue(field);
2303            if (value == null) {
2304                return false;
2305            }
2306            String text = provider.getText(field, value, textStyle, context.getLocale());
2307            if (text == null) {
2308                return numberPrinterParser().print(context, buf);
2309            }
2310            buf.append(text);
2311            return true;
2312        }
2313
2314        @Override
2315        public int parse(DateTimeParseContext context, CharSequence parseText, int position) {
2316            int length = parseText.length();
2317            if (position < 0 || position > length) {
2318                throw new IndexOutOfBoundsException();
2319            }
2320            TextStyle style = (context.isStrict() ? textStyle : null);
2321            Iterator<Entry<String, Long>> it = provider.getTextIterator(field, style, context.getLocale());
2322            if (it != null) {
2323                while (it.hasNext()) {
2324                    Entry<String, Long> entry = it.next();
2325                    String itText = entry.getKey();
2326                    if (context.subSequenceEquals(itText, 0, parseText, position, itText.length())) {
2327                        context.setParsedField(field, entry.getValue());
2328                        return position + itText.length();
2329                    }
2330                }
2331                if (context.isStrict()) {
2332                    return ~position;
2333                }
2334            }
2335            return numberPrinterParser().parse(context, parseText, position);
2336        }
2337
2338        /**
2339         * Create and cache a number printer parser.
2340         * @return the number printer parser for this field, not null
2341         */
2342        private NumberPrinterParser numberPrinterParser() {
2343            if (numberPrinterParser == null) {
2344                numberPrinterParser = new NumberPrinterParser(field, 1, 19, SignStyle.NORMAL);
2345            }
2346            return numberPrinterParser;
2347        }
2348
2349        @Override
2350        public String toString() {
2351            if (textStyle == TextStyle.FULL) {
2352                return "Text(" + field.getName() + ")";
2353            }
2354            return "Text(" + field.getName() + "," + textStyle + ")";
2355        }
2356    }
2357
2358    //-----------------------------------------------------------------------
2359    /**
2360     * Prints or parses an ISO-8601 instant.
2361     */
2362    static final class InstantPrinterParser implements DateTimePrinterParser {
2363        // days in a 400 year cycle = 146097
2364        // days in a 10,000 year cycle = 146097 * 25
2365        // seconds per day = 86400
2366        private static final long SECONDS_PER_10000_YEARS = 146097L * 25L * 86400L;
2367        private static final long SECONDS_0000_TO_1970 = ((146097L * 5L) - (30L * 365L + 7L)) * 86400L;
2368        private static final CompositePrinterParser PARSER = new DateTimeFormatterBuilder()
2369                    .parseCaseInsensitive()
2370                    .append(DateTimeFormatters.isoLocalDate()).appendLiteral('T')
2371                    .append(DateTimeFormatters.isoLocalTime()).appendLiteral('Z')
2372                    .toFormatter().toPrinterParser(false);
2373
2374        InstantPrinterParser() {
2375        }
2376
2377        @Override
2378        public boolean print(DateTimePrintContext context, StringBuilder buf) {
2379            // use INSTANT_SECONDS, thus this code is not bound by Instant.MAX
2380            Long inSecs = context.getValue(INSTANT_SECONDS);
2381            Long inNanos = context.getValue(NANO_OF_SECOND);
2382            if (inSecs == null || inNanos == null) {
2383                return false;
2384            }
2385            long inSec = inSecs;
2386            int inNano = NANO_OF_SECOND.checkValidIntValue(inNanos);
2387            if (inSec >= -SECONDS_0000_TO_1970) {
2388                // current era
2389                long zeroSecs = inSec - SECONDS_PER_10000_YEARS + SECONDS_0000_TO_1970;
2390                long hi = Jdk8Methods.floorDiv(zeroSecs, SECONDS_PER_10000_YEARS) + 1;
2391                long lo = Jdk8Methods.floorMod(zeroSecs, SECONDS_PER_10000_YEARS);
2392                LocalDateTime ldt = LocalDateTime.ofEpochSecond(lo - SECONDS_0000_TO_1970, inNano, ZoneOffset.UTC);
2393                if (hi > 0) {
2394                    buf.append('+').append(hi);
2395                }
2396                buf.append(ldt).append('Z');
2397            } else {
2398                // before current era
2399                long zeroSecs = inSec + SECONDS_0000_TO_1970;
2400                long hi = zeroSecs / SECONDS_PER_10000_YEARS;
2401                long lo = zeroSecs % SECONDS_PER_10000_YEARS;
2402                LocalDateTime ldt = LocalDateTime.ofEpochSecond(lo - SECONDS_0000_TO_1970, inNano, ZoneOffset.UTC);
2403                int pos = buf.length();
2404                buf.append(ldt).append('Z');
2405                if (hi < 0) {
2406                    if (ldt.getYear() == -10_000) {
2407                        buf.replace(pos, pos + 2, Long.toString(hi - 1));
2408                    } else if (lo == 0) {
2409                        buf.insert(pos, hi);
2410                    } else {
2411                        buf.insert(pos + 1, Math.abs(hi));
2412                    }
2413                }
2414            }
2415            return true;
2416        }
2417
2418        @Override
2419        public int parse(DateTimeParseContext context, CharSequence text, int position) {
2420            // new context to avoid overwriting fields like year/month/day
2421            DateTimeParseContext newContext = context.copy();
2422            int pos = PARSER.parse(newContext, text, position);
2423            if (pos < 0) {
2424                return pos;
2425            }
2426            // parser restricts most fields to 2 digits, so definitely int
2427            // correctly parsed nano is also guaranteed to be valid
2428            long yearParsed = newContext.getParsed(YEAR);
2429            int month = newContext.getParsed(MONTH_OF_YEAR).intValue();
2430            int day = newContext.getParsed(DAY_OF_MONTH).intValue();
2431            int hour = newContext.getParsed(HOUR_OF_DAY).intValue();
2432            int min = newContext.getParsed(MINUTE_OF_HOUR).intValue();
2433            Long secVal = newContext.getParsed(SECOND_OF_MINUTE);
2434            Long nanoVal = newContext.getParsed(NANO_OF_SECOND);
2435            int sec = (secVal != null ? secVal.intValue() : 0);
2436            int nano = (nanoVal != null ? nanoVal.intValue() : 0);
2437            int year = (int) yearParsed % 10_000;
2438            long instantSecs;
2439            try {
2440                LocalDateTime ldt = LocalDateTime.of(year, month, day, hour, min, sec, 0);
2441                instantSecs = ldt.toEpochSecond(ZoneOffset.UTC);
2442                instantSecs += Jdk8Methods.safeMultiply(yearParsed / 10_000L, SECONDS_PER_10000_YEARS);
2443            } catch (RuntimeException ex) {
2444                return ~position;
2445            }
2446            context.setParsedField(INSTANT_SECONDS, instantSecs);
2447            context.setParsedField(NANO_OF_SECOND, nano);
2448            return text.length();
2449        }
2450
2451        @Override
2452        public String toString() {
2453            return "Instant()";
2454        }
2455    }
2456
2457    //-----------------------------------------------------------------------
2458    /**
2459     * Prints or parses an offset ID.
2460     */
2461    static final class OffsetIdPrinterParser implements DateTimePrinterParser {
2462        static final String[] PATTERNS = new String[] {
2463            "+HH", "+HHMM", "+HH:MM", "+HHMMss", "+HH:MM:ss", "+HHMMSS", "+HH:MM:SS",
2464        };  // order used in pattern builder
2465        static final OffsetIdPrinterParser INSTANCE_ID = new OffsetIdPrinterParser("Z", "+HH:MM:ss");
2466
2467        private final String noOffsetText;
2468        private final int type;
2469
2470        /**
2471         * Constructor.
2472         *
2473         * @param noOffsetText  the text to use for UTC, not null
2474         * @param pattern  the pattern
2475         */
2476        OffsetIdPrinterParser(String noOffsetText, String pattern) {
2477            Objects.requireNonNull(noOffsetText, "noOffsetText");
2478            Objects.requireNonNull(pattern, "pattern");
2479            this.noOffsetText = noOffsetText;
2480            this.type = checkPattern(pattern);
2481        }
2482
2483        private int checkPattern(String pattern) {
2484            for (int i = 0; i < PATTERNS.length; i++) {
2485                if (PATTERNS[i].equals(pattern)) {
2486                    return i;
2487                }
2488            }
2489            throw new IllegalArgumentException("Invalid zone offset pattern: " + pattern);
2490        }
2491
2492        @Override
2493        public boolean print(DateTimePrintContext context, StringBuilder buf) {
2494            Long offsetSecs = context.getValue(OFFSET_SECONDS);
2495            if (offsetSecs == null) {
2496                return false;
2497            }
2498            int totalSecs = Jdk8Methods.safeToInt(offsetSecs);
2499            if (totalSecs == 0) {
2500                buf.append(noOffsetText);
2501            } else {
2502                int absHours = Math.abs((totalSecs / 3600) % 100);  // anything larger than 99 silently dropped
2503                int absMinutes = Math.abs((totalSecs / 60) % 60);
2504                int absSeconds = Math.abs(totalSecs % 60);
2505                buf.append(totalSecs < 0 ? "-" : "+")
2506                    .append((char) (absHours / 10 + '0')).append((char) (absHours % 10 + '0'));
2507                if (type >= 1) {
2508                    buf.append((type % 2) == 0 ? ":" : "")
2509                        .append((char) (absMinutes / 10 + '0')).append((char) (absMinutes % 10 + '0'));
2510                    if (type >= 5 || (type >= 3 && absSeconds > 0)) {
2511                        buf.append((type % 2) == 0 ? ":" : "")
2512                            .append((char) (absSeconds / 10 + '0')).append((char) (absSeconds % 10 + '0'));
2513                    }
2514                }
2515            }
2516            return true;
2517        }
2518
2519        @Override
2520        public int parse(DateTimeParseContext context, CharSequence text, int position) {
2521            int length = text.length();
2522            int noOffsetLen = noOffsetText.length();
2523            if (noOffsetLen == 0) {
2524                if (position == length) {
2525                    context.setParsedField(OFFSET_SECONDS, 0);
2526                    return position;
2527                }
2528            } else {
2529                if (position == length) {
2530                    return ~position;
2531                }
2532                if (context.subSequenceEquals(text, position, noOffsetText, 0, noOffsetLen)) {
2533                    context.setParsedField(OFFSET_SECONDS, 0);
2534                    return position + noOffsetLen;
2535                }
2536            }
2537
2538            // parse normal plus/minus offset
2539            char sign = text.charAt(position);  // IOOBE if invalid position
2540            if (sign == '+' || sign == '-') {
2541                // starts
2542                int negative = (sign == '-' ? -1 : 1);
2543                int[] array = new int[4];
2544                array[0] = position + 1;
2545                if (parseNumber(array, 1, text, true) ||
2546                        parseNumber(array, 2, text, type > 0) ||
2547                        parseNumber(array, 3, text, false)) {
2548                    return ~position;
2549                }
2550                long offsetSecs = negative * (array[1] * 3600L + array[2] * 60L + array[3]);
2551                context.setParsedField(OFFSET_SECONDS, offsetSecs);
2552                return array[0];
2553            } else {
2554                // handle special case of empty no offset text
2555                if (noOffsetLen == 0) {
2556                    context.setParsedField(OFFSET_SECONDS, 0);
2557                    return position + noOffsetLen;
2558                }
2559                return ~position;
2560            }
2561        }
2562
2563        /**
2564         * Parse a two digit zero-prefixed number.
2565         *
2566         * @param array  the array of parsed data, 0=pos,1=hours,2=mins,3=secs, not null
2567         * @param arrayIndex  the index to parse the value into
2568         * @param parseText  the offset ID, not null
2569         * @param required  whether this number is required
2570         * @return true if an error occurred
2571         */
2572        private boolean parseNumber(int[] array, int arrayIndex, CharSequence parseText, boolean required) {
2573            if ((type + 3) / 2 < arrayIndex) {
2574                return false;  // ignore seconds/minutes
2575            }
2576            int pos = array[0];
2577            if ((type % 2) == 0 && arrayIndex > 1) {
2578                if (pos + 1 > parseText.length() || parseText.charAt(pos) != ':') {
2579                    return required;
2580                }
2581                pos++;
2582            }
2583            if (pos + 2 > parseText.length()) {
2584                return required;
2585            }
2586            char ch1 = parseText.charAt(pos++);
2587            char ch2 = parseText.charAt(pos++);
2588            if (ch1 < '0' || ch1 > '9' || ch2 < '0' || ch2 > '9') {
2589                return required;
2590            }
2591            int value = (ch1 - 48) * 10 + (ch2 - 48);
2592            if (value < 0 || value > 59) {
2593                return required;
2594            }
2595            array[arrayIndex] = value;
2596            array[0] = pos;
2597            return false;
2598        }
2599
2600        @Override
2601        public String toString() {
2602            String converted = noOffsetText.replace("'", "''");
2603            return "Offset('" + converted + "'," + PATTERNS[type] + ")";
2604        }
2605    }
2606
2607    //-----------------------------------------------------------------------
2608    /**
2609     * Prints or parses a zone ID.
2610     */
2611    static final class ZoneTextPrinterParser implements DateTimePrinterParser {
2612        // TODO: remove this as it is incomplete
2613        /** The text style to output. */
2614        private final TextStyle textStyle;
2615
2616        ZoneTextPrinterParser(TextStyle textStyle) {
2617            this.textStyle = Objects.requireNonNull(textStyle, "textStyle");
2618        }
2619
2620        //-----------------------------------------------------------------------
2621        @Override
2622        public boolean print(DateTimePrintContext context, StringBuilder buf) {
2623            ZoneId zone = context.getValue(TemporalQueries.zoneId());
2624            if (zone == null) {
2625                return false;
2626            }
2627            // TODO: fix getText(textStyle, context.getLocale())
2628            buf.append(zone.getId());  // TODO: Use symbols
2629            return true;
2630        }
2631
2632        @Override
2633        public int parse(DateTimeParseContext context, CharSequence text, int position) {
2634            throw new UnsupportedOperationException();
2635        }
2636
2637        @Override
2638        public String toString() {
2639            return "ZoneText(" + textStyle + ")";
2640        }
2641    }
2642
2643    //-----------------------------------------------------------------------
2644    /**
2645     * Prints or parses a zone ID.
2646     */
2647    static final class ZoneIdPrinterParser implements DateTimePrinterParser {
2648        private final TemporalQuery<ZoneId> query;
2649        private final String description;
2650
2651        ZoneIdPrinterParser(TemporalQuery<ZoneId> query, String description) {
2652            this.query = query;
2653            this.description = description;
2654        }
2655
2656        //-----------------------------------------------------------------------
2657        @Override
2658        public boolean print(DateTimePrintContext context, StringBuilder buf) {
2659            ZoneId zone = context.getValue(query);
2660            if (zone == null) {
2661                return false;
2662            }
2663            buf.append(zone.getId());
2664            return true;
2665        }
2666
2667        //-----------------------------------------------------------------------
2668        /**
2669         * The cached tree to speed up parsing.
2670         */
2671        private static volatile Entry<Integer, SubstringTree> cachedSubstringTree;
2672
2673        /**
2674         * This implementation looks for the longest matching string.
2675         * For example, parsing Etc/GMT-2 will return Etc/GMC-2 rather than just
2676         * Etc/GMC although both are valid.
2677         * <p>
2678         * This implementation uses a tree to search for valid time-zone names in
2679         * the parseText. The top level node of the tree has a length equal to the
2680         * length of the shortest time-zone as well as the beginning characters of
2681         * all other time-zones.
2682         */
2683        @Override
2684        public int parse(DateTimeParseContext context, CharSequence text, int position) {
2685            // TODO case insensitive?
2686            int length = text.length();
2687            if (position > length) {
2688                throw new IndexOutOfBoundsException();
2689            }
2690            if (position == length) {
2691                return ~position;
2692            }
2693
2694            // handle fixed time-zone IDs
2695            char nextChar = text.charAt(position);
2696            if (nextChar == '+' || nextChar == '-') {
2697                DateTimeParseContext newContext = context.copy();
2698                int endPos = OffsetIdPrinterParser.INSTANCE_ID.parse(newContext, text, position);
2699                if (endPos < 0) {
2700                    return endPos;
2701                }
2702                int offset = (int) newContext.getParsed(OFFSET_SECONDS).longValue();
2703                ZoneId zone = ZoneOffset.ofTotalSeconds(offset);
2704                context.setParsed(zone);
2705                return endPos;
2706            } else if (length >= position + 2) {
2707                char nextNextChar = text.charAt(position + 1);
2708                if (nextChar == 'U' && nextNextChar == 'T') {
2709                    if (length >= position + 3 && text.charAt(position + 2) == 'C') {
2710                        return parsePrefixedOffset(context, text, position + 3);
2711                    }
2712                    return parsePrefixedOffset(context, text, position + 2);
2713                } else if (nextChar == 'G' && length >= position + 3 &&
2714                        nextNextChar == 'M' && text.charAt(position + 2) == 'T') {
2715                    return parsePrefixedOffset(context, text, position + 3);
2716                }
2717            }
2718
2719            // prepare parse tree
2720            Set<String> regionIds = ZoneRulesProvider.getAvailableZoneIds();
2721            final int regionIdsSize = regionIds.size();
2722            Entry<Integer, SubstringTree> cached = cachedSubstringTree;
2723            if (cached == null || cached.getKey() != regionIdsSize) {
2724                synchronized (this) {
2725                    cached = cachedSubstringTree;
2726                    if (cached == null || cached.getKey() != regionIdsSize) {
2727                        cachedSubstringTree = cached = new SimpleImmutableEntry<>(regionIdsSize, prepareParser(regionIds));
2728                    }
2729                }
2730            }
2731            SubstringTree tree = cached.getValue();
2732
2733            // parse
2734            String parsedZoneId = null;
2735            while (tree != null) {
2736                int nodeLength = tree.length;
2737                if (position + nodeLength > length) {
2738                    break;
2739                }
2740                parsedZoneId = text.subSequence(position, position + nodeLength).toString();
2741                tree = tree.get(parsedZoneId);
2742            }
2743
2744            if (parsedZoneId == null || regionIds.contains(parsedZoneId) == false) {
2745                if (nextChar == 'Z') {
2746                    context.setParsed(ZoneOffset.UTC);
2747                    return position + 1;
2748                }
2749                return ~position;
2750            }
2751            context.setParsed(ZoneId.of(parsedZoneId));
2752            return position + parsedZoneId.length();
2753        }
2754
2755        private int parsePrefixedOffset(DateTimeParseContext context, CharSequence text, int position) {
2756            DateTimeParseContext newContext = context.copy();
2757            int endPos = OffsetIdPrinterParser.INSTANCE_ID.parse(newContext, text, position);
2758            if (endPos < 0) {
2759                context.setParsed(ZoneOffset.UTC);
2760                return position;
2761            }
2762            int offset = (int) newContext.getParsed(OFFSET_SECONDS).longValue();
2763            ZoneId zone = ZoneOffset.ofTotalSeconds(offset);
2764            context.setParsed(zone);
2765            return endPos;
2766        }
2767
2768        //-----------------------------------------------------------------------
2769        /**
2770         * Model a tree of substrings to make the parsing easier. Due to the nature
2771         * of time-zone names, it can be faster to parse based in unique substrings
2772         * rather than just a character by character match.
2773         * <p>
2774         * For example, to parse America/Denver we can look at the first two
2775         * character "Am". We then notice that the shortest time-zone that starts
2776         * with Am is America/Nome which is 12 characters long. Checking the first
2777         * 12 characters of America/Denver gives America/Denv which is a substring
2778         * of only 1 time-zone: America/Denver. Thus, with just 3 comparisons that
2779         * match can be found.
2780         * <p>
2781         * This structure maps substrings to substrings of a longer length. Each
2782         * node of the tree contains a length and a map of valid substrings to
2783         * sub-nodes. The parser gets the length from the root node. It then
2784         * extracts a substring of that length from the parseText. If the map
2785         * contains the substring, it is set as the possible time-zone and the
2786         * sub-node for that substring is retrieved. The process continues until the
2787         * substring is no longer found, at which point the matched text is checked
2788         * against the real time-zones.
2789         */
2790        private static final class SubstringTree {
2791            /**
2792             * The length of the substring this node of the tree contains.
2793             * Subtrees will have a longer length.
2794             */
2795            final int length;
2796            /**
2797             * Map of a substring to a set of substrings that contain the key.
2798             */
2799            private final Map<CharSequence, SubstringTree> substringMap = new HashMap<>();
2800
2801            /**
2802             * Constructor.
2803             *
2804             * @param length  the length of this tree
2805             */
2806            private SubstringTree(int length) {
2807                this.length = length;
2808            }
2809
2810            private SubstringTree get(CharSequence substring2) {
2811                return substringMap.get(substring2);
2812
2813            }
2814
2815            /**
2816             * Values must be added from shortest to longest.
2817             *
2818             * @param newSubstring  the substring to add, not null
2819             */
2820            private void add(String newSubstring) {
2821                int idLen = newSubstring.length();
2822                if (idLen == length) {
2823                    substringMap.put(newSubstring, null);
2824                } else if (idLen > length) {
2825                    String substring = newSubstring.substring(0, length);
2826                    SubstringTree parserTree = substringMap.get(substring);
2827                    if (parserTree == null) {
2828                        parserTree = new SubstringTree(idLen);
2829                        substringMap.put(substring, parserTree);
2830                    }
2831                    parserTree.add(newSubstring);
2832                }
2833            }
2834        }
2835
2836        /**
2837         * Builds an optimized parsing tree.
2838         *
2839         * @param availableIDs  the available IDs, not null, not empty
2840         * @return the tree, not null
2841         */
2842        private static SubstringTree prepareParser(Set<String> availableIDs) {
2843            // sort by length
2844            List<String> ids = new ArrayList<>(availableIDs);
2845            Collections.sort(ids, LENGTH_SORT);
2846
2847            // build the tree
2848            SubstringTree tree = new SubstringTree(ids.get(0).length());
2849            for (String id : ids) {
2850                tree.add(id);
2851            }
2852            return tree;
2853        }
2854
2855        //-----------------------------------------------------------------------
2856        @Override
2857        public String toString() {
2858            return description;
2859        }
2860    }
2861
2862    //-----------------------------------------------------------------------
2863    /**
2864     * Prints or parses a chronology.
2865     */
2866    static final class ChronoPrinterParser implements DateTimePrinterParser {
2867        /** The text style to output, null means the ID. */
2868        private final TextStyle textStyle;
2869
2870        ChronoPrinterParser(TextStyle textStyle) {
2871            // validated by caller
2872            this.textStyle = textStyle;
2873        }
2874
2875        @Override
2876        public boolean print(DateTimePrintContext context, StringBuilder buf) {
2877            Chrono<?> chrono = context.getValue(TemporalQueries.chrono());
2878            if (chrono == null) {
2879                return false;
2880            }
2881            if (textStyle == null) {
2882                buf.append(chrono.getId());
2883            } else {
2884                buf.append(chrono.getId());  // TODO: Use symbols
2885            }
2886            return true;
2887        }
2888
2889        @Override
2890        public int parse(DateTimeParseContext context, CharSequence text, int position) {
2891            return ~position;  // TODO, including case insensitive
2892        }
2893    }
2894
2895    //-----------------------------------------------------------------------
2896    /**
2897     * Prints or parses a localized pattern.
2898     */
2899    static final class LocalizedPrinterParser implements DateTimePrinterParser {
2900        private final FormatStyle dateStyle;
2901        private final FormatStyle timeStyle;
2902        private final Chrono<?> chrono;
2903
2904        /**
2905         * Constructor.
2906         *
2907         * @param dateStyle  the date style to use, may be null
2908         * @param timeStyle  the time style to use, may be null
2909         * @param chrono  the chronology to use, not null
2910         */
2911        LocalizedPrinterParser(FormatStyle dateStyle, FormatStyle timeStyle, Chrono<?> chrono) {
2912            // validated by caller
2913            this.dateStyle = dateStyle;
2914            this.timeStyle = timeStyle;
2915            this.chrono = chrono;
2916        }
2917
2918        @Override
2919        public boolean print(DateTimePrintContext context, StringBuilder buf) {
2920            return formatter(context.getLocale()).toPrinterParser(false).print(context, buf);
2921        }
2922
2923        @Override
2924        public int parse(DateTimeParseContext context, CharSequence text, int position) {
2925            return formatter(context.getLocale()).toPrinterParser(false).parse(context, text, position);
2926        }
2927
2928        /**
2929         * Gets the formatter to use.
2930         *
2931         * @param locale  the locale to use, not null
2932         * @return the formatter, not null
2933         * @throws IllegalArgumentException if the formatter cannot be found
2934         */
2935        private DateTimeFormatter formatter(Locale locale) {
2936            return DateTimeFormatStyleProvider.getInstance()
2937                    .getFormatter(dateStyle, timeStyle, chrono, locale);
2938        }
2939
2940        @Override
2941        public String toString() {
2942            return "Localized(" + (dateStyle != null ? dateStyle : "") + "," +
2943                (timeStyle != null ? timeStyle : "") + "," + chrono.getId() + ")";
2944        }
2945    }
2946
2947    //-------------------------------------------------------------------------
2948    /**
2949     * Length comparator.
2950     */
2951    static final Comparator<String> LENGTH_SORT = new Comparator<String>() {
2952        @Override
2953        public int compare(String str1, String str2) {
2954            return str1.length() == str2.length() ? str1.compareTo(str2) : str1.length() - str2.length();
2955        }
2956    };
2957
2958}