1 /*
2  * hunt-time: A time library for D programming language.
3  *
4  * Copyright (C) 2015-2018 HuntLabs
5  *
6  * Website: https://www.huntlabs.net/
7  *
8  * Licensed under the Apache-2.0 License.
9  *
10  */
11 
12 module hunt.time.format.DateTimeFormatterBuilder;
13 
14 import hunt.time.format.NumberPrinterParser;
15 import hunt.time.format.OffsetIdPrinterParser;
16 
17 import hunt.time.chrono.ChronoLocalDate;
18 import hunt.time.chrono.ChronoLocalDateTime;
19 import hunt.time.chrono.Chronology;
20 import hunt.time.chrono.IsoChronology;
21 
22 import hunt.time.format.CharLiteralPrinterParser;
23 import hunt.time.format.ChronoPrinterParser;
24 import hunt.time.format.CompositePrinterParser;
25 import hunt.time.format.DateTimeFormatter;
26 import hunt.time.format.DateTimeParseContext;
27 import hunt.time.format.DateTimePrintContext;
28 import hunt.time.format.DateTimePrinterParser;
29 import hunt.time.format.DateTimeTextProvider;
30 import hunt.time.format.DecimalStyle;
31 import hunt.time.format.InstantPrinterParser;
32 import hunt.time.format.FractionPrinterParser;
33 import hunt.time.format.FormatStyle;
34 import hunt.time.format.LocalizedOffsetIdPrinterParser;
35 import hunt.time.format.LocalizedPrinterParser;
36 import hunt.time.format.NumberPrinterParser;
37 import hunt.time.format.PadPrinterParserDecorator;
38 import hunt.time.format.ReducedPrinterParser;
39 import hunt.time.format.ResolverStyle;
40 import hunt.time.format.SettingsParser;
41 import hunt.time.format.SignStyle;
42 import hunt.time.format.StringLiteralPrinterParser;
43 import hunt.time.format.TextPrinterParser;
44 import hunt.time.format.TextStyle;
45 import hunt.time.format.WeekBasedFieldPrinterParser;
46 import hunt.time.format.ZoneIdPrinterParser;
47 import hunt.time.format.ZoneTextPrinterParser;
48 
49 import hunt.time.temporal.ChronoField;
50 import hunt.time.temporal.TemporalAccessor;
51 import hunt.time.temporal.TemporalField;
52 import hunt.time.temporal.TemporalQuery;
53 import hunt.time.temporal.TemporalQueries;
54 
55 import hunt.time.util.Common;
56 import hunt.time.util.QueryHelper;
57 import hunt.time.ZoneId;
58 import hunt.time.ZoneOffset;
59 
60 import hunt.collection.ArrayList;
61 import hunt.collection.HashMap;
62 import hunt.collection.Iterator;
63 import hunt.collection.List;
64 import hunt.collection.LinkedHashMap;
65 import hunt.collection.Map;
66 import hunt.collection.Set;
67 import hunt.Exceptions;
68 import hunt.Long;
69 import hunt.text.Common;
70 import hunt.util.StringBuilder;
71 import hunt.util.Common;
72 import hunt.util.Comparator;
73 import hunt.util.Locale;
74 
75 import std.conv;
76 import std.concurrency : initOnce;
77 import hunt.time.temporal.IsoFields;
78 
79 
80 /**
81  * Builder to create date-time formatters.
82  * !(p)
83  * This allows a {@code DateTimeFormatter} to be created.
84  * All date-time formatters are created ultimately using this builder.
85  * !(p)
86  * The basic elements of date-time can all be added:
87  * !(ul)
88  * !(li)Value - a numeric value</li>
89  * !(li)Fraction - a fractional value including the decimal place. Always use this when
90  * outputting fractions to ensure that the fraction is parsed correctly</li>
91  * !(li)Text - the textual equivalent for the value</li>
92  * !(li)OffsetId/Offset - the {@linkplain ZoneOffset zone offset}</li>
93  * !(li)ZoneId - the {@linkplain ZoneId time-zone} id</li>
94  * !(li)ZoneText - the name of the time-zone</li>
95  * !(li)ChronologyId - the {@linkplain Chronology chronology} id</li>
96  * !(li)ChronologyText - the name of the chronology</li>
97  * !(li)Literal - a text literal</li>
98  * !(li)Nested and Optional - formats can be nested or made optional</li>
99  * </ul>
100  * In addition, any of the elements may be decorated by padding, either with spaces or any other character.
101  * !(p)
102  * Finally, a shorthand pattern, mostly compatible with {@code java.text.SimpleDateFormat SimpleDateFormat}
103  * can be used, see {@link #appendPattern(string)}.
104  * In practice, this simply parses the pattern and calls other methods on the builder.
105  *
106  * @implSpec
107  * This class is a mutable builder intended for use from a single thread.
108  *
109  * @since 1.8
110  */
111 public final class DateTimeFormatterBuilder {
112 
113 // dfmt off
114     //-----------------------------------------------------------------------
115     /**
116      * The ISO date formatter that formats or parses a date without an
117      * offset, such as '2011-12-03'.
118      * !(p)
119      * This returns an immutable formatter capable of formatting and parsing
120      * the ISO-8601 extended local date format.
121      * The format consists of:
122      * !(ul)
123      * !(li)Four digits or more for the {@link ChronoField#YEAR year}.
124      * Years _in the range 0000 to 9999 will be pre-padded by zero to ensure four digits.
125      * Years outside that range will have a prefixed positive or negative symbol.
126      * !(li)A dash
127      * !(li)Two digits for the {@link ChronoField#MONTH_OF_YEAR month-of-year}.
128      *  This is pre-padded by zero to ensure two digits.
129      * !(li)A dash
130      * !(li)Two digits for the {@link ChronoField#DAY_OF_MONTH day-of-month}.
131      *  This is pre-padded by zero to ensure two digits.
132      * </ul>
133      * !(p)
134      * The returned formatter has a chronology of ISO set to ensure dates _in
135      * other calendar systems are correctly converted.
136      * It has no override zone and uses the {@link ResolverStyle#STRICT STRICT} resolver style.
137      */
138     static DateTimeFormatter ISO_LOCAL_DATE() {
139         __gshared DateTimeFormatter _ISO_LOCAL_DATE ;
140         return initOnce!(_ISO_LOCAL_DATE)({
141             return new DateTimeFormatterBuilder()
142                 .appendValue(ChronoField.YEAR, 4, 10, SignStyle.EXCEEDS_PAD)
143                 .appendLiteral('-')
144                 .appendValue(ChronoField.MONTH_OF_YEAR, 2)
145                 .appendLiteral('-')
146                 .appendValue(ChronoField.DAY_OF_MONTH, 2)
147                 .toFormatter(ResolverStyle.STRICT, IsoChronology.INSTANCE);
148         }());
149     }
150   
151     //-----------------------------------------------------------------------
152     /**
153      * The ISO date formatter that formats or parses a date with an
154      * offset, such as '2011-12-03+01:00'.
155      * !(p)
156      * This returns an immutable formatter capable of formatting and parsing
157      * the ISO-8601 extended offset date format.
158      * The format consists of:
159      * !(ul)
160      * !(li)The {@link #ISO_LOCAL_DATE}
161      * !(li)The {@link ZoneOffset#getId() offset ID}. If the offset has seconds then
162      *  they will be handled even though this is not part of the ISO-8601 standard.
163      *  Parsing is case insensitive.
164      * </ul>
165      * !(p)
166      * The returned formatter has a chronology of ISO set to ensure dates _in
167      * other calendar systems are correctly converted.
168      * It has no override zone and uses the {@link ResolverStyle#STRICT STRICT} resolver style.
169      */
170     static DateTimeFormatter ISO_OFFSET_DATE() {
171         __gshared DateTimeFormatter _ISO_OFFSET_DATE ;
172         return initOnce!(_ISO_OFFSET_DATE)({
173             return new DateTimeFormatterBuilder()
174             .parseCaseInsensitive()
175             .append(ISO_LOCAL_DATE())
176             .appendOffsetId()
177             .toFormatter(ResolverStyle.STRICT, IsoChronology.INSTANCE);
178         }());
179     }
180  
181     //-----------------------------------------------------------------------
182     /**
183      * The ISO date formatter that formats or parses a date with the
184      * offset if available, such as '2011-12-03' or '2011-12-03+01:00'.
185      * !(p)
186      * This returns an immutable formatter capable of formatting and parsing
187      * the ISO-8601 extended date format.
188      * The format consists of:
189      * !(ul)
190      * !(li)The {@link #ISO_LOCAL_DATE}
191      * !(li)If the offset is not available then the format is complete.
192      * !(li)The {@link ZoneOffset#getId() offset ID}. If the offset has seconds then
193      *  they will be handled even though this is not part of the ISO-8601 standard.
194      *  Parsing is case insensitive.
195      * </ul>
196      * !(p)
197      * As this formatter has an optional element, it may be necessary to parse using
198      * {@link DateTimeFormatter#parseBest}.
199      * !(p)
200      * The returned formatter has a chronology of ISO set to ensure dates _in
201      * other calendar systems are correctly converted.
202      * It has no override zone and uses the {@link ResolverStyle#STRICT STRICT} resolver style.
203      */
204     static DateTimeFormatter ISO_DATE() {
205         __gshared DateTimeFormatter _ISO_DATE ;
206         return initOnce!(_ISO_DATE)({
207             return new DateTimeFormatterBuilder()
208             .parseCaseInsensitive()
209             .append(ISO_LOCAL_DATE())
210             .optionalStart()
211             .appendOffsetId()
212             .toFormatter(ResolverStyle.STRICT, IsoChronology.INSTANCE);
213         }());
214     }    
215     
216 
217     //-----------------------------------------------------------------------
218     /**
219      * The ISO time formatter that formats or parses a time without an
220      * offset, such as '10:15' or '10:15:30'.
221      * !(p)
222      * This returns an immutable formatter capable of formatting and parsing
223      * the ISO-8601 extended local time format.
224      * The format consists of:
225      * !(ul)
226      * !(li)Two digits for the {@link ChronoField#HOUR_OF_DAY hour-of-day}.
227      *  This is pre-padded by zero to ensure two digits.
228      * !(li)A colon
229      * !(li)Two digits for the {@link ChronoField#MINUTE_OF_HOUR minute-of-hour}.
230      *  This is pre-padded by zero to ensure two digits.
231      * !(li)If the second-of-minute is not available then the format is complete.
232      * !(li)A colon
233      * !(li)Two digits for the {@link ChronoField#SECOND_OF_MINUTE second-of-minute}.
234      *  This is pre-padded by zero to ensure two digits.
235      * !(li)If the nano-of-second is zero or not available then the format is complete.
236      * !(li)A decimal point
237      * !(li)One to nine digits for the {@link ChronoField#NANO_OF_SECOND nano-of-second}.
238      *  As many digits will be output as required.
239      * </ul>
240      * !(p)
241      * The returned formatter has no override chronology or zone.
242      * It uses the {@link ResolverStyle#STRICT STRICT} resolver style.
243      */
244     static DateTimeFormatter ISO_LOCAL_TIME() {
245         __gshared DateTimeFormatter _ISO_LOCAL_TIME ;
246         return initOnce!(_ISO_LOCAL_TIME)({
247             return new DateTimeFormatterBuilder()
248             .appendValue(ChronoField.HOUR_OF_DAY, 2)
249             .appendLiteral(':')
250             .appendValue(ChronoField.MINUTE_OF_HOUR, 2)
251             .optionalStart()
252             .appendLiteral(':')
253             .appendValue(ChronoField.SECOND_OF_MINUTE, 2)
254             .optionalStart()
255             .appendFraction(ChronoField.NANO_OF_SECOND, 0, 9, true)
256             .toFormatter(ResolverStyle.STRICT, null);
257         }());
258     }
259     
260     //-----------------------------------------------------------------------
261     /**
262      * The ISO time formatter that formats or parses a time with an
263      * offset, such as '10:15+01:00' or '10:15:30+01:00'.
264      * !(p)
265      * This returns an immutable formatter capable of formatting and parsing
266      * the ISO-8601 extended offset time format.
267      * The format consists of:
268      * !(ul)
269      * !(li)The {@link #ISO_LOCAL_TIME}
270      * !(li)The {@link ZoneOffset#getId() offset ID}. If the offset has seconds then
271      *  they will be handled even though this is not part of the ISO-8601 standard.
272      *  Parsing is case insensitive.
273      * </ul>
274      * !(p)
275      * The returned formatter has no override chronology or zone.
276      * It uses the {@link ResolverStyle#STRICT STRICT} resolver style.
277      */
278     static DateTimeFormatter ISO_OFFSET_TIME() {
279         __gshared DateTimeFormatter _ISO_OFFSET_TIME ;
280         return initOnce!(_ISO_OFFSET_TIME)({
281             return new DateTimeFormatterBuilder()
282             .parseCaseInsensitive()
283             .append(ISO_LOCAL_TIME())
284             .appendOffsetId()
285             .toFormatter(ResolverStyle.STRICT, null);
286         }());
287     }
288     
289 
290     //-----------------------------------------------------------------------
291     /**
292      * The ISO time formatter that formats or parses a time, with the
293      * offset if available, such as '10:15', '10:15:30' or '10:15:30+01:00'.
294      * !(p)
295      * This returns an immutable formatter capable of formatting and parsing
296      * the ISO-8601 extended offset time format.
297      * The format consists of:
298      * !(ul)
299      * !(li)The {@link #ISO_LOCAL_TIME}
300      * !(li)If the offset is not available then the format is complete.
301      * !(li)The {@link ZoneOffset#getId() offset ID}. If the offset has seconds then
302      *  they will be handled even though this is not part of the ISO-8601 standard.
303      *  Parsing is case insensitive.
304      * </ul>
305      * !(p)
306      * As this formatter has an optional element, it may be necessary to parse using
307      * {@link DateTimeFormatter#parseBest}.
308      * !(p)
309      * The returned formatter has no override chronology or zone.
310      * It uses the {@link ResolverStyle#STRICT STRICT} resolver style.
311      */
312     static DateTimeFormatter ISO_TIME() {
313         __gshared DateTimeFormatter _ISO_TIME ;
314         return initOnce!(_ISO_TIME)({
315             return new DateTimeFormatterBuilder()
316             .parseCaseInsensitive()
317             .append(ISO_LOCAL_TIME())
318             .optionalStart()
319             .appendOffsetId()
320             .toFormatter(ResolverStyle.STRICT, null);
321         }());
322     }
323     
324     //-----------------------------------------------------------------------
325     /**
326      * The ISO date-time formatter that formats or parses a date-time without
327      * an offset, such as '2011-12-03T10:15:30'.
328      * !(p)
329      * This returns an immutable formatter capable of formatting and parsing
330      * the ISO-8601 extended offset date-time format.
331      * The format consists of:
332      * !(ul)
333      * !(li)The {@link #ISO_LOCAL_DATE}
334      * !(li)The letter 'T'. Parsing is case insensitive.
335      * !(li)The {@link #ISO_LOCAL_TIME}
336      * </ul>
337      * !(p)
338      * The returned formatter has a chronology of ISO set to ensure dates _in
339      * other calendar systems are correctly converted.
340      * It has no override zone and uses the {@link ResolverStyle#STRICT STRICT} resolver style.
341      */
342     static DateTimeFormatter ISO_LOCAL_DATE_TIME() {
343         __gshared DateTimeFormatter _ISO_LOCAL_DATE_TIME ;
344         return initOnce!(_ISO_LOCAL_DATE_TIME)({
345             return new DateTimeFormatterBuilder()
346                 .parseCaseInsensitive()
347                 .append(ISO_LOCAL_DATE())
348                 .appendLiteral('T')
349                 .append(ISO_LOCAL_DATE())
350                 .toFormatter(ResolverStyle.STRICT, IsoChronology.INSTANCE);
351         }());
352     }
353  
354 
355     //-----------------------------------------------------------------------
356     /**
357      * The ISO date-time formatter that formats or parses a date-time with an
358      * offset, such as '2011-12-03T10:15:30+01:00'.
359      * !(p)
360      * This returns an immutable formatter capable of formatting and parsing
361      * the ISO-8601 extended offset date-time format.
362      * The format consists of:
363      * !(ul)
364      * !(li)The {@link #ISO_LOCAL_DATE_TIME}
365      * !(li)The {@link ZoneOffset#getId() offset ID}. If the offset has seconds then
366      *  they will be handled even though this is not part of the ISO-8601 standard.
367      *  The offset parsing is lenient, which allows the minutes and seconds to be optional.
368      *  Parsing is case insensitive.
369      * </ul>
370      * !(p)
371      * The returned formatter has a chronology of ISO set to ensure dates _in
372      * other calendar systems are correctly converted.
373      * It has no override zone and uses the {@link ResolverStyle#STRICT STRICT} resolver style.
374      */
375     static DateTimeFormatter ISO_OFFSET_DATE_TIME() {
376         __gshared DateTimeFormatter _ISO_OFFSET_DATE_TIME ;
377         return initOnce!(_ISO_OFFSET_DATE_TIME)({
378             return new DateTimeFormatterBuilder()
379             .parseCaseInsensitive()
380             .append(ISO_LOCAL_DATE_TIME())
381             .parseLenient()
382             .appendOffsetId()
383             .parseStrict()
384             .toFormatter(ResolverStyle.STRICT, IsoChronology.INSTANCE);
385         }());
386     }
387 
388 
389     //-----------------------------------------------------------------------
390     /**
391      * The ISO-like date-time formatter that formats or parses a date-time with
392      * offset and zone, such as '2011-12-03T10:15:30+01:00[Europe/Paris]'.
393      * !(p)
394      * This returns an immutable formatter capable of formatting and parsing
395      * a format that extends the ISO-8601 extended offset date-time format
396      * to add the time-zone.
397      * The section _in square brackets is not part of the ISO-8601 standard.
398      * The format consists of:
399      * !(ul)
400      * !(li)The {@link #ISO_OFFSET_DATE_TIME}
401      * !(li)If the zone ID is not available or is a {@code ZoneOffset} then the format is complete.
402      * !(li)An open square bracket '['.
403      * !(li)The {@link ZoneId#getId() zone ID}. This is not part of the ISO-8601 standard.
404      *  Parsing is case sensitive.
405      * !(li)A close square bracket ']'.
406      * </ul>
407      * !(p)
408      * The returned formatter has a chronology of ISO set to ensure dates _in
409      * other calendar systems are correctly converted.
410      * It has no override zone and uses the {@link ResolverStyle#STRICT STRICT} resolver style.
411      */
412     static DateTimeFormatter ISO_ZONED_DATE_TIME() {
413         __gshared DateTimeFormatter _ISO_ZONED_DATE_TIME ;
414         return initOnce!(_ISO_ZONED_DATE_TIME)({
415             return new DateTimeFormatterBuilder()
416             .append(ISO_OFFSET_DATE_TIME())
417             .optionalStart()
418             .appendLiteral('[')
419             .parseCaseSensitive()
420             .appendZoneRegionId()
421             .appendLiteral(']')
422             .toFormatter(ResolverStyle.STRICT, IsoChronology.INSTANCE);
423         }());
424     }
425 
426     //-----------------------------------------------------------------------
427     /**
428      * The ISO-like date-time formatter that formats or parses a date-time with
429      * the offset and zone if available, such as '2011-12-03T10:15:30',
430      * '2011-12-03T10:15:30+01:00' or '2011-12-03T10:15:30+01:00[Europe/Paris]'.
431      * !(p)
432      * This returns an immutable formatter capable of formatting and parsing
433      * the ISO-8601 extended local or offset date-time format, as well as the
434      * extended non-ISO form specifying the time-zone.
435      * The format consists of:
436      * !(ul)
437      * !(li)The {@link #ISO_LOCAL_DATE_TIME}
438      * !(li)If the offset is not available to format or parse then the format is complete.
439      * !(li)The {@link ZoneOffset#getId() offset ID}. If the offset has seconds then
440      *  they will be handled even though this is not part of the ISO-8601 standard.
441      * !(li)If the zone ID is not available or is a {@code ZoneOffset} then the format is complete.
442      * !(li)An open square bracket '['.
443      * !(li)The {@link ZoneId#getId() zone ID}. This is not part of the ISO-8601 standard.
444      *  Parsing is case sensitive.
445      * !(li)A close square bracket ']'.
446      * </ul>
447      * !(p)
448      * As this formatter has an optional element, it may be necessary to parse using
449      * {@link DateTimeFormatter#parseBest}.
450      * !(p)
451      * The returned formatter has a chronology of ISO set to ensure dates _in
452      * other calendar systems are correctly converted.
453      * It has no override zone and uses the {@link ResolverStyle#STRICT STRICT} resolver style.
454      */
455     static DateTimeFormatter ISO_DATE_TIME() {
456         __gshared DateTimeFormatter _ISO_DATE_TIME ;
457         return initOnce!(_ISO_DATE_TIME)({
458             return new DateTimeFormatterBuilder()
459             .append(ISO_LOCAL_DATE_TIME())
460             .optionalStart()
461             .appendOffsetId()
462             .optionalStart()
463             .appendLiteral('[')
464             .parseCaseSensitive()
465             .appendZoneRegionId()
466             .appendLiteral(']')
467             .toFormatter(ResolverStyle.STRICT, IsoChronology.INSTANCE);
468         }());
469     }
470 
471 
472     //-----------------------------------------------------------------------
473     /**
474      * The ISO date formatter that formats or parses the ordinal date
475      * without an offset, such as '2012-337'.
476      * !(p)
477      * This returns an immutable formatter capable of formatting and parsing
478      * the ISO-8601 extended ordinal date format.
479      * The format consists of:
480      * !(ul)
481      * !(li)Four digits or more for the {@link ChronoField#YEAR year}.
482      * Years _in the range 0000 to 9999 will be pre-padded by zero to ensure four digits.
483      * Years outside that range will have a prefixed positive or negative symbol.
484      * !(li)A dash
485      * !(li)Three digits for the {@link ChronoField#DAY_OF_YEAR day-of-year}.
486      *  This is pre-padded by zero to ensure three digits.
487      * !(li)If the offset is not available to format or parse then the format is complete.
488      * !(li)The {@link ZoneOffset#getId() offset ID}. If the offset has seconds then
489      *  they will be handled even though this is not part of the ISO-8601 standard.
490      *  Parsing is case insensitive.
491      * </ul>
492      * !(p)
493      * As this formatter has an optional element, it may be necessary to parse using
494      * {@link DateTimeFormatter#parseBest}.
495      * !(p)
496      * The returned formatter has a chronology of ISO set to ensure dates _in
497      * other calendar systems are correctly converted.
498      * It has no override zone and uses the {@link ResolverStyle#STRICT STRICT} resolver style.
499      */
500     static DateTimeFormatter ISO_ORDINAL_DATE() {
501         __gshared DateTimeFormatter _ISO_ORDINAL_DATE ;
502         return initOnce!(_ISO_ORDINAL_DATE)({
503             return new DateTimeFormatterBuilder()
504             .parseCaseInsensitive()
505             .appendValue(ChronoField.YEAR, 4, 10, SignStyle.EXCEEDS_PAD)
506             .appendLiteral('-')
507             .appendValue(ChronoField.DAY_OF_YEAR, 3)
508             .optionalStart()
509             .appendOffsetId()
510             .toFormatter(ResolverStyle.STRICT, IsoChronology.INSTANCE);
511         }());
512     }
513 
514     //-----------------------------------------------------------------------
515     /**
516      * The ISO date formatter that formats or parses the week-based date
517      * without an offset, such as '2012-W48-6'.
518      * !(p)
519      * This returns an immutable formatter capable of formatting and parsing
520      * the ISO-8601 extended week-based date format.
521      * The format consists of:
522      * !(ul)
523      * !(li)Four digits or more for the {@link IsoFields#WEEK_BASED_YEAR week-based-year}.
524      * Years _in the range 0000 to 9999 will be pre-padded by zero to ensure four digits.
525      * Years outside that range will have a prefixed positive or negative symbol.
526      * !(li)A dash
527      * !(li)The letter 'W'. Parsing is case insensitive.
528      * !(li)Two digits for the {@link IsoFields#WEEK_OF_WEEK_BASED_YEAR week-of-week-based-year}.
529      *  This is pre-padded by zero to ensure three digits.
530      * !(li)A dash
531      * !(li)One digit for the {@link ChronoField#DAY_OF_WEEK day-of-week}.
532      *  The value run from Monday (1) to Sunday (7).
533      * !(li)If the offset is not available to format or parse then the format is complete.
534      * !(li)The {@link ZoneOffset#getId() offset ID}. If the offset has seconds then
535      *  they will be handled even though this is not part of the ISO-8601 standard.
536      *  Parsing is case insensitive.
537      * </ul>
538      * !(p)
539      * As this formatter has an optional element, it may be necessary to parse using
540      * {@link DateTimeFormatter#parseBest}.
541      * !(p)
542      * The returned formatter has a chronology of ISO set to ensure dates _in
543      * other calendar systems are correctly converted.
544      * It has no override zone and uses the {@link ResolverStyle#STRICT STRICT} resolver style.
545      */
546     static DateTimeFormatter ISO_WEEK_DATE() {
547         __gshared DateTimeFormatter _ISO_WEEK_DATE ;
548         return initOnce!(_ISO_WEEK_DATE)({
549             return new DateTimeFormatterBuilder()
550             .parseCaseInsensitive()
551             .appendValue(IsoFields.WEEK_BASED_YEAR, 4, 10, SignStyle.EXCEEDS_PAD)
552             .appendLiteral("-W")
553             .appendValue(IsoFields.WEEK_OF_WEEK_BASED_YEAR, 2)
554             .appendLiteral('-')
555             .appendValue(ChronoField.DAY_OF_WEEK, 1)
556             .optionalStart()
557             .appendOffsetId()
558             .toFormatter(ResolverStyle.STRICT, IsoChronology.INSTANCE);
559         }());
560     }
561 
562 
563     //-----------------------------------------------------------------------
564     /**
565      * The ISO instant formatter that formats or parses an instant _in UTC,
566      * such as '2011-12-03T10:15:30Z'.
567      * !(p)
568      * This returns an immutable formatter capable of formatting and parsing
569      * the ISO-8601 instant format.
570      * When formatting, the instant will always be suffixed by 'Z' to indicate UTC.
571      * The second-of-minute is always output.
572      * The nano-of-second outputs zero, three, six or nine digits as necessary.
573      * When parsing, the behaviour of {@link DateTimeFormatterBuilder#appendOffsetId()}
574      * will be used to parse the offset, converting the instant to UTC as necessary.
575      * The time to at least the seconds field is required.
576      * Fractional seconds from zero to nine are parsed.
577      * The localized decimal style is not used.
578      * !(p)
579      * This is a special case formatter intended to allow a human readable form
580      * of an {@link hunt.time.Instant}. The {@code Instant} class is designed to
581      * only represent a point _in time and internally stores a value _in nanoseconds
582      * from a fixed epoch of 1970-01-01Z. As such, an {@code Instant} cannot be
583      * formatted as a date or time without providing some form of time-zone.
584      * This formatter allows the {@code Instant} to be formatted, by providing
585      * a suitable conversion using {@code ZoneOffset.UTC}.
586      * !(p)
587      * The format consists of:
588      * !(ul)
589      * !(li)The {@link #ISO_OFFSET_DATE_TIME} where the instant is converted from
590      *  {@link ChronoField#INSTANT_SECONDS} and {@link ChronoField#NANO_OF_SECOND}
591      *  using the {@code UTC} offset. Parsing is case insensitive.
592      * </ul>
593      * !(p)
594      * The returned formatter has no override chronology or zone.
595      * It uses the {@link ResolverStyle#STRICT STRICT} resolver style.
596      */
597     static DateTimeFormatter ISO_INSTANT() {
598         __gshared DateTimeFormatter _ISO_INSTANT ;
599         return initOnce!(_ISO_INSTANT)({
600             return new DateTimeFormatterBuilder()
601                 .parseCaseInsensitive()
602                 .appendInstant()
603                 .toFormatter(ResolverStyle.STRICT, null);
604         }());
605     }
606 
607     //-----------------------------------------------------------------------
608     /**
609      * The ISO date formatter that formats or parses a date without an
610      * offset, such as '20111203'.
611      * !(p)
612      * This returns an immutable formatter capable of formatting and parsing
613      * the ISO-8601 basic local date format.
614      * The format consists of:
615      * !(ul)
616      * !(li)Four digits for the {@link ChronoField#YEAR year}.
617      *  Only years _in the range 0000 to 9999 are supported.
618      * !(li)Two digits for the {@link ChronoField#MONTH_OF_YEAR month-of-year}.
619      *  This is pre-padded by zero to ensure two digits.
620      * !(li)Two digits for the {@link ChronoField#DAY_OF_MONTH day-of-month}.
621      *  This is pre-padded by zero to ensure two digits.
622      * !(li)If the offset is not available to format or parse then the format is complete.
623      * !(li)The {@link ZoneOffset#getId() offset ID} without colons. If the offset has
624      *  seconds then they will be handled even though this is not part of the ISO-8601 standard.
625      *  The offset parsing is lenient, which allows the minutes and seconds to be optional.
626      *  Parsing is case insensitive.
627      * </ul>
628      * !(p)
629      * As this formatter has an optional element, it may be necessary to parse using
630      * {@link DateTimeFormatter#parseBest}.
631      * !(p)
632      * The returned formatter has a chronology of ISO set to ensure dates _in
633      * other calendar systems are correctly converted.
634      * It has no override zone and uses the {@link ResolverStyle#STRICT STRICT} resolver style.
635      */
636     static DateTimeFormatter BASIC_ISO_DATE() {
637         __gshared DateTimeFormatter _BASIC_ISO_DATE ;
638         return initOnce!(_BASIC_ISO_DATE)({
639             return new DateTimeFormatterBuilder()
640                 .parseCaseInsensitive()
641                 .appendValue(ChronoField.YEAR, 4)
642                 .appendValue(ChronoField.MONTH_OF_YEAR, 2)
643                 .appendValue(ChronoField.DAY_OF_MONTH, 2)
644                 .optionalStart()
645                 .parseLenient()
646                 .appendOffset("+HHMMss", "Z")
647                 .parseStrict()
648                 .toFormatter(ResolverStyle.STRICT, IsoChronology.INSTANCE);
649         }());
650     }
651 
652     //-----------------------------------------------------------------------
653     /**
654      * The RFC-1123 date-time formatter, such as 'Tue, 3 Jun 2008 11:05:30 GMT'.
655      * !(p)
656      * This returns an immutable formatter capable of formatting and parsing
657      * most of the RFC-1123 format.
658      * RFC-1123 updates RFC-822 changing the year from two digits to four.
659      * This implementation requires a four digit year.
660      * This implementation also does not handle North American or military zone
661      * names, only 'GMT' and offset amounts.
662      * !(p)
663      * The format consists of:
664      * !(ul)
665      * !(li)If the day-of-week is not available to format or parse then jump to day-of-month.
666      * !(li)Three letter {@link ChronoField#DAY_OF_WEEK day-of-week} _in English.
667      * !(li)A comma
668      * !(li)A space
669      * !(li)One or two digits for the {@link ChronoField#DAY_OF_MONTH day-of-month}.
670      * !(li)A space
671      * !(li)Three letter {@link ChronoField#MONTH_OF_YEAR month-of-year} _in English.
672      * !(li)A space
673      * !(li)Four digits for the {@link ChronoField#YEAR year}.
674      *  Only years _in the range 0000 to 9999 are supported.
675      * !(li)A space
676      * !(li)Two digits for the {@link ChronoField#HOUR_OF_DAY hour-of-day}.
677      *  This is pre-padded by zero to ensure two digits.
678      * !(li)A colon
679      * !(li)Two digits for the {@link ChronoField#MINUTE_OF_HOUR minute-of-hour}.
680      *  This is pre-padded by zero to ensure two digits.
681      * !(li)If the second-of-minute is not available then jump to the next space.
682      * !(li)A colon
683      * !(li)Two digits for the {@link ChronoField#SECOND_OF_MINUTE second-of-minute}.
684      *  This is pre-padded by zero to ensure two digits.
685      * !(li)A space
686      * !(li)The {@link ZoneOffset#getId() offset ID} without colons or seconds.
687      *  An offset of zero uses "GMT". North American zone names and military zone names are not handled.
688      * </ul>
689      * !(p)
690      * Parsing is case insensitive.
691      * !(p)
692      * The returned formatter has a chronology of ISO set to ensure dates _in
693      * other calendar systems are correctly converted.
694      * It has no override zone and uses the {@link ResolverStyle#SMART SMART} resolver style.
695      */
696     static DateTimeFormatter RFC_1123_DATE_TIME() {
697         __gshared DateTimeFormatter _RFC_1123_DATE_TIME ;
698         return initOnce!(_RFC_1123_DATE_TIME)({
699             // manually code maps to ensure correct data always used
700             // (locale data can be changed by application code)
701             Map!(Long, string) dow = new HashMap!(Long, string)();
702             dow.put(new Long(1L), "Mon");
703             dow.put(new Long(2L), "Tue");
704             dow.put(new Long(3L), "Wed");
705             dow.put(new Long(4L), "Thu");
706             dow.put(new Long(5L), "Fri");
707             dow.put(new Long(6L), "Sat");
708             dow.put(new Long(7L), "Sun");
709             Map!(Long, string) moy = new HashMap!(Long, string)();
710             moy.put(new Long(1L), "Jan");
711             moy.put(new Long(2L), "Feb");
712             moy.put(new Long(3L), "Mar");
713             moy.put(new Long(4L), "Apr");
714             moy.put(new Long(5L), "May");
715             moy.put(new Long(6L), "Jun");
716             moy.put(new Long(7L), "Jul");
717             moy.put(new Long(8L), "Aug");
718             moy.put(new Long(9L), "Sep");
719             moy.put(new Long(10L), "Oct");
720             moy.put(new Long(11L), "Nov");
721             moy.put(new Long(12L), "Dec");
722 
723             return new DateTimeFormatterBuilder()
724                 .parseCaseInsensitive()
725                 .parseLenient()
726                 .optionalStart()
727                 .appendText(ChronoField.DAY_OF_WEEK, dow)
728                 .appendLiteral(", ")
729                 .optionalEnd()
730                 .appendValue(ChronoField.DAY_OF_MONTH, 1, 2, SignStyle.NOT_NEGATIVE)
731                 .appendLiteral(' ')
732                 .appendText(ChronoField.MONTH_OF_YEAR, moy)
733                 .appendLiteral(' ')
734                 .appendValue(ChronoField.YEAR, 4)  // 2 digit year not handled
735                 .appendLiteral(' ')
736                 .appendValue(ChronoField.HOUR_OF_DAY, 2)
737                 .appendLiteral(':')
738                 .appendValue(ChronoField.MINUTE_OF_HOUR, 2)
739                 .optionalStart()
740                 .appendLiteral(':')
741                 .appendValue(ChronoField.SECOND_OF_MINUTE, 2)
742                 .optionalEnd()
743                 .appendLiteral(' ')
744                 .appendOffset("+HHMM", "GMT")  // should handle UT/Z/EST/EDT/CST/CDT/MST/MDT/PST/MDT
745                 .toFormatter(ResolverStyle.SMART, IsoChronology.INSTANCE);
746         }());
747     }
748 
749     /**
750      * Query for a time-zone that is region-only.
751      */
752     static TemporalQuery!(ZoneId) QUERY_REGION_ONLY() {
753         __gshared TemporalQuery!(ZoneId) _QUERY_REGION_ONLY;
754         return initOnce!(_QUERY_REGION_ONLY)({
755             return new class TemporalQuery!(ZoneId)  {
756             ZoneId queryFrom(TemporalAccessor temporal){
757                 ZoneId zone = QueryHelper.query!ZoneId(temporal ,TemporalQueries.zoneId());
758                 return (zone !is null && (cast(ZoneOffset)(zone) !is null) == false ? zone : null);
759             }
760         };
761         }());
762     }
763 
764 // dfmt on
765 
766     /**
767      * The currently active builder, used by the outermost builder.
768      */
769     private DateTimeFormatterBuilder _active;
770     // alias active = this;
771 
772     DateTimeFormatterBuilder active() @trusted nothrow
773     {
774         if (_active is null)
775             return this;
776         else
777             return _active;
778     }
779     /**
780      * The parent builder, null for the outermost builder.
781      */
782     private DateTimeFormatterBuilder parent;
783     /**
784      * The list of printers that will be used.
785      */
786     private List!(DateTimePrinterParser) printerParsers;
787     /**
788      * Whether this builder produces an optional formatter.
789      */
790     private bool optional;
791     /**
792      * The width to pad the next field to.
793      */
794     private int padNextWidth;
795     /**
796      * The character to pad the next field with.
797      */
798     private char padNextChar;
799     /**
800      * The index of the last variable width value parser.
801      */
802     private int valueParserIndex = -1;
803 
804     
805     /**
806      * Creates a formatter using the specified pattern.
807      * !(p)
808      * This method will create a formatter based on a simple
809      * <a href="#patterns">pattern of letters and symbols</a>
810      * as described _in the class documentation.
811      * For example, {@code d MMM uuuu} will format 2011-12-03 as '3 Dec 2011'.
812      * !(p)
813      * The formatter will use the {@link Locale#getDefault(Locale.Category) default FORMAT locale}.
814      * This can be changed using {@link DateTimeFormatter#withLocale(Locale)} on the returned formatter.
815      * Alternatively use the {@link #ofPattern(string, Locale)} variant of this method.
816      * !(p)
817      * The returned formatter has no override chronology or zone.
818      * It uses {@link ResolverStyle#SMART SMART} resolver style.
819      *
820      * @param pattern  the pattern to use, not null
821      * @return the formatter based on the pattern, not null
822      * @throws IllegalArgumentException if the pattern is invalid
823      * @see DateTimeFormatterBuilder#appendPattern(string)
824      */
825     public static DateTimeFormatter ofPattern(string pattern) {
826         return new DateTimeFormatterBuilder().appendPattern(pattern).toFormatter();
827     }
828 
829     /**
830      * Creates a formatter using the specified pattern and locale.
831      * !(p)
832      * This method will create a formatter based on a simple
833      * <a href="#patterns">pattern of letters and symbols</a>
834      * as described _in the class documentation.
835      * For example, {@code d MMM uuuu} will format 2011-12-03 as '3 Dec 2011'.
836      * !(p)
837      * The formatter will use the specified locale.
838      * This can be changed using {@link DateTimeFormatter#withLocale(Locale)} on the returned formatter.
839      * !(p)
840      * The returned formatter has no override chronology or zone.
841      * It uses {@link ResolverStyle#SMART SMART} resolver style.
842      *
843      * @param pattern  the pattern to use, not null
844      * @param locale  the locale to use, not null
845      * @return the formatter based on the pattern, not null
846      * @throws IllegalArgumentException if the pattern is invalid
847      * @see DateTimeFormatterBuilder#appendPattern(string)
848      */
849     public static DateTimeFormatter ofPattern(string pattern, Locale locale) {
850         return new DateTimeFormatterBuilder().appendPattern(pattern).toFormatter(locale);
851     }
852     
853 
854     /**
855      * Returns a locale specific date-time formatter for the ISO chronology.
856      * !(p)
857      * This returns a formatter that will format or parse a date-time.
858      * The exact format pattern used varies by locale.
859      * !(p)
860      * The locale is determined from the formatter. The formatter returned directly by
861      * this method will use the {@link Locale#getDefault(Locale.Category) default FORMAT locale}.
862      * The locale can be controlled using {@link DateTimeFormatter#withLocale(Locale) withLocale(Locale)}
863      * on the result of this method.
864      * !(p)
865      * Note that the localized pattern is looked up lazily.
866      * This {@code DateTimeFormatter} holds the style required and the locale,
867      * looking up the pattern required on demand.
868      * !(p)
869      * The returned formatter has a chronology of ISO set to ensure dates _in
870      * other calendar systems are correctly converted.
871      * It has no override zone and uses the {@link ResolverStyle#SMART SMART} resolver style.
872      * The {@code FULL} and {@code LONG} styles typically require a time-zone.
873      * When formatting using these styles, a {@code ZoneId} must be available,
874      * either by using {@code ZonedDateTime} or {@link DateTimeFormatter#withZone}.
875      *
876      * @param dateTimeStyle  the formatter style to obtain, not null
877      * @return the date-time formatter, not null
878      */
879     public static DateTimeFormatter ofLocalizedDateTime(FormatStyle dateTimeStyle) {
880         assert(dateTimeStyle, "dateTimeStyle");
881         return new DateTimeFormatterBuilder().appendLocalized(dateTimeStyle, dateTimeStyle)
882                 .toFormatter(ResolverStyle.SMART, IsoChronology.INSTANCE);
883     }
884 
885     /**
886      * Returns a locale specific date and time format for the ISO chronology.
887      * !(p)
888      * This returns a formatter that will format or parse a date-time.
889      * The exact format pattern used varies by locale.
890      * !(p)
891      * The locale is determined from the formatter. The formatter returned directly by
892      * this method will use the {@link Locale#getDefault() default FORMAT locale}.
893      * The locale can be controlled using {@link DateTimeFormatter#withLocale(Locale) withLocale(Locale)}
894      * on the result of this method.
895      * !(p)
896      * Note that the localized pattern is looked up lazily.
897      * This {@code DateTimeFormatter} holds the style required and the locale,
898      * looking up the pattern required on demand.
899      * !(p)
900      * The returned formatter has a chronology of ISO set to ensure dates _in
901      * other calendar systems are correctly converted.
902      * It has no override zone and uses the {@link ResolverStyle#SMART SMART} resolver style.
903      * The {@code FULL} and {@code LONG} styles typically require a time-zone.
904      * When formatting using these styles, a {@code ZoneId} must be available,
905      * either by using {@code ZonedDateTime} or {@link DateTimeFormatter#withZone}.
906      *
907      * @param dateStyle  the date formatter style to obtain, not null
908      * @param timeStyle  the time formatter style to obtain, not null
909      * @return the date, time or date-time formatter, not null
910      */
911     public static DateTimeFormatter ofLocalizedDateTime(FormatStyle dateStyle, FormatStyle timeStyle) {
912         assert(dateStyle, "dateStyle");
913         assert(timeStyle, "timeStyle");
914         return new DateTimeFormatterBuilder().appendLocalized(dateStyle, timeStyle)
915                 .toFormatter(ResolverStyle.SMART, IsoChronology.INSTANCE);
916     }
917 
918     /**
919      * Returns a locale specific time format for the ISO chronology.
920      * !(p)
921      * This returns a formatter that will format or parse a time.
922      * The exact format pattern used varies by locale.
923      * !(p)
924      * The locale is determined from the formatter. The formatter returned directly by
925      * this method will use the {@link Locale#getDefault(Locale.Category) default FORMAT locale}.
926      * The locale can be controlled using {@link DateTimeFormatter#withLocale(Locale) withLocale(Locale)}
927      * on the result of this method.
928      * !(p)
929      * Note that the localized pattern is looked up lazily.
930      * This {@code DateTimeFormatter} holds the style required and the locale,
931      * looking up the pattern required on demand.
932      * !(p)
933      * The returned formatter has a chronology of ISO set to ensure dates _in
934      * other calendar systems are correctly converted.
935      * It has no override zone and uses the {@link ResolverStyle#SMART SMART} resolver style.
936      * The {@code FULL} and {@code LONG} styles typically require a time-zone.
937      * When formatting using these styles, a {@code ZoneId} must be available,
938      * either by using {@code ZonedDateTime} or {@link DateTimeFormatter#withZone}.
939      *
940      * @param timeStyle  the formatter style to obtain, not null
941      * @return the time formatter, not null
942      */
943     public static DateTimeFormatter ofLocalizedTime(FormatStyle timeStyle) {
944         assert(timeStyle, "timeStyle");
945         return new DateTimeFormatterBuilder().appendLocalized(null, timeStyle)
946                 .toFormatter(ResolverStyle.SMART, IsoChronology.INSTANCE);
947     }
948 
949     //-----------------------------------------------------------------------
950     /**
951      * Returns a locale specific date format for the ISO chronology.
952      * !(p)
953      * This returns a formatter that will format or parse a date.
954      * The exact format pattern used varies by locale.
955      * !(p)
956      * The locale is determined from the formatter. The formatter returned directly by
957      * this method will use the {@link Locale#getDefault(Locale.Category) default FORMAT locale}.
958      * The locale can be controlled using {@link DateTimeFormatter#withLocale(Locale) withLocale(Locale)}
959      * on the result of this method.
960      * !(p)
961      * Note that the localized pattern is looked up lazily.
962      * This {@code DateTimeFormatter} holds the style required and the locale,
963      * looking up the pattern required on demand.
964      * !(p)
965      * The returned formatter has a chronology of ISO set to ensure dates _in
966      * other calendar systems are correctly converted.
967      * It has no override zone and uses the {@link ResolverStyle#SMART SMART} resolver style.
968      *
969      * @param dateStyle  the formatter style to obtain, not null
970      * @return the date formatter, not null
971      */
972     public static DateTimeFormatter ofLocalizedDate(FormatStyle dateStyle) {
973         assert(dateStyle, "dateStyle");
974         return new DateTimeFormatterBuilder().appendLocalized(dateStyle, null)
975                 .toFormatter(ResolverStyle.SMART, IsoChronology.INSTANCE);
976     }
977 
978     /**
979      * Gets the formatting pattern for date and time styles for a locale and chronology.
980      * The locale and chronology are used to lookup the locale specific format
981      * for the requested dateStyle and/or timeStyle.
982      * !(p)
983      * If the locale contains the "rg" (region override)
984      * <a href="../../util/Locale.html#def_locale_extension">Unicode extensions</a>,
985      * the formatting pattern is overridden with the one appropriate for the region.
986      *
987      * @param dateStyle  the FormatStyle for the date, null for time-only pattern
988      * @param timeStyle  the FormatStyle for the time, null for date-only pattern
989      * @param chrono  the Chronology, non-null
990      * @param locale  the locale, non-null
991      * @return the locale and Chronology specific formatting pattern
992      * @throws IllegalArgumentException if both dateStyle and timeStyle are null
993      */
994      // using LocalizedPrinterParser.getLocalizedDateTimePattern
995     public static string getLocalizedDateTimePattern(FormatStyle dateStyle,
996             FormatStyle timeStyle, Chronology chrono, Locale locale)
997     {
998         // 
999         // assert(locale, "locale");
1000         // assert(chrono, "chrono");
1001         // if (dateStyle is null && timeStyle is null) {
1002         //     throw new IllegalArgumentException("Either dateStyle or timeStyle must be non-null");
1003         // }
1004         // LocaleProviderAdapter adapter = LocaleProviderAdapter.getAdapter(JavaTimeDateTimePatternProvider.class, locale);
1005         // JavaTimeDateTimePatternProvider provider = adapter.getJavaTimeDateTimePatternProvider();
1006         // string pattern = provider.getJavaTimeDateTimePattern(convertStyle(timeStyle),
1007         //                  convertStyle(dateStyle), chrono.getCalendarType(),
1008         //                  CalendarDataUtility.findRegionOverride(locale));
1009         // return pattern;
1010         implementationMissing(false);
1011         return null;
1012     }
1013 
1014     /**
1015      * Converts the given FormatStyle to the java.text.DateFormat style.
1016      *
1017      * @param style  the FormatStyle style
1018      * @return the int style, or -1 if style is null, indicating un-required
1019      */
1020     private static int convertStyle(FormatStyle style)
1021     {
1022         if (style is null)
1023         {
1024             return -1;
1025         }
1026         return style.ordinal(); // indices happen to align
1027     }
1028 
1029     /**
1030      * Constructs a new instance of the builder.
1031      */
1032     public this()
1033     {
1034         // super();
1035         printerParsers = new ArrayList!(DateTimePrinterParser)();
1036         parent = null;
1037         optional = false;
1038     }
1039 
1040     /**
1041      * Constructs a new instance of the builder.
1042      *
1043      * @param parent  the parent builder, not null
1044      * @param optional  whether the formatter is optional, not null
1045      */
1046     private this(DateTimeFormatterBuilder parent, bool optional)
1047     {
1048         // super();
1049         printerParsers = new ArrayList!(DateTimePrinterParser)();
1050         this.parent = parent;
1051         this.optional = optional;
1052     }
1053 
1054     // void opAssign(DateTimeFormatterBuilder other)
1055     // {
1056     //     this.parent = other.parent;
1057     //     this.optional = other.optional;
1058     //     this.printerParsers = other.printerParsers;
1059     //     this.padNextWidth = other.padNextWidth;
1060     //     this.padNextChar = other.padNextChar;
1061     //     this.valueParserIndex = other.valueParserIndex;
1062     // }
1063 
1064     //-----------------------------------------------------------------------
1065     /**
1066      * Changes the parse style to be case sensitive for the remainder of the formatter.
1067      * !(p)
1068      * Parsing can be case sensitive or insensitive - by default it is case sensitive.
1069      * This method allows the case sensitivity setting of parsing to be changed.
1070      * !(p)
1071      * Calling this method changes the state of the builder such that all
1072      * subsequent builder method calls will parse text _in case sensitive mode.
1073      * See {@link #parseCaseInsensitive} for the opposite setting.
1074      * The parse case sensitive/insensitive methods may be called at any point
1075      * _in the builder, thus the parser can swap between case parsing modes
1076      * multiple times during the parse.
1077      * !(p)
1078      * Since the default is case sensitive, this method should only be used after
1079      * a previous call to {@code #parseCaseInsensitive}.
1080      *
1081      * @return this, for chaining, not null
1082      */
1083     public DateTimeFormatterBuilder parseCaseSensitive()
1084     {
1085         appendInternal(SettingsParser.SENSITIVE);
1086         return this;
1087     }
1088 
1089     /**
1090      * Changes the parse style to be case insensitive for the remainder of the formatter.
1091      * !(p)
1092      * Parsing can be case sensitive or insensitive - by default it is case sensitive.
1093      * This method allows the case sensitivity setting of parsing to be changed.
1094      * !(p)
1095      * Calling this method changes the state of the builder such that all
1096      * subsequent builder method calls will parse text _in case insensitive mode.
1097      * See {@link #parseCaseSensitive()} for the opposite setting.
1098      * The parse case sensitive/insensitive methods may be called at any point
1099      * _in the builder, thus the parser can swap between case parsing modes
1100      * multiple times during the parse.
1101      *
1102      * @return this, for chaining, not null
1103      */
1104     public DateTimeFormatterBuilder parseCaseInsensitive()
1105     {
1106         appendInternal(SettingsParser.INSENSITIVE);
1107         return this;
1108     }
1109 
1110     //-----------------------------------------------------------------------
1111     /**
1112      * Changes the parse style to be strict for the remainder of the formatter.
1113      * !(p)
1114      * Parsing can be strict or lenient - by default its strict.
1115      * This controls the degree of flexibility _in matching the text and sign styles.
1116      * !(p)
1117      * When used, this method changes the parsing to be strict from this point onwards.
1118      * As strict is the default, this is normally only needed after calling {@link #parseLenient()}.
1119      * The change will remain _in force until the end of the formatter that is eventually
1120      * constructed or until {@code parseLenient} is called.
1121      *
1122      * @return this, for chaining, not null
1123      */
1124     public DateTimeFormatterBuilder parseStrict()
1125     {
1126         appendInternal(SettingsParser.STRICT);
1127         return this;
1128     }
1129 
1130     /**
1131      * Changes the parse style to be lenient for the remainder of the formatter.
1132      * Note that case sensitivity is set separately to this method.
1133      * !(p)
1134      * Parsing can be strict or lenient - by default its strict.
1135      * This controls the degree of flexibility _in matching the text and sign styles.
1136      * Applications calling this method should typically also call {@link #parseCaseInsensitive()}.
1137      * !(p)
1138      * When used, this method changes the parsing to be lenient from this point onwards.
1139      * The change will remain _in force until the end of the formatter that is eventually
1140      * constructed or until {@code parseStrict} is called.
1141      *
1142      * @return this, for chaining, not null
1143      */
1144     public DateTimeFormatterBuilder parseLenient()
1145     {
1146         appendInternal(SettingsParser.LENIENT);
1147         return this;
1148     }
1149 
1150     //-----------------------------------------------------------------------
1151     /**
1152      * Appends a default value for a field to the formatter for use _in parsing.
1153      * !(p)
1154      * This appends an instruction to the builder to inject a default value
1155      * into the parsed result. This is especially useful _in conjunction with
1156      * optional parts of the formatter.
1157      * !(p)
1158      * For example, consider a formatter that parses the year, followed by
1159      * an optional month, with a further optional day-of-month. Using such a
1160      * formatter would require the calling code to check whether a full date,
1161      * year-month or just a year had been parsed. This method can be used to
1162      * default the month and day-of-month to a sensible value, such as the
1163      * first of the month, allowing the calling code to always get a date.
1164      * !(p)
1165      * During formatting, this method has no effect.
1166      * !(p)
1167      * During parsing, the current state of the parse is inspected.
1168      * If the specified field has no associated value, because it has not been
1169      * parsed successfully at that point, then the specified value is injected
1170      * into the parse result. Injection is immediate, thus the field-value pair
1171      * will be visible to any subsequent elements _in the formatter.
1172      * As such, this method is normally called at the end of the builder.
1173      *
1174      * @param field  the field to default the value of, not null
1175      * @param value  the value to default the field to
1176      * @return this, for chaining, not null
1177      */
1178     public DateTimeFormatterBuilder parseDefaulting(TemporalField field, long value)
1179     {
1180         assert(field, "field");
1181         appendInternal(new DefaultValueParser(field, value));
1182         return this;
1183     }
1184 
1185     //-----------------------------------------------------------------------
1186     /**
1187      * Appends the value of a date-time field to the formatter using a normal
1188      * output style.
1189      * !(p)
1190      * The value of the field will be output during a format.
1191      * If the value cannot be obtained then an exception will be thrown.
1192      * !(p)
1193      * The value will be printed as per the normal format of an integer value.
1194      * Only negative numbers will be signed. No padding will be added.
1195      * !(p)
1196      * The parser for a variable width value such as this normally behaves greedily,
1197      * requiring one digit, but accepting as many digits as possible.
1198      * This behavior can be affected by 'adjacent value parsing'.
1199      * See {@link #appendValue(hunt.time.temporal.TemporalField, int)} for full details.
1200      *
1201      * @param field  the field to append, not null
1202      * @return this, for chaining, not null
1203      */
1204     public DateTimeFormatterBuilder appendValue(TemporalField field)
1205     {
1206         assert(field, "field");
1207         appendValue(new NumberPrinterParser(field, 1, 19, SignStyle.NORMAL));
1208         return this;
1209     }
1210 
1211     /**
1212      * Appends the value of a date-time field to the formatter using a fixed
1213      * width, zero-padded approach.
1214      * !(p)
1215      * The value of the field will be output during a format.
1216      * If the value cannot be obtained then an exception will be thrown.
1217      * !(p)
1218      * The value will be zero-padded on the left. If the size of the value
1219      * means that it cannot be printed within the width then an exception is thrown.
1220      * If the value of the field is negative then an exception is thrown during formatting.
1221      * !(p)
1222      * This method supports a special technique of parsing known as 'adjacent value parsing'.
1223      * This technique solves the problem where a value, variable or fixed width, is followed by one or more
1224      * fixed length values. The standard parser is greedy, and thus it would normally
1225      * steal the digits that are needed by the fixed width value parsers that follow the
1226      * variable width one.
1227      * !(p)
1228      * No action is required to initiate 'adjacent value parsing'.
1229      * When a call to {@code appendValue} is made, the builder
1230      * enters adjacent value parsing setup mode. If the immediately subsequent method
1231      * call or calls on the same builder are for a fixed width value, then the parser will reserve
1232      * space so that the fixed width values can be parsed.
1233      * !(p)
1234      * For example, consider {@code builder.appendValue(YEAR).appendValue(MONTH_OF_YEAR, 2);}
1235      * The year is a variable width parse of between 1 and 19 digits.
1236      * The month is a fixed width parse of 2 digits.
1237      * Because these were appended to the same builder immediately after one another,
1238      * the year parser will reserve two digits for the month to parse.
1239      * Thus, the text '201106' will correctly parse to a year of 2011 and a month of 6.
1240      * Without adjacent value parsing, the year would greedily parse all six digits and leave
1241      * nothing for the month.
1242      * !(p)
1243      * Adjacent value parsing applies to each set of fixed width not-negative values _in the parser
1244      * that immediately follow any kind of value, variable or fixed width.
1245      * Calling any other append method will end the setup of adjacent value parsing.
1246      * Thus, _in the unlikely event that you need to avoid adjacent value parsing behavior,
1247      * simply add the {@code appendValue} to another {@code DateTimeFormatterBuilder}
1248      * and add that to this builder.
1249      * !(p)
1250      * If adjacent parsing is active, then parsing must match exactly the specified
1251      * number of digits _in both strict and lenient modes.
1252      * In addition, no positive or negative sign is permitted.
1253      *
1254      * @param field  the field to append, not null
1255      * @param width  the width of the printed field, from 1 to 19
1256      * @return this, for chaining, not null
1257      * @throws IllegalArgumentException if the width is invalid
1258      */
1259     public DateTimeFormatterBuilder appendValue(TemporalField field, int width)
1260     {
1261         assert(field, "field");
1262         if (width < 1 || width > 19)
1263         {
1264             throw new IllegalArgumentException(
1265                     "The width must be from 1 to 19 inclusive but was " ~ width.to!string);
1266         }
1267         NumberPrinterParser pp = new NumberPrinterParser(field, width, width,
1268                 SignStyle.NOT_NEGATIVE);
1269         appendValue(pp);
1270         return this;
1271     }
1272 
1273     /**
1274      * Appends the value of a date-time field to the formatter providing full
1275      * control over formatting.
1276      * !(p)
1277      * The value of the field will be output during a format.
1278      * If the value cannot be obtained then an exception will be thrown.
1279      * !(p)
1280      * This method provides full control of the numeric formatting, including
1281      * zero-padding and the positive/negative sign.
1282      * !(p)
1283      * The parser for a variable width value such as this normally behaves greedily,
1284      * accepting as many digits as possible.
1285      * This behavior can be affected by 'adjacent value parsing'.
1286      * See {@link #appendValue(hunt.time.temporal.TemporalField, int)} for full details.
1287      * !(p)
1288      * In strict parsing mode, the minimum number of parsed digits is {@code minWidth}
1289      * and the maximum is {@code maxWidth}.
1290      * In lenient parsing mode, the minimum number of parsed digits is one
1291      * and the maximum is 19 (except as limited by adjacent value parsing).
1292      * !(p)
1293      * If this method is invoked with equal minimum and maximum widths and a sign style of
1294      * {@code NOT_NEGATIVE} then it delegates to {@code appendValue(TemporalField,int)}.
1295      * In this scenario, the formatting and parsing behavior described there occur.
1296      *
1297      * @param field  the field to append, not null
1298      * @param minWidth  the minimum field width of the printed field, from 1 to 19
1299      * @param maxWidth  the maximum field width of the printed field, from 1 to 19
1300      * @param signStyle  the positive/negative output style, not null
1301      * @return this, for chaining, not null
1302      * @throws IllegalArgumentException if the widths are invalid
1303      */
1304     public DateTimeFormatterBuilder appendValue(TemporalField field,
1305             int minWidth, int maxWidth, SignStyle signStyle)
1306     {
1307         if (minWidth == maxWidth && signStyle == SignStyle.NOT_NEGATIVE)
1308         {
1309             return appendValue(field, maxWidth);
1310         }
1311         assert(field, "field");
1312         // assert(signStyle, "signStyle");
1313         if (minWidth < 1 || minWidth > 19)
1314         {
1315             throw new IllegalArgumentException(
1316                     "The minimum width must be from 1 to 19 inclusive but was "
1317                     ~ minWidth.to!string);
1318         }
1319         if (maxWidth < 1 || maxWidth > 19)
1320         {
1321             throw new IllegalArgumentException(
1322                     "The maximum width must be from 1 to 19 inclusive but was "
1323                     ~ maxWidth.to!string);
1324         }
1325         if (maxWidth < minWidth)
1326         {
1327             throw new IllegalArgumentException("The maximum width must exceed or equal the minimum width but "
1328                     ~ maxWidth.to!string ~ " < " ~ minWidth.to!string);
1329         }
1330         NumberPrinterParser pp = new NumberPrinterParser(field, minWidth, maxWidth, signStyle);
1331         appendValue(pp);
1332         return this;
1333     }
1334 
1335     //-----------------------------------------------------------------------
1336     /**
1337      * Appends the reduced value of a date-time field to the formatter.
1338      * !(p)
1339      * Since fields such as year vary by chronology, it is recommended to use the
1340      * {@link #appendValueReduced(TemporalField, int, int, ChronoLocalDate)} date}
1341      * variant of this method _in most cases. This variant is suitable for
1342      * simple fields or working with only the ISO chronology.
1343      * !(p)
1344      * For formatting, the {@code width} and {@code maxWidth} are used to
1345      * determine the number of characters to format.
1346      * If they are equal then the format is fixed width.
1347      * If the value of the field is within the range of the {@code baseValue} using
1348      * {@code width} characters then the reduced value is formatted otherwise the value is
1349      * truncated to fit {@code maxWidth}.
1350      * The rightmost characters are output to match the width, left padding with zero.
1351      * !(p)
1352      * For strict parsing, the number of characters allowed by {@code width} to {@code maxWidth} are parsed.
1353      * For lenient parsing, the number of characters must be at least 1 and less than 10.
1354      * If the number of digits parsed is equal to {@code width} and the value is positive,
1355      * the value of the field is computed to be the first number greater than
1356      * or equal to the {@code baseValue} with the same least significant characters,
1357      * otherwise the value parsed is the field value.
1358      * This allows a reduced value to be entered for values _in range of the baseValue
1359      * and width and absolute values can be entered for values outside the range.
1360      * !(p)
1361      * For example, a base value of {@code 1980} and a width of {@code 2} will have
1362      * valid values from {@code 1980} to {@code 2079}.
1363      * During parsing, the text {@code "12"} will result _in the value {@code 2012} as that
1364      * is the value within the range where the last two characters are "12".
1365      * By contrast, parsing the text {@code "1915"} will result _in the value {@code 1915}.
1366      *
1367      * @param field  the field to append, not null
1368      * @param width  the field width of the printed and parsed field, from 1 to 10
1369      * @param maxWidth  the maximum field width of the printed field, from 1 to 10
1370      * @param baseValue  the base value of the range of valid values
1371      * @return this, for chaining, not null
1372      * @throws IllegalArgumentException if the width or base value is invalid
1373      */
1374     public DateTimeFormatterBuilder appendValueReduced(TemporalField field,
1375             int width, int maxWidth, int baseValue)
1376     {
1377         assert(field, "field");
1378         ReducedPrinterParser pp = new ReducedPrinterParser(field, width,
1379                 maxWidth, baseValue, null);
1380         appendValue(pp);
1381         return this;
1382     }
1383 
1384     /**
1385      * Appends the reduced value of a date-time field to the formatter.
1386      * !(p)
1387      * This is typically used for formatting and parsing a two digit year.
1388      * !(p)
1389      * The base date is used to calculate the full value during parsing.
1390      * For example, if the base date is 1950-01-01 then parsed values for
1391      * a two digit year parse will be _in the range 1950-01-01 to 2049-12-31.
1392      * Only the year would be extracted from the date, thus a base date of
1393      * 1950-08-25 would also parse to the range 1950-01-01 to 2049-12-31.
1394      * This behavior is necessary to support fields such as week-based-year
1395      * or other calendar systems where the parsed value does not align with
1396      * standard ISO years.
1397      * !(p)
1398      * The exact behavior is as follows. Parse the full set of fields and
1399      * determine the effective chronology using the last chronology if
1400      * it appears more than once. Then convert the base date to the
1401      * effective chronology. Then extract the specified field from the
1402      * chronology-specific base date and use it to determine the
1403      * {@code baseValue} used below.
1404      * !(p)
1405      * For formatting, the {@code width} and {@code maxWidth} are used to
1406      * determine the number of characters to format.
1407      * If they are equal then the format is fixed width.
1408      * If the value of the field is within the range of the {@code baseValue} using
1409      * {@code width} characters then the reduced value is formatted otherwise the value is
1410      * truncated to fit {@code maxWidth}.
1411      * The rightmost characters are output to match the width, left padding with zero.
1412      * !(p)
1413      * For strict parsing, the number of characters allowed by {@code width} to {@code maxWidth} are parsed.
1414      * For lenient parsing, the number of characters must be at least 1 and less than 10.
1415      * If the number of digits parsed is equal to {@code width} and the value is positive,
1416      * the value of the field is computed to be the first number greater than
1417      * or equal to the {@code baseValue} with the same least significant characters,
1418      * otherwise the value parsed is the field value.
1419      * This allows a reduced value to be entered for values _in range of the baseValue
1420      * and width and absolute values can be entered for values outside the range.
1421      * !(p)
1422      * For example, a base value of {@code 1980} and a width of {@code 2} will have
1423      * valid values from {@code 1980} to {@code 2079}.
1424      * During parsing, the text {@code "12"} will result _in the value {@code 2012} as that
1425      * is the value within the range where the last two characters are "12".
1426      * By contrast, parsing the text {@code "1915"} will result _in the value {@code 1915}.
1427      *
1428      * @param field  the field to append, not null
1429      * @param width  the field width of the printed and parsed field, from 1 to 10
1430      * @param maxWidth  the maximum field width of the printed field, from 1 to 10
1431      * @param baseDate  the base date used to calculate the base value for the range
1432      *  of valid values _in the parsed chronology, not null
1433      * @return this, for chaining, not null
1434      * @throws IllegalArgumentException if the width or base value is invalid
1435      */
1436     public DateTimeFormatterBuilder appendValueReduced(TemporalField field,
1437             int width, int maxWidth, ChronoLocalDate baseDate)
1438     {
1439         assert(field, "field");
1440         assert(baseDate, "baseDate");
1441         ReducedPrinterParser pp = new ReducedPrinterParser(field, width, maxWidth, 0, baseDate);
1442         appendValue(pp);
1443         return this;
1444     }
1445 
1446     /**
1447      * Appends a fixed or variable width printer-parser handling adjacent value mode.
1448      * If a PrinterParser is not active then the new PrinterParser becomes
1449      * the active PrinterParser.
1450      * Otherwise, the active PrinterParser is modified depending on the new PrinterParser.
1451      * If the new PrinterParser is fixed width and has sign style {@code NOT_NEGATIVE}
1452      * then its width is added to the active PP and
1453      * the new PrinterParser is forced to be fixed width.
1454      * If the new PrinterParser is variable width, the active PrinterParser is changed
1455      * to be fixed width and the new PrinterParser becomes the active PP.
1456      *
1457      * @param pp  the printer-parser, not null
1458      * @return this, for chaining, not null
1459      */
1460     private DateTimeFormatterBuilder appendValue(NumberPrinterParser pp)
1461     {
1462         if (active.valueParserIndex >= 0)
1463         {
1464             int activeValueParser = active.valueParserIndex;
1465 
1466             // adjacent parsing mode, update setting _in previous parsers
1467             NumberPrinterParser basePP = cast(NumberPrinterParser) active.printerParsers.get(
1468                     activeValueParser);
1469             if (pp.minWidth == pp.maxWidth && pp.signStyle == SignStyle.NOT_NEGATIVE)
1470             {
1471                 // Append the width to the subsequentWidth of the active parser
1472                 basePP = basePP.withSubsequentWidth(pp.maxWidth);
1473                 // Append the new parser as a fixed width
1474                 appendInternal(pp.withFixedWidth());
1475                 // Retain the previous active parser
1476                 active.valueParserIndex = activeValueParser;
1477             }
1478             else
1479             {
1480                 // Modify the active parser to be fixed width
1481                 basePP = basePP.withFixedWidth();
1482                 // The new parser becomes the mew active parser
1483                 active.valueParserIndex = appendInternal(pp);
1484             }
1485             // Replace the modified parser with the updated one
1486             active.printerParsers.set(activeValueParser, basePP);
1487         }
1488         else
1489         {
1490             // The new Parser becomes the active parser
1491             active.valueParserIndex = appendInternal(pp);
1492         }
1493         return this;
1494     }
1495 
1496     //-----------------------------------------------------------------------
1497     /**
1498      * Appends the fractional value of a date-time field to the formatter.
1499      * !(p)
1500      * The fractional value of the field will be output including the
1501      * preceding decimal point. The preceding value is not output.
1502      * For example, the second-of-minute value of 15 would be output as {@code .25}.
1503      * !(p)
1504      * The width of the printed fraction can be controlled. Setting the
1505      * minimum width to zero will cause no output to be generated.
1506      * The printed fraction will have the minimum width necessary between
1507      * the minimum and maximum widths - trailing zeroes are omitted.
1508      * No rounding occurs due to the maximum width - digits are simply dropped.
1509      * !(p)
1510      * When parsing _in strict mode, the number of parsed digits must be between
1511      * the minimum and maximum width. In strict mode, if the minimum and maximum widths
1512      * are equal and there is no decimal point then the parser will
1513      * participate _in adjacent value parsing, see
1514      * {@link appendValue(hunt.time.temporal.TemporalField, int)}. When parsing _in lenient mode,
1515      * the minimum width is considered to be zero and the maximum is nine.
1516      * !(p)
1517      * If the value cannot be obtained then an exception will be thrown.
1518      * If the value is negative an exception will be thrown.
1519      * If the field does not have a fixed set of valid values then an
1520      * exception will be thrown.
1521      * If the field value _in the date-time to be printed is invalid it
1522      * cannot be printed and an exception will be thrown.
1523      *
1524      * @param field  the field to append, not null
1525      * @param minWidth  the minimum width of the field excluding the decimal point, from 0 to 9
1526      * @param maxWidth  the maximum width of the field excluding the decimal point, from 1 to 9
1527      * @param decimalPoint  whether to output the localized decimal point symbol
1528      * @return this, for chaining, not null
1529      * @throws IllegalArgumentException if the field has a variable set of valid values or
1530      *  either width is invalid
1531      */
1532     public DateTimeFormatterBuilder appendFraction(TemporalField field,
1533             int minWidth, int maxWidth, bool decimalPoint)
1534     {
1535         if (minWidth == maxWidth && decimalPoint == false)
1536         {
1537             // adjacent parsing
1538             appendValue(new FractionPrinterParser(field, minWidth, maxWidth, decimalPoint));
1539         }
1540         else
1541         {
1542             appendInternal(new FractionPrinterParser(field, minWidth, maxWidth, decimalPoint));
1543         }
1544         return this;
1545     }
1546 
1547     //-----------------------------------------------------------------------
1548     /**
1549      * Appends the text of a date-time field to the formatter using the full
1550      * text style.
1551      * !(p)
1552      * The text of the field will be output during a format.
1553      * The value must be within the valid range of the field.
1554      * If the value cannot be obtained then an exception will be thrown.
1555      * If the field has no textual representation, then the numeric value will be used.
1556      * !(p)
1557      * The value will be printed as per the normal format of an integer value.
1558      * Only negative numbers will be signed. No padding will be added.
1559      *
1560      * @param field  the field to append, not null
1561      * @return this, for chaining, not null
1562      */
1563     public DateTimeFormatterBuilder appendText(TemporalField field)
1564     {
1565         return appendText(field, TextStyle.FULL);
1566     }
1567 
1568     /**
1569      * Appends the text of a date-time field to the formatter.
1570      * !(p)
1571      * The text of the field will be output during a format.
1572      * The value must be within the valid range of the field.
1573      * If the value cannot be obtained then an exception will be thrown.
1574      * If the field has no textual representation, then the numeric value will be used.
1575      * !(p)
1576      * The value will be printed as per the normal format of an integer value.
1577      * Only negative numbers will be signed. No padding will be added.
1578      *
1579      * @param field  the field to append, not null
1580      * @param textStyle  the text style to use, not null
1581      * @return this, for chaining, not null
1582      */
1583     public DateTimeFormatterBuilder appendText(TemporalField field, TextStyle textStyle)
1584     {
1585         assert(field, "field");
1586         // assert(textStyle, "textStyle");
1587         appendInternal(new TextPrinterParser(field, textStyle, DateTimeTextProvider.getInstance()));
1588         return this;
1589     }
1590 
1591     /**
1592      * Appends the text of a date-time field to the formatter using the specified
1593      * map to supply the text.
1594      * !(p)
1595      * The standard text outputting methods use the localized text _in the JDK.
1596      * This method allows that text to be specified directly.
1597      * The supplied map is not validated by the builder to ensure that formatting or
1598      * parsing is possible, thus an invalid map may throw an error during later use.
1599      * !(p)
1600      * Supplying the map of text provides considerable flexibility _in formatting and parsing.
1601      * For example, a legacy application might require or supply the months of the
1602      * year as "JNY", "FBY", "MCH" etc. These do not match the standard set of text
1603      * for localized month names. Using this method, a map can be created which
1604      * defines the connection between each value and the text:
1605      * !(pre)
1606      * Map&lt;Long, string&gt; map = new HashMap&lt;&gt;();
1607      * map.put(1L, "JNY");
1608      * map.put(2L, "FBY");
1609      * map.put(3L, "MCH");
1610      * ...
1611      * builder.appendText(MONTH_OF_YEAR, map);
1612      * </pre>
1613      * !(p)
1614      * Other uses might be to output the value with a suffix, such as "1st", "2nd", "3rd",
1615      * or as Roman numerals "I", "II", "III", "IV".
1616      * !(p)
1617      * During formatting, the value is obtained and checked that it is _in the valid range.
1618      * If text is not available for the value then it is output as a number.
1619      * During parsing, the parser will match against the map of text and numeric values.
1620      *
1621      * @param field  the field to append, not null
1622      * @param textLookup  the map from the value to the text
1623      * @return this, for chaining, not null
1624      */
1625     public DateTimeFormatterBuilder appendText(TemporalField field, Map!(Long, string) textLookup)
1626     {
1627         assert(field, "field");
1628         assert(textLookup, "textLookup");
1629         Map!(Long, string) copy = new LinkedHashMap!(Long, string)(textLookup);
1630         Map!(TextStyle, Map!(Long, string)) map = /* Collections.singletonMap */ new HashMap!(TextStyle,
1631                     Map!(Long, string))();
1632         map.put(TextStyle.FULL, copy);
1633         LocaleStore store = new LocaleStore(map);
1634         DateTimeTextProvider provider = new class DateTimeTextProvider
1635         {
1636             override public string getText(Chronology chrono,
1637                     TemporalField field, long value, TextStyle style, Locale locale)
1638             {
1639                 return store.getText(value, style);
1640             }
1641 
1642             override public string getText(TemporalField field, long value,
1643                     TextStyle style, Locale locale)
1644             {
1645                 return store.getText(value, style);
1646             }
1647 
1648             override public Iterable!(MapEntry!(string, Long)) getTextIterator(Chronology chrono,
1649                     TemporalField field, TextStyle style, Locale locale)
1650             {
1651                 return store.getTextIterator(style);
1652             }
1653 
1654             override public Iterable!(MapEntry!(string, Long)) getTextIterator(TemporalField field,
1655                     TextStyle style, Locale locale)
1656             {
1657                 return store.getTextIterator(style);
1658             }
1659         };
1660         appendInternal(new TextPrinterParser(field, TextStyle.FULL, provider));
1661         return this;
1662     }
1663 
1664     //-----------------------------------------------------------------------
1665     /**
1666      * Appends an instant using ISO-8601 to the formatter, formatting fractional
1667      * digits _in groups of three.
1668      * !(p)
1669      * Instants have a fixed output format.
1670      * They are converted to a date-time with a zone-offset of UTC and formatted
1671      * using the standard ISO-8601 format.
1672      * With this method, formatting nano-of-second outputs zero, three, six
1673      * or nine digits as necessary.
1674      * The localized decimal style is not used.
1675      * !(p)
1676      * The instant is obtained using {@link ChronoField#INSTANT_SECONDS INSTANT_SECONDS}
1677      * and optionally {@code NANO_OF_SECOND}. The value of {@code INSTANT_SECONDS}
1678      * may be outside the maximum range of {@code LocalDateTime}.
1679      * !(p)
1680      * The {@linkplain ResolverStyle resolver style} has no effect on instant parsing.
1681      * The end-of-day time of '24:00' is handled as midnight at the start of the following day.
1682      * The leap-second time of '23:59:59' is handled to some degree, see
1683      * {@link DateTimeFormatter#parsedLeapSecond()} for full details.
1684      * !(p)
1685      * When formatting, the instant will always be suffixed by 'Z' to indicate UTC.
1686      * When parsing, the behaviour of {@link DateTimeFormatterBuilder#appendOffsetId()}
1687      * will be used to parse the offset, converting the instant to UTC as necessary.
1688      * !(p)
1689      * An alternative to this method is to format/parse the instant as a single
1690      * epoch-seconds value. That is achieved using {@code appendValue(INSTANT_SECONDS)}.
1691      *
1692      * @return this, for chaining, not null
1693      */
1694     public DateTimeFormatterBuilder appendInstant()
1695     {
1696         appendInternal(new InstantPrinterParser(-2));
1697         return this;
1698     }
1699 
1700     /**
1701      * Appends an instant using ISO-8601 to the formatter with control over
1702      * the number of fractional digits.
1703      * !(p)
1704      * Instants have a fixed output format, although this method provides some
1705      * control over the fractional digits. They are converted to a date-time
1706      * with a zone-offset of UTC and printed using the standard ISO-8601 format.
1707      * The localized decimal style is not used.
1708      * !(p)
1709      * The {@code fractionalDigits} parameter allows the output of the fractional
1710      * second to be controlled. Specifying zero will cause no fractional digits
1711      * to be output. From 1 to 9 will output an increasing number of digits, using
1712      * zero right-padding if necessary. The special value -1 is used to output as
1713      * many digits as necessary to avoid any trailing zeroes.
1714      * !(p)
1715      * When parsing _in strict mode, the number of parsed digits must match the
1716      * fractional digits. When parsing _in lenient mode, any number of fractional
1717      * digits from zero to nine are accepted.
1718      * !(p)
1719      * The instant is obtained using {@link ChronoField#INSTANT_SECONDS INSTANT_SECONDS}
1720      * and optionally {@code NANO_OF_SECOND}. The value of {@code INSTANT_SECONDS}
1721      * may be outside the maximum range of {@code LocalDateTime}.
1722      * !(p)
1723      * The {@linkplain ResolverStyle resolver style} has no effect on instant parsing.
1724      * The end-of-day time of '24:00' is handled as midnight at the start of the following day.
1725      * The leap-second time of '23:59:60' is handled to some degree, see
1726      * {@link DateTimeFormatter#parsedLeapSecond()} for full details.
1727      * !(p)
1728      * An alternative to this method is to format/parse the instant as a single
1729      * epoch-seconds value. That is achieved using {@code appendValue(INSTANT_SECONDS)}.
1730      *
1731      * @param fractionalDigits  the number of fractional second digits to format with,
1732      *  from 0 to 9, or -1 to use as many digits as necessary
1733      * @return this, for chaining, not null
1734      * @throws IllegalArgumentException if the number of fractional digits is invalid
1735      */
1736     public DateTimeFormatterBuilder appendInstant(int fractionalDigits)
1737     {
1738         if (fractionalDigits < -1 || fractionalDigits > 9)
1739         {
1740             throw new IllegalArgumentException(
1741                     "The fractional digits must be from -1 to 9 inclusive but was "
1742                     ~ fractionalDigits.to!string);
1743         }
1744         appendInternal(new InstantPrinterParser(fractionalDigits));
1745         return this;
1746     }
1747 
1748     //-----------------------------------------------------------------------
1749     /**
1750      * Appends the zone offset, such as '+01:00', to the formatter.
1751      * !(p)
1752      * This appends an instruction to format/parse the offset ID to the builder.
1753      * This is equivalent to calling {@code appendOffset("+HH:mm:ss", "Z")}.
1754      * See {@link #appendOffset(string, string)} for details on formatting
1755      * and parsing.
1756      *
1757      * @return this, for chaining, not null
1758      */
1759     public DateTimeFormatterBuilder appendOffsetId()
1760     {
1761         appendInternal(OffsetIdPrinterParser.INSTANCE_ID_Z);
1762         return this;
1763     }
1764 
1765     /**
1766      * Appends the zone offset, such as '+01:00', to the formatter.
1767      * !(p)
1768      * This appends an instruction to format/parse the offset ID to the builder.
1769      * !(p)
1770      * During formatting, the offset is obtained using a mechanism equivalent
1771      * to querying the temporal with {@link TemporalQueries#offset()}.
1772      * It will be printed using the format defined below.
1773      * If the offset cannot be obtained then an exception is thrown unless the
1774      * section of the formatter is optional.
1775      * !(p)
1776      * When parsing _in strict mode, the input must contain the mandatory
1777      * and optional elements are defined by the specified pattern.
1778      * If the offset cannot be parsed then an exception is thrown unless
1779      * the section of the formatter is optional.
1780      * !(p)
1781      * When parsing _in lenient mode, only the hours are mandatory - minutes
1782      * and seconds are optional. The colons are required if the specified
1783      * pattern contains a colon. If the specified pattern is "+HH", the
1784      * presence of colons is determined by whether the character after the
1785      * hour digits is a colon or not.
1786      * If the offset cannot be parsed then an exception is thrown unless
1787      * the section of the formatter is optional.
1788      * !(p)
1789      * The format of the offset is controlled by a pattern which must be one
1790      * of the following:
1791      * !(ul)
1792      * !(li){@code +HH} - hour only, ignoring minute and second
1793      * !(li){@code +HHmm} - hour, with minute if non-zero, ignoring second, no colon
1794      * !(li){@code +HH:mm} - hour, with minute if non-zero, ignoring second, with colon
1795      * !(li){@code +HHMM} - hour and minute, ignoring second, no colon
1796      * !(li){@code +HH:MM} - hour and minute, ignoring second, with colon
1797      * !(li){@code +HHMMss} - hour and minute, with second if non-zero, no colon
1798      * !(li){@code +HH:MM:ss} - hour and minute, with second if non-zero, with colon
1799      * !(li){@code +HHMMSS} - hour, minute and second, no colon
1800      * !(li){@code +HH:MM:SS} - hour, minute and second, with colon
1801      * !(li){@code +HHmmss} - hour, with minute if non-zero or with minute and
1802      * second if non-zero, no colon
1803      * !(li){@code +HH:mm:ss} - hour, with minute if non-zero or with minute and
1804      * second if non-zero, with colon
1805      * !(li){@code +H} - hour only, ignoring minute and second
1806      * !(li){@code +Hmm} - hour, with minute if non-zero, ignoring second, no colon
1807      * !(li){@code +H:mm} - hour, with minute if non-zero, ignoring second, with colon
1808      * !(li){@code +HMM} - hour and minute, ignoring second, no colon
1809      * !(li){@code +H:MM} - hour and minute, ignoring second, with colon
1810      * !(li){@code +HMMss} - hour and minute, with second if non-zero, no colon
1811      * !(li){@code +H:MM:ss} - hour and minute, with second if non-zero, with colon
1812      * !(li){@code +HMMSS} - hour, minute and second, no colon
1813      * !(li){@code +H:MM:SS} - hour, minute and second, with colon
1814      * !(li){@code +Hmmss} - hour, with minute if non-zero or with minute and
1815      * second if non-zero, no colon
1816      * !(li){@code +H:mm:ss} - hour, with minute if non-zero or with minute and
1817      * second if non-zero, with colon
1818      * </ul>
1819      * Patterns containing "HH" will format and parse a two digit hour,
1820      * zero-padded if necessary. Patterns containing "H" will format with no
1821      * zero-padding, and parse either one or two digits.
1822      * In lenient mode, the parser will be greedy and parse the maximum digits possible.
1823      * The "no offset" text controls what text is printed when the total amount of
1824      * the offset fields to be output is zero.
1825      * Example values would be 'Z', '+00:00', 'UTC' or 'GMT'.
1826      * Three formats are accepted for parsing UTC - the "no offset" text, and the
1827      * plus and minus versions of zero defined by the pattern.
1828      *
1829      * @param pattern  the pattern to use, not null
1830      * @param noOffsetText  the text to use when the offset is zero, not null
1831      * @return this, for chaining, not null
1832      * @throws IllegalArgumentException if the pattern is invalid
1833      */
1834     public DateTimeFormatterBuilder appendOffset(string pattern, string noOffsetText)
1835     {
1836         appendInternal(new OffsetIdPrinterParser(pattern, noOffsetText));
1837         return this;
1838     }
1839 
1840     /**
1841      * Appends the localized zone offset, such as 'GMT+01:00', to the formatter.
1842      * !(p)
1843      * This appends a localized zone offset to the builder, the format of the
1844      * localized offset is controlled by the specified {@link FormatStyle style}
1845      * to this method:
1846      * !(ul)
1847      * !(li){@link TextStyle#FULL full} - formats with localized offset text, such
1848      * as 'GMT, 2-digit hour and minute field, optional second field if non-zero,
1849      * and colon.
1850      * !(li){@link TextStyle#SHORT short} - formats with localized offset text,
1851      * such as 'GMT, hour without leading zero, optional 2-digit minute and
1852      * second if non-zero, and colon.
1853      * </ul>
1854      * !(p)
1855      * During formatting, the offset is obtained using a mechanism equivalent
1856      * to querying the temporal with {@link TemporalQueries#offset()}.
1857      * If the offset cannot be obtained then an exception is thrown unless the
1858      * section of the formatter is optional.
1859      * !(p)
1860      * During parsing, the offset is parsed using the format defined above.
1861      * If the offset cannot be parsed then an exception is thrown unless the
1862      * section of the formatter is optional.
1863      *
1864      * @param style  the format style to use, not null
1865      * @return this, for chaining, not null
1866      * @throws IllegalArgumentException if style is neither {@link TextStyle#FULL
1867      * full} nor {@link TextStyle#SHORT short}
1868      */
1869     public DateTimeFormatterBuilder appendLocalizedOffset(TextStyle style)
1870     {
1871         // assert(style, "style");
1872         if (style != TextStyle.FULL && style != TextStyle.SHORT)
1873         {
1874             throw new IllegalArgumentException("Style must be either full or short");
1875         }
1876         appendInternal(new LocalizedOffsetIdPrinterParser(style));
1877         return this;
1878     }
1879 
1880     //-----------------------------------------------------------------------
1881     /**
1882      * Appends the time-zone ID, such as 'Europe/Paris' or '+02:00', to the formatter.
1883      * !(p)
1884      * This appends an instruction to format/parse the zone ID to the builder.
1885      * The zone ID is obtained _in a strict manner suitable for {@code ZonedDateTime}.
1886      * By contrast, {@code OffsetDateTime} does not have a zone ID suitable
1887      * for use with this method, see {@link #appendZoneOrOffsetId()}.
1888      * !(p)
1889      * During formatting, the zone is obtained using a mechanism equivalent
1890      * to querying the temporal with {@link TemporalQueries#zoneId()}.
1891      * It will be printed using the result of {@link ZoneId#getId()}.
1892      * If the zone cannot be obtained then an exception is thrown unless the
1893      * section of the formatter is optional.
1894      * !(p)
1895      * During parsing, the text must match a known zone or offset.
1896      * There are two types of zone ID, offset-based, such as '+01:30' and
1897      * region-based, such as 'Europe/London'. These are parsed differently.
1898      * If the parse starts with '+', '-', 'UT', 'UTC' or 'GMT', then the parser
1899      * expects an offset-based zone and will not match region-based zones.
1900      * The offset ID, such as '+02:30', may be at the start of the parse,
1901      * or prefixed by  'UT', 'UTC' or 'GMT'. The offset ID parsing is
1902      * equivalent to using {@link #appendOffset(string, string)} using the
1903      * arguments 'HH:MM:ss' and the no offset string '0'.
1904      * If the parse starts with 'UT', 'UTC' or 'GMT', and the parser cannot
1905      * match a following offset ID, then {@link ZoneOffset#UTC} is selected.
1906      * In all other cases, the list of known region-based zones is used to
1907      * find the longest available match. If no match is found, and the parse
1908      * starts with 'Z', then {@code ZoneOffset.UTC} is selected.
1909      * The parser uses the {@linkplain #parseCaseInsensitive() case sensitive} setting.
1910      * !(p)
1911      * For example, the following will parse:
1912      * !(pre)
1913      *   "Europe/London"           -- ZoneId.of("Europe/London")
1914      *   "Z"                       -- ZoneOffset.UTC
1915      *   "UT"                      -- ZoneId.of("UT")
1916      *   "UTC"                     -- ZoneId.of("UTC")
1917      *   "GMT"                     -- ZoneId.of("GMT")
1918      *   "+01:30"                  -- ZoneOffset.of("+01:30")
1919      *   "UT+01:30"                -- ZoneOffset.of("+01:30")
1920      *   "UTC+01:30"               -- ZoneOffset.of("+01:30")
1921      *   "GMT+01:30"               -- ZoneOffset.of("+01:30")
1922      * </pre>
1923      *
1924      * @return this, for chaining, not null
1925      * @see #appendZoneRegionId()
1926      */
1927     public DateTimeFormatterBuilder appendZoneId()
1928     {
1929         appendInternal(new ZoneIdPrinterParser(TemporalQueries.zoneId(), "ZoneId()"));
1930         return this;
1931     }
1932 
1933     /**
1934      * Appends the time-zone region ID, such as 'Europe/Paris', to the formatter,
1935      * rejecting the zone ID if it is a {@code ZoneOffset}.
1936      * !(p)
1937      * This appends an instruction to format/parse the zone ID to the builder
1938      * only if it is a region-based ID.
1939      * !(p)
1940      * During formatting, the zone is obtained using a mechanism equivalent
1941      * to querying the temporal with {@link TemporalQueries#zoneId()}.
1942      * If the zone is a {@code ZoneOffset} or it cannot be obtained then
1943      * an exception is thrown unless the section of the formatter is optional.
1944      * If the zone is not an offset, then the zone will be printed using
1945      * the zone ID from {@link ZoneId#getId()}.
1946      * !(p)
1947      * During parsing, the text must match a known zone or offset.
1948      * There are two types of zone ID, offset-based, such as '+01:30' and
1949      * region-based, such as 'Europe/London'. These are parsed differently.
1950      * If the parse starts with '+', '-', 'UT', 'UTC' or 'GMT', then the parser
1951      * expects an offset-based zone and will not match region-based zones.
1952      * The offset ID, such as '+02:30', may be at the start of the parse,
1953      * or prefixed by  'UT', 'UTC' or 'GMT'. The offset ID parsing is
1954      * equivalent to using {@link #appendOffset(string, string)} using the
1955      * arguments 'HH:MM:ss' and the no offset string '0'.
1956      * If the parse starts with 'UT', 'UTC' or 'GMT', and the parser cannot
1957      * match a following offset ID, then {@link ZoneOffset#UTC} is selected.
1958      * In all other cases, the list of known region-based zones is used to
1959      * find the longest available match. If no match is found, and the parse
1960      * starts with 'Z', then {@code ZoneOffset.UTC} is selected.
1961      * The parser uses the {@linkplain #parseCaseInsensitive() case sensitive} setting.
1962      * !(p)
1963      * For example, the following will parse:
1964      * !(pre)
1965      *   "Europe/London"           -- ZoneId.of("Europe/London")
1966      *   "Z"                       -- ZoneOffset.UTC
1967      *   "UT"                      -- ZoneId.of("UT")
1968      *   "UTC"                     -- ZoneId.of("UTC")
1969      *   "GMT"                     -- ZoneId.of("GMT")
1970      *   "+01:30"                  -- ZoneOffset.of("+01:30")
1971      *   "UT+01:30"                -- ZoneOffset.of("+01:30")
1972      *   "UTC+01:30"               -- ZoneOffset.of("+01:30")
1973      *   "GMT+01:30"               -- ZoneOffset.of("+01:30")
1974      * </pre>
1975      * !(p)
1976      * Note that this method is identical to {@code appendZoneId()} except
1977      * _in the mechanism used to obtain the zone.
1978      * Note also that parsing accepts offsets, whereas formatting will never
1979      * produce one.
1980      *
1981      * @return this, for chaining, not null
1982      * @see #appendZoneId()
1983      */
1984     public DateTimeFormatterBuilder appendZoneRegionId()
1985     {
1986         appendInternal(new ZoneIdPrinterParser(QUERY_REGION_ONLY, "ZoneRegionId()"));
1987         return this;
1988     }
1989 
1990     /**
1991      * Appends the time-zone ID, such as 'Europe/Paris' or '+02:00', to
1992      * the formatter, using the best available zone ID.
1993      * !(p)
1994      * This appends an instruction to format/parse the best available
1995      * zone or offset ID to the builder.
1996      * The zone ID is obtained _in a lenient manner that first attempts to
1997      * find a true zone ID, such as that on {@code ZonedDateTime}, and
1998      * then attempts to find an offset, such as that on {@code OffsetDateTime}.
1999      * !(p)
2000      * During formatting, the zone is obtained using a mechanism equivalent
2001      * to querying the temporal with {@link TemporalQueries#zone()}.
2002      * It will be printed using the result of {@link ZoneId#getId()}.
2003      * If the zone cannot be obtained then an exception is thrown unless the
2004      * section of the formatter is optional.
2005      * !(p)
2006      * During parsing, the text must match a known zone or offset.
2007      * There are two types of zone ID, offset-based, such as '+01:30' and
2008      * region-based, such as 'Europe/London'. These are parsed differently.
2009      * If the parse starts with '+', '-', 'UT', 'UTC' or 'GMT', then the parser
2010      * expects an offset-based zone and will not match region-based zones.
2011      * The offset ID, such as '+02:30', may be at the start of the parse,
2012      * or prefixed by  'UT', 'UTC' or 'GMT'. The offset ID parsing is
2013      * equivalent to using {@link #appendOffset(string, string)} using the
2014      * arguments 'HH:MM:ss' and the no offset string '0'.
2015      * If the parse starts with 'UT', 'UTC' or 'GMT', and the parser cannot
2016      * match a following offset ID, then {@link ZoneOffset#UTC} is selected.
2017      * In all other cases, the list of known region-based zones is used to
2018      * find the longest available match. If no match is found, and the parse
2019      * starts with 'Z', then {@code ZoneOffset.UTC} is selected.
2020      * The parser uses the {@linkplain #parseCaseInsensitive() case sensitive} setting.
2021      * !(p)
2022      * For example, the following will parse:
2023      * !(pre)
2024      *   "Europe/London"           -- ZoneId.of("Europe/London")
2025      *   "Z"                       -- ZoneOffset.UTC
2026      *   "UT"                      -- ZoneId.of("UT")
2027      *   "UTC"                     -- ZoneId.of("UTC")
2028      *   "GMT"                     -- ZoneId.of("GMT")
2029      *   "+01:30"                  -- ZoneOffset.of("+01:30")
2030      *   "UT+01:30"                -- ZoneOffset.of("UT+01:30")
2031      *   "UTC+01:30"               -- ZoneOffset.of("UTC+01:30")
2032      *   "GMT+01:30"               -- ZoneOffset.of("GMT+01:30")
2033      * </pre>
2034      * !(p)
2035      * Note that this method is identical to {@code appendZoneId()} except
2036      * _in the mechanism used to obtain the zone.
2037      *
2038      * @return this, for chaining, not null
2039      * @see #appendZoneId()
2040      */
2041     public DateTimeFormatterBuilder appendZoneOrOffsetId()
2042     {
2043         appendInternal(new ZoneIdPrinterParser(TemporalQueries.zone(), "ZoneOrOffsetId()"));
2044         return this;
2045     }
2046 
2047     /**
2048      * Appends the time-zone name, such as 'British Summer Time', to the formatter.
2049      * !(p)
2050      * This appends an instruction to format/parse the textual name of the zone to
2051      * the builder.
2052      * !(p)
2053      * During formatting, the zone is obtained using a mechanism equivalent
2054      * to querying the temporal with {@link TemporalQueries#zoneId()}.
2055      * If the zone is a {@code ZoneOffset} it will be printed using the
2056      * result of {@link ZoneOffset#getId()}.
2057      * If the zone is not an offset, the textual name will be looked up
2058      * for the locale set _in the {@link DateTimeFormatter}.
2059      * If the temporal object being printed represents an instant, or if it is a
2060      * local date-time that is not _in a daylight saving gap or overlap then
2061      * the text will be the summer or winter time text as appropriate.
2062      * If the lookup for text does not find any suitable result, then the
2063      * {@link ZoneId#getId() ID} will be printed.
2064      * If the zone cannot be obtained then an exception is thrown unless the
2065      * section of the formatter is optional.
2066      * !(p)
2067      * During parsing, either the textual zone name, the zone ID or the offset
2068      * is accepted. Many textual zone names are not unique, such as CST can be
2069      * for both "Central Standard Time" and "China Standard Time". In this
2070      * situation, the zone id will be determined by the region information from
2071      * formatter's  {@link DateTimeFormatter#getLocale() locale} and the standard
2072      * zone id for that area, for example, America/New_York for the America Eastern
2073      * zone. The {@link #appendZoneText(TextStyle, Set)} may be used
2074      * to specify a set of preferred {@link ZoneId} _in this situation.
2075      *
2076      * @param textStyle  the text style to use, not null
2077      * @return this, for chaining, not null
2078      */
2079     public DateTimeFormatterBuilder appendZoneText(TextStyle textStyle)
2080     {
2081         appendInternal(new ZoneTextPrinterParser(textStyle, null, false));
2082         return this;
2083     }
2084 
2085     /**
2086      * Appends the time-zone name, such as 'British Summer Time', to the formatter.
2087      * !(p)
2088      * This appends an instruction to format/parse the textual name of the zone to
2089      * the builder.
2090      * !(p)
2091      * During formatting, the zone is obtained using a mechanism equivalent
2092      * to querying the temporal with {@link TemporalQueries#zoneId()}.
2093      * If the zone is a {@code ZoneOffset} it will be printed using the
2094      * result of {@link ZoneOffset#getId()}.
2095      * If the zone is not an offset, the textual name will be looked up
2096      * for the locale set _in the {@link DateTimeFormatter}.
2097      * If the temporal object being printed represents an instant, or if it is a
2098      * local date-time that is not _in a daylight saving gap or overlap, then the text
2099      * will be the summer or winter time text as appropriate.
2100      * If the lookup for text does not find any suitable result, then the
2101      * {@link ZoneId#getId() ID} will be printed.
2102      * If the zone cannot be obtained then an exception is thrown unless the
2103      * section of the formatter is optional.
2104      * !(p)
2105      * During parsing, either the textual zone name, the zone ID or the offset
2106      * is accepted. Many textual zone names are not unique, such as CST can be
2107      * for both "Central Standard Time" and "China Standard Time". In this
2108      * situation, the zone id will be determined by the region information from
2109      * formatter's  {@link DateTimeFormatter#getLocale() locale} and the standard
2110      * zone id for that area, for example, America/New_York for the America Eastern
2111      * zone. This method also allows a set of preferred {@link ZoneId} to be
2112      * specified for parsing. The matched preferred zone id will be used if the
2113      * textural zone name being parsed is not unique.
2114      * !(p)
2115      * If the zone cannot be parsed then an exception is thrown unless the
2116      * section of the formatter is optional.
2117      *
2118      * @param textStyle  the text style to use, not null
2119      * @param preferredZones  the set of preferred zone ids, not null
2120      * @return this, for chaining, not null
2121      */
2122     public DateTimeFormatterBuilder appendZoneText(TextStyle textStyle, Set!(ZoneId) preferredZones)
2123     {
2124         assert(preferredZones, "preferredZones");
2125         appendInternal(new ZoneTextPrinterParser(textStyle, preferredZones, false));
2126         return this;
2127     }
2128     //----------------------------------------------------------------------
2129     /**
2130      * Appends the generic time-zone name, such as 'Pacific Time', to the formatter.
2131      * !(p)
2132      * This appends an instruction to format/parse the generic textual
2133      * name of the zone to the builder. The generic name is the same throughout the whole
2134      * year, ignoring any daylight saving changes. For example, 'Pacific Time' is the
2135      * generic name, whereas 'Pacific Standard Time' and 'Pacific Daylight Time' are the
2136      * specific names, see {@link #appendZoneText(TextStyle)}.
2137      * !(p)
2138      * During formatting, the zone is obtained using a mechanism equivalent
2139      * to querying the temporal with {@link TemporalQueries#zoneId()}.
2140      * If the zone is a {@code ZoneOffset} it will be printed using the
2141      * result of {@link ZoneOffset#getId()}.
2142      * If the zone is not an offset, the textual name will be looked up
2143      * for the locale set _in the {@link DateTimeFormatter}.
2144      * If the lookup for text does not find any suitable result, then the
2145      * {@link ZoneId#getId() ID} will be printed.
2146      * If the zone cannot be obtained then an exception is thrown unless the
2147      * section of the formatter is optional.
2148      * !(p)
2149      * During parsing, either the textual zone name, the zone ID or the offset
2150      * is accepted. Many textual zone names are not unique, such as CST can be
2151      * for both "Central Standard Time" and "China Standard Time". In this
2152      * situation, the zone id will be determined by the region information from
2153      * formatter's  {@link DateTimeFormatter#getLocale() locale} and the standard
2154      * zone id for that area, for example, America/New_York for the America Eastern zone.
2155      * The {@link #appendGenericZoneText(TextStyle, Set)} may be used
2156      * to specify a set of preferred {@link ZoneId} _in this situation.
2157      *
2158      * @param textStyle  the text style to use, not null
2159      * @return this, for chaining, not null
2160      * @since 9
2161      */
2162     public DateTimeFormatterBuilder appendGenericZoneText(TextStyle textStyle)
2163     {
2164         appendInternal(new ZoneTextPrinterParser(textStyle, null, true));
2165         return this;
2166     }
2167 
2168     /**
2169      * Appends the generic time-zone name, such as 'Pacific Time', to the formatter.
2170      * !(p)
2171      * This appends an instruction to format/parse the generic textual
2172      * name of the zone to the builder. The generic name is the same throughout the whole
2173      * year, ignoring any daylight saving changes. For example, 'Pacific Time' is the
2174      * generic name, whereas 'Pacific Standard Time' and 'Pacific Daylight Time' are the
2175      * specific names, see {@link #appendZoneText(TextStyle)}.
2176      * !(p)
2177      * This method also allows a set of preferred {@link ZoneId} to be
2178      * specified for parsing. The matched preferred zone id will be used if the
2179      * textural zone name being parsed is not unique.
2180      * !(p)
2181      * See {@link #appendGenericZoneText(TextStyle)} for details about
2182      * formatting and parsing.
2183      *
2184      * @param textStyle  the text style to use, not null
2185      * @param preferredZones  the set of preferred zone ids, not null
2186      * @return this, for chaining, not null
2187      * @since 9
2188      */
2189     public DateTimeFormatterBuilder appendGenericZoneText(TextStyle textStyle,
2190             Set!(ZoneId) preferredZones)
2191     {
2192         appendInternal(new ZoneTextPrinterParser(textStyle, preferredZones, true));
2193         return this;
2194     }
2195 
2196     //-----------------------------------------------------------------------
2197     /**
2198      * Appends the chronology ID, such as 'ISO' or 'ThaiBuddhist', to the formatter.
2199      * !(p)
2200      * This appends an instruction to format/parse the chronology ID to the builder.
2201      * !(p)
2202      * During formatting, the chronology is obtained using a mechanism equivalent
2203      * to querying the temporal with {@link TemporalQueries#chronology()}.
2204      * It will be printed using the result of {@link Chronology#getId()}.
2205      * If the chronology cannot be obtained then an exception is thrown unless the
2206      * section of the formatter is optional.
2207      * !(p)
2208      * During parsing, the chronology is parsed and must match one of the chronologies
2209      * _in {@link Chronology#getAvailableChronologies()}.
2210      * If the chronology cannot be parsed then an exception is thrown unless the
2211      * section of the formatter is optional.
2212      * The parser uses the {@linkplain #parseCaseInsensitive() case sensitive} setting.
2213      *
2214      * @return this, for chaining, not null
2215      */
2216     public DateTimeFormatterBuilder appendChronologyId()
2217     {
2218         appendInternal(new ChronoPrinterParser(null));
2219         return this;
2220     }
2221 
2222     /**
2223      * Appends the chronology name to the formatter.
2224      * !(p)
2225      * The calendar system name will be output during a format.
2226      * If the chronology cannot be obtained then an exception will be thrown.
2227      *
2228      * @param textStyle  the text style to use, not null
2229      * @return this, for chaining, not null
2230      */
2231     public DateTimeFormatterBuilder appendChronologyText(TextStyle textStyle)
2232     {
2233         assert(textStyle, "textStyle");
2234         appendInternal(new ChronoPrinterParser(textStyle));
2235         return this;
2236     }
2237 
2238     //-----------------------------------------------------------------------
2239     /**
2240      * Appends a localized date-time pattern to the formatter.
2241      * !(p)
2242      * This appends a localized section to the builder, suitable for outputting
2243      * a date, time or date-time combination. The format of the localized
2244      * section is lazily looked up based on four items:
2245      * !(ul)
2246      * !(li)the {@code dateStyle} specified to this method
2247      * !(li)the {@code timeStyle} specified to this method
2248      * !(li)the {@code Locale} of the {@code DateTimeFormatter}
2249      * !(li)the {@code Chronology}, selecting the best available
2250      * </ul>
2251      * During formatting, the chronology is obtained from the temporal object
2252      * being formatted, which may have been overridden by
2253      * {@link DateTimeFormatter#withChronology(Chronology)}.
2254      * The {@code FULL} and {@code LONG} styles typically require a time-zone.
2255      * When formatting using these styles, a {@code ZoneId} must be available,
2256      * either by using {@code ZonedDateTime} or {@link DateTimeFormatter#withZone}.
2257      * !(p)
2258      * During parsing, if a chronology has already been parsed, then it is used.
2259      * Otherwise the default from {@code DateTimeFormatter.withChronology(Chronology)}
2260      * is used, with {@code IsoChronology} as the fallback.
2261      * !(p)
2262      * Note that this method provides similar functionality to methods on
2263      * {@code DateFormat} such as {@link java.text.DateFormat#getDateTimeInstance(int, int)}.
2264      *
2265      * @param dateStyle  the date style to use, null means no date required
2266      * @param timeStyle  the time style to use, null means no time required
2267      * @return this, for chaining, not null
2268      * @throws IllegalArgumentException if both the date and time styles are null
2269      */
2270     public DateTimeFormatterBuilder appendLocalized(FormatStyle dateStyle, FormatStyle timeStyle)
2271     {
2272         if (dateStyle is null && timeStyle is null)
2273         {
2274             throw new IllegalArgumentException("Either the date or time style must be non-null");
2275         }
2276         appendInternal(new LocalizedPrinterParser(dateStyle, timeStyle));
2277         return this;
2278     }
2279 
2280     //-----------------------------------------------------------------------
2281     /**
2282      * Appends a character literal to the formatter.
2283      * !(p)
2284      * This character will be output during a format.
2285      *
2286      * @param literal  the literal to append, not null
2287      * @return this, for chaining, not null
2288      */
2289     public DateTimeFormatterBuilder appendLiteral(char literal)
2290     {
2291         appendInternal(new CharLiteralPrinterParser(literal));
2292         return this;
2293     }
2294 
2295     /**
2296      * Appends a string literal to the formatter.
2297      * !(p)
2298      * This string will be output during a format.
2299      * !(p)
2300      * If the literal is empty, nothing is added to the formatter.
2301      *
2302      * @param literal  the literal to append, not null
2303      * @return this, for chaining, not null
2304      */
2305     public DateTimeFormatterBuilder appendLiteral(string literal)
2306     {
2307         assert(literal, "literal");
2308         if (literal.length > 0)
2309         {
2310             if (literal.length == 1)
2311             {
2312                 appendInternal(new CharLiteralPrinterParser(literal[0]));
2313             }
2314             else
2315             {
2316                 appendInternal(new StringLiteralPrinterParser(literal));
2317             }
2318         }
2319         return this;
2320     }
2321 
2322     //-----------------------------------------------------------------------
2323     /**
2324      * Appends all the elements of a formatter to the builder.
2325      * !(p)
2326      * This method has the same effect as appending each of the constituent
2327      * parts of the formatter directly to this builder.
2328      *
2329      * @param formatter  the formatter to add, not null
2330      * @return this, for chaining, not null
2331      */
2332     public DateTimeFormatterBuilder append(DateTimeFormatter formatter)
2333     {
2334         assert(formatter, "formatter");
2335         appendInternal(formatter.toPrinterParser(false));
2336         return this;
2337     }
2338 
2339     /**
2340      * Appends a formatter to the builder which will optionally format/parse.
2341      * !(p)
2342      * This method has the same effect as appending each of the constituent
2343      * parts directly to this builder surrounded by an {@link #optionalStart()} and
2344      * {@link #optionalEnd()}.
2345      * !(p)
2346      * The formatter will format if data is available for all the fields contained within it.
2347      * The formatter will parse if the string matches, otherwise no error is returned.
2348      *
2349      * @param formatter  the formatter to add, not null
2350      * @return this, for chaining, not null
2351      */
2352     public DateTimeFormatterBuilder appendOptional(DateTimeFormatter formatter)
2353     {
2354         assert(formatter, "formatter");
2355         appendInternal(formatter.toPrinterParser(true));
2356         return this;
2357     }
2358 
2359     //-----------------------------------------------------------------------
2360     /**
2361      * Appends the elements defined by the specified pattern to the builder.
2362      * !(p)
2363      * All letters 'A' to 'Z' and 'a' to 'z' are reserved as pattern letters.
2364      * The characters '#', '{' and '}' are reserved for future use.
2365      * The characters '[' and ']' indicate optional patterns.
2366      * The following pattern letters are defined:
2367      * !(pre)
2368      *  Symbol  Meaning                     Presentation      Examples
2369      *  ------  -------                     ------------      -------
2370      *   G       era                         text              AD; Anno Domini; A
2371      *   u       year                        year              2004; 04
2372      *   y       year-of-era                 year              2004; 04
2373      *   D       day-of-year                 number            189
2374      *   M/L     month-of-year               number/text       7; 07; Jul; July; J
2375      *   d       day-of-month                number            10
2376      *   g       modified-julian-day         number            2451334
2377      *
2378      *   Q/q     quarter-of-year             number/text       3; 03; Q3; 3rd quarter
2379      *   Y       week-based-year             year              1996; 96
2380      *   w       week-of-week-based-year     number            27
2381      *   W       week-of-month               number            4
2382      *   E       day-of-week                 text              Tue; Tuesday; T
2383      *   e/c     localized day-of-week       number/text       2; 02; Tue; Tuesday; T
2384      *   F       day-of-week-_in-month        number            3
2385      *
2386      *   a       am-pm-of-day                text              PM
2387      *   h       clock-hour-of-am-pm (1-12)  number            12
2388      *   K       hour-of-am-pm (0-11)        number            0
2389      *   k       clock-hour-of-day (1-24)    number            24
2390      *
2391      *   H       hour-of-day (0-23)          number            0
2392      *   m       minute-of-hour              number            30
2393      *   s       second-of-minute            number            55
2394      *   S       fraction-of-second          fraction          978
2395      *   A       milli-of-day                number            1234
2396      *   n       nano-of-second              number            987654321
2397      *   N       nano-of-day                 number            1234000000
2398      *
2399      *   V       time-zone ID                zone-id           America/Los_Angeles; Z; -08:30
2400      *   v       generic time-zone name      zone-name         PT, Pacific Time
2401      *   z       time-zone name              zone-name         Pacific Standard Time; PST
2402      *   O       localized zone-offset       offset-O          GMT+8; GMT+08:00; UTC-08:00;
2403      *   X       zone-offset 'Z' for zero    offset-X          Z; -08; -0830; -08:30; -083015; -08:30:15
2404      *   x       zone-offset                 offset-x          +0000; -08; -0830; -08:30; -083015; -08:30:15
2405      *   Z       zone-offset                 offset-Z          +0000; -0800; -08:00
2406      *
2407      *   p       pad next                    pad modifier      1
2408      *
2409      *   '       escape for text             delimiter
2410      *   ''      single quote                literal           '
2411      *   [       optional section start
2412      *   ]       optional section end
2413      *   #       reserved for future use
2414      *   {       reserved for future use
2415      *   }       reserved for future use
2416      * </pre>
2417      * !(p)
2418      * The count of pattern letters determine the format.
2419      * See <a href="DateTimeFormatter.html#patterns">DateTimeFormatter</a> for a user-focused description of the patterns.
2420      * The following tables define how the pattern letters map to the builder.
2421      * !(p)
2422      * !(b)Date fields</b>: Pattern letters to output a date.
2423      * !(pre)
2424      *  Pattern  Count  Equivalent builder methods
2425      *  -------  -----  --------------------------
2426      *    G       1      appendText(ChronoField.ERA, TextStyle.SHORT)
2427      *    GG      2      appendText(ChronoField.ERA, TextStyle.SHORT)
2428      *    GGG     3      appendText(ChronoField.ERA, TextStyle.SHORT)
2429      *    GGGG    4      appendText(ChronoField.ERA, TextStyle.FULL)
2430      *    GGGGG   5      appendText(ChronoField.ERA, TextStyle.NARROW)
2431      *
2432      *    u       1      appendValue(ChronoField.YEAR, 1, 19, SignStyle.NORMAL)
2433      *    uu      2      appendValueReduced(ChronoField.YEAR, 2, 2000)
2434      *    uuu     3      appendValue(ChronoField.YEAR, 3, 19, SignStyle.NORMAL)
2435      *    u..u    4..n   appendValue(ChronoField.YEAR, n, 19, SignStyle.EXCEEDS_PAD)
2436      *    y       1      appendValue(ChronoField.YEAR_OF_ERA, 1, 19, SignStyle.NORMAL)
2437      *    yy      2      appendValueReduced(ChronoField.YEAR_OF_ERA, 2, 2000)
2438      *    yyy     3      appendValue(ChronoField.YEAR_OF_ERA, 3, 19, SignStyle.NORMAL)
2439      *    y..y    4..n   appendValue(ChronoField.YEAR_OF_ERA, n, 19, SignStyle.EXCEEDS_PAD)
2440      *    Y       1      append special localized WeekFields element for numeric week-based-year
2441      *    YY      2      append special localized WeekFields element for reduced numeric week-based-year 2 digits
2442      *    YYY     3      append special localized WeekFields element for numeric week-based-year (3, 19, SignStyle.NORMAL)
2443      *    Y..Y    4..n   append special localized WeekFields element for numeric week-based-year (n, 19, SignStyle.EXCEEDS_PAD)
2444      *
2445      *    Q       1      appendValue(IsoFields.QUARTER_OF_YEAR)
2446      *    QQ      2      appendValue(IsoFields.QUARTER_OF_YEAR, 2)
2447      *    QQQ     3      appendText(IsoFields.QUARTER_OF_YEAR, TextStyle.SHORT)
2448      *    QQQQ    4      appendText(IsoFields.QUARTER_OF_YEAR, TextStyle.FULL)
2449      *    QQQQQ   5      appendText(IsoFields.QUARTER_OF_YEAR, TextStyle.NARROW)
2450      *    q       1      appendValue(IsoFields.QUARTER_OF_YEAR)
2451      *    qq      2      appendValue(IsoFields.QUARTER_OF_YEAR, 2)
2452      *    qqq     3      appendText(IsoFields.QUARTER_OF_YEAR, TextStyle.SHORT_STANDALONE)
2453      *    qqqq    4      appendText(IsoFields.QUARTER_OF_YEAR, TextStyle.FULL_STANDALONE)
2454      *    qqqqq   5      appendText(IsoFields.QUARTER_OF_YEAR, TextStyle.NARROW_STANDALONE)
2455      *
2456      *    M       1      appendValue(ChronoField.MONTH_OF_YEAR)
2457      *    MM      2      appendValue(ChronoField.MONTH_OF_YEAR, 2)
2458      *    MMM     3      appendText(ChronoField.MONTH_OF_YEAR, TextStyle.SHORT)
2459      *    MMMM    4      appendText(ChronoField.MONTH_OF_YEAR, TextStyle.FULL)
2460      *    MMMMM   5      appendText(ChronoField.MONTH_OF_YEAR, TextStyle.NARROW)
2461      *    L       1      appendValue(ChronoField.MONTH_OF_YEAR)
2462      *    LL      2      appendValue(ChronoField.MONTH_OF_YEAR, 2)
2463      *    LLL     3      appendText(ChronoField.MONTH_OF_YEAR, TextStyle.SHORT_STANDALONE)
2464      *    LLLL    4      appendText(ChronoField.MONTH_OF_YEAR, TextStyle.FULL_STANDALONE)
2465      *    LLLLL   5      appendText(ChronoField.MONTH_OF_YEAR, TextStyle.NARROW_STANDALONE)
2466      *
2467      *    w       1      append special localized WeekFields element for numeric week-of-year
2468      *    ww      2      append special localized WeekFields element for numeric week-of-year, zero-padded
2469      *    W       1      append special localized WeekFields element for numeric week-of-month
2470      *    d       1      appendValue(ChronoField.DAY_OF_MONTH)
2471      *    dd      2      appendValue(ChronoField.DAY_OF_MONTH, 2)
2472      *    D       1      appendValue(ChronoField.DAY_OF_YEAR)
2473      *    DD      2      appendValue(ChronoField.DAY_OF_YEAR, 2, 3, SignStyle.NOT_NEGATIVE)
2474      *    DDD     3      appendValue(ChronoField.DAY_OF_YEAR, 3)
2475      *    F       1      appendValue(ChronoField.ALIGNED_DAY_OF_WEEK_IN_MONTH)
2476      *    g..g    1..n   appendValue(JulianFields.MODIFIED_JULIAN_DAY, n, 19, SignStyle.NORMAL)
2477      *    E       1      appendText(ChronoField.DAY_OF_WEEK, TextStyle.SHORT)
2478      *    EE      2      appendText(ChronoField.DAY_OF_WEEK, TextStyle.SHORT)
2479      *    EEE     3      appendText(ChronoField.DAY_OF_WEEK, TextStyle.SHORT)
2480      *    EEEE    4      appendText(ChronoField.DAY_OF_WEEK, TextStyle.FULL)
2481      *    EEEEE   5      appendText(ChronoField.DAY_OF_WEEK, TextStyle.NARROW)
2482      *    e       1      append special localized WeekFields element for numeric day-of-week
2483      *    ee      2      append special localized WeekFields element for numeric day-of-week, zero-padded
2484      *    eee     3      appendText(ChronoField.DAY_OF_WEEK, TextStyle.SHORT)
2485      *    eeee    4      appendText(ChronoField.DAY_OF_WEEK, TextStyle.FULL)
2486      *    eeeee   5      appendText(ChronoField.DAY_OF_WEEK, TextStyle.NARROW)
2487      *    c       1      append special localized WeekFields element for numeric day-of-week
2488      *    ccc     3      appendText(ChronoField.DAY_OF_WEEK, TextStyle.SHORT_STANDALONE)
2489      *    cccc    4      appendText(ChronoField.DAY_OF_WEEK, TextStyle.FULL_STANDALONE)
2490      *    ccccc   5      appendText(ChronoField.DAY_OF_WEEK, TextStyle.NARROW_STANDALONE)
2491      * </pre>
2492      * !(p)
2493      * !(b)Time fields</b>: Pattern letters to output a time.
2494      * !(pre)
2495      *  Pattern  Count  Equivalent builder methods
2496      *  -------  -----  --------------------------
2497      *    a       1      appendText(ChronoField.AMPM_OF_DAY, TextStyle.SHORT)
2498      *    h       1      appendValue(ChronoField.CLOCK_HOUR_OF_AMPM)
2499      *    hh      2      appendValue(ChronoField.CLOCK_HOUR_OF_AMPM, 2)
2500      *    H       1      appendValue(ChronoField.HOUR_OF_DAY)
2501      *    HH      2      appendValue(ChronoField.HOUR_OF_DAY, 2)
2502      *    k       1      appendValue(ChronoField.CLOCK_HOUR_OF_DAY)
2503      *    kk      2      appendValue(ChronoField.CLOCK_HOUR_OF_DAY, 2)
2504      *    K       1      appendValue(ChronoField.HOUR_OF_AMPM)
2505      *    KK      2      appendValue(ChronoField.HOUR_OF_AMPM, 2)
2506      *    m       1      appendValue(ChronoField.MINUTE_OF_HOUR)
2507      *    mm      2      appendValue(ChronoField.MINUTE_OF_HOUR, 2)
2508      *    s       1      appendValue(ChronoField.SECOND_OF_MINUTE)
2509      *    ss      2      appendValue(ChronoField.SECOND_OF_MINUTE, 2)
2510      *
2511      *    S..S    1..n   appendFraction(ChronoField.NANO_OF_SECOND, n, n, false)
2512      *    A..A    1..n   appendValue(ChronoField.MILLI_OF_DAY, n, 19, SignStyle.NOT_NEGATIVE)
2513      *    n..n    1..n   appendValue(ChronoField.NANO_OF_SECOND, n, 19, SignStyle.NOT_NEGATIVE)
2514      *    N..N    1..n   appendValue(ChronoField.NANO_OF_DAY, n, 19, SignStyle.NOT_NEGATIVE)
2515      * </pre>
2516      * !(p)
2517      * !(b)Zone ID</b>: Pattern letters to output {@code ZoneId}.
2518      * !(pre)
2519      *  Pattern  Count  Equivalent builder methods
2520      *  -------  -----  --------------------------
2521      *    VV      2      appendZoneId()
2522      *    v       1      appendGenericZoneText(TextStyle.SHORT)
2523      *    vvvv    4      appendGenericZoneText(TextStyle.FULL)
2524      *    z       1      appendZoneText(TextStyle.SHORT)
2525      *    zz      2      appendZoneText(TextStyle.SHORT)
2526      *    zzz     3      appendZoneText(TextStyle.SHORT)
2527      *    zzzz    4      appendZoneText(TextStyle.FULL)
2528      * </pre>
2529      * !(p)
2530      * !(b)Zone offset</b>: Pattern letters to output {@code ZoneOffset}.
2531      * !(pre)
2532      *  Pattern  Count  Equivalent builder methods
2533      *  -------  -----  --------------------------
2534      *    O       1      appendLocalizedOffset(TextStyle.SHORT)
2535      *    OOOO    4      appendLocalizedOffset(TextStyle.FULL)
2536      *    X       1      appendOffset("+HHmm","Z")
2537      *    XX      2      appendOffset("+HHMM","Z")
2538      *    XXX     3      appendOffset("+HH:MM","Z")
2539      *    XXXX    4      appendOffset("+HHMMss","Z")
2540      *    XXXXX   5      appendOffset("+HH:MM:ss","Z")
2541      *    x       1      appendOffset("+HHmm","+00")
2542      *    xx      2      appendOffset("+HHMM","+0000")
2543      *    xxx     3      appendOffset("+HH:MM","+00:00")
2544      *    xxxx    4      appendOffset("+HHMMss","+0000")
2545      *    xxxxx   5      appendOffset("+HH:MM:ss","+00:00")
2546      *    Z       1      appendOffset("+HHMM","+0000")
2547      *    ZZ      2      appendOffset("+HHMM","+0000")
2548      *    ZZZ     3      appendOffset("+HHMM","+0000")
2549      *    ZZZZ    4      appendLocalizedOffset(TextStyle.FULL)
2550      *    ZZZZZ   5      appendOffset("+HH:MM:ss","Z")
2551      * </pre>
2552      * !(p)
2553      * !(b)Modifiers</b>: Pattern letters that modify the rest of the pattern:
2554      * !(pre)
2555      *  Pattern  Count  Equivalent builder methods
2556      *  -------  -----  --------------------------
2557      *    [       1      optionalStart()
2558      *    ]       1      optionalEnd()
2559      *    p..p    1..n   padNext(n)
2560      * </pre>
2561      * !(p)
2562      * Any sequence of letters not specified above, unrecognized letter or
2563      * reserved character will throw an exception.
2564      * Future versions may add to the set of patterns.
2565      * It is recommended to use single quotes around all characters that you want
2566      * to output directly to ensure that future changes do not break your application.
2567      * !(p)
2568      * Note that the pattern string is similar, but not identical, to
2569      * {@link java.text.SimpleDateFormat SimpleDateFormat}.
2570      * The pattern string is also similar, but not identical, to that defined by the
2571      * Unicode Common Locale Data Repository (CLDR/LDML).
2572      * Pattern letters 'X' and 'u' are aligned with Unicode CLDR/LDML.
2573      * By contrast, {@code SimpleDateFormat} uses 'u' for the numeric day of week.
2574      * Pattern letters 'y' and 'Y' parse years of two digits and more than 4 digits differently.
2575      * Pattern letters 'n', 'A', 'N', and 'p' are added.
2576      * Number types will reject large numbers.
2577      *
2578      * @param pattern  the pattern to add, not null
2579      * @return this, for chaining, not null
2580      * @throws IllegalArgumentException if the pattern is invalid
2581      */
2582     public DateTimeFormatterBuilder appendPattern(string pattern)
2583     {
2584         assert(pattern, "pattern");
2585         parsePattern(pattern);
2586         return this;
2587     }
2588 
2589     private void parsePattern(string pattern)
2590     {
2591         for (int pos = 0; pos < pattern.length; pos++)
2592         {
2593             char cur = pattern[pos];
2594             if ((cur >= 'A' && cur <= 'Z') || (cur >= 'a' && cur <= 'z'))
2595             {
2596                 int start = pos++;
2597                 for (; pos < pattern.length && pattern[pos] == cur; pos++)
2598                 {
2599                 } // short loop
2600                 int count = pos - start;
2601                 // padding
2602                 if (cur == 'p')
2603                 {
2604                     int pad = 0;
2605                     if (pos < pattern.length)
2606                     {
2607                         cur = pattern[pos];
2608                         if ((cur >= 'A' && cur <= 'Z') || (cur >= 'a' && cur <= 'z'))
2609                         {
2610                             pad = count;
2611                             start = pos++;
2612                             for (; pos < pattern.length && pattern[pos] == cur;
2613                                     pos++)
2614                             {
2615                             } // short loop
2616                             count = pos - start;
2617                         }
2618                     }
2619                     if (pad == 0)
2620                     {
2621                         throw new IllegalArgumentException(
2622                                 "Pad letter 'p' must be followed by valid pad pattern: " ~ pattern);
2623                     }
2624                     padNext(pad); // pad and continue parsing
2625                 }
2626                 // main rules
2627                 TemporalField field = FIELD_MAP.get(cur);
2628                 if (field !is null)
2629                 {
2630                     parseField(cur, count, field);
2631                 }
2632                 else if (cur == 'z')
2633                 {
2634                     if (count > 4)
2635                     {
2636                         throw new IllegalArgumentException("Too many pattern letters: " ~ cur);
2637                     }
2638                     else if (count == 4)
2639                     {
2640                         appendZoneText(TextStyle.FULL);
2641                     }
2642                     else
2643                     {
2644                         appendZoneText(TextStyle.SHORT);
2645                     }
2646                 }
2647                 else if (cur == 'V')
2648                 {
2649                     if (count != 2)
2650                     {
2651                         throw new IllegalArgumentException("Pattern letter count must be 2: " ~ cur);
2652                     }
2653                     appendZoneId();
2654                 }
2655                 else if (cur == 'v')
2656                 {
2657                     if (count == 1)
2658                     {
2659                         appendGenericZoneText(TextStyle.SHORT);
2660                     }
2661                     else if (count == 4)
2662                     {
2663                         appendGenericZoneText(TextStyle.FULL);
2664                     }
2665                     else
2666                     {
2667                         throw new IllegalArgumentException(
2668                                 "Wrong number of  pattern letters: " ~ cur);
2669                     }
2670                 }
2671                 else if (cur == 'Z')
2672                 {
2673                     if (count < 4)
2674                     {
2675                         appendOffset("+HHMM", "+0000");
2676                     }
2677                     else if (count == 4)
2678                     {
2679                         appendLocalizedOffset(TextStyle.FULL);
2680                     }
2681                     else if (count == 5)
2682                     {
2683                         appendOffset("+HH:MM:ss", "Z");
2684                     }
2685                     else
2686                     {
2687                         throw new IllegalArgumentException("Too many pattern letters: " ~ cur);
2688                     }
2689                 }
2690                 else if (cur == 'O')
2691                 {
2692                     if (count == 1)
2693                     {
2694                         appendLocalizedOffset(TextStyle.SHORT);
2695                     }
2696                     else if (count == 4)
2697                     {
2698                         appendLocalizedOffset(TextStyle.FULL);
2699                     }
2700                     else
2701                     {
2702                         throw new IllegalArgumentException(
2703                                 "Pattern letter count must be 1 or 4: " ~ cur);
2704                     }
2705                 }
2706                 else if (cur == 'X')
2707                 {
2708                     if (count > 5)
2709                     {
2710                         throw new IllegalArgumentException("Too many pattern letters: " ~ cur);
2711                     }
2712                     appendOffset(OffsetIdPrinterParser.PATTERNS[count + (count == 1 ? 0 : 1)], "Z");
2713                 }
2714                 else if (cur == 'x')
2715                 {
2716                     if (count > 5)
2717                     {
2718                         throw new IllegalArgumentException("Too many pattern letters: " ~ cur);
2719                     }
2720                     string zero = (count == 1 ? "+00" : (count % 2 == 0 ? "+0000" : "+00:00"));
2721                     appendOffset(OffsetIdPrinterParser.PATTERNS[count + (count == 1 ? 0 : 1)], zero);
2722                 }
2723                 else if (cur == 'W')
2724                 {
2725                     // Fields defined by Locale
2726                     if (count > 1)
2727                     {
2728                         throw new IllegalArgumentException("Too many pattern letters: " ~ cur);
2729                     }
2730                     appendValue(new WeekBasedFieldPrinterParser(cur, count, count, count));
2731                 }
2732                 else if (cur == 'w')
2733                 {
2734                     // Fields defined by Locale
2735                     if (count > 2)
2736                     {
2737                         throw new IllegalArgumentException("Too many pattern letters: " ~ cur);
2738                     }
2739                     appendValue(new WeekBasedFieldPrinterParser(cur, count, count, 2));
2740                 }
2741                 else if (cur == 'Y')
2742                 {
2743                     // Fields defined by Locale
2744                     if (count == 2)
2745                     {
2746                         appendValue(new WeekBasedFieldPrinterParser(cur, count, count, 2));
2747                     }
2748                     else
2749                     {
2750                         appendValue(new WeekBasedFieldPrinterParser(cur, count, count, 19));
2751                     }
2752                 }
2753                 else
2754                 {
2755                     throw new IllegalArgumentException("Unknown pattern letter: " ~ cur);
2756                 }
2757                 pos--;
2758 
2759             }
2760             else if (cur == '\'')
2761             {
2762                 // parse literals
2763                 int start = pos++;
2764                 for (; pos < pattern.length; pos++)
2765                 {
2766                     if (pattern[pos] == '\'')
2767                     {
2768                         if (pos + 1 < pattern.length && pattern[pos + 1] == '\'')
2769                         {
2770                             pos++;
2771                         }
2772                         else
2773                         {
2774                             break; // end of literal
2775                         }
2776                     }
2777                 }
2778                 if (pos >= pattern.length)
2779                 {
2780                     throw new IllegalArgumentException(
2781                             "Pattern ends with an incomplete string literal: " ~ pattern);
2782                 }
2783                 string str = pattern.substring(start + 1, pos);
2784                 if (str.length == 0)
2785                 {
2786                     appendLiteral('\'');
2787                 }
2788                 else
2789                 {
2790                     import std.array;
2791 
2792                     appendLiteral(str.replace("''", "'"));
2793                 }
2794 
2795             }
2796             else if (cur == '[')
2797             {
2798                 optionalStart();
2799 
2800             }
2801             else if (cur == ']')
2802             {
2803                 if (active.parent is null)
2804                 {
2805                     throw new IllegalArgumentException(
2806                             "Pattern invalid as it contains ] without previous [");
2807                 }
2808                 optionalEnd();
2809 
2810             }
2811             else if (cur == '{' || cur == '}' || cur == '#')
2812             {
2813                 throw new IllegalArgumentException(
2814                         "Pattern includes reserved character: '" ~ cur ~ "'");
2815             }
2816             else
2817             {
2818                 appendLiteral(cur);
2819             }
2820         }
2821     }
2822 
2823     // @SuppressWarnings("fallthrough")
2824     private void parseField(char cur, int count, TemporalField field)
2825     {
2826         bool standalone = false;
2827         switch (cur)
2828         {
2829         case 'u':
2830         case 'y':
2831             if (count == 2)
2832             {
2833                 appendValueReduced(field, 2, 2, ReducedPrinterParser.BASE_DATE);
2834             }
2835             else if (count < 4)
2836             {
2837                 appendValue(field, count, 19, SignStyle.NORMAL);
2838             }
2839             else
2840             {
2841                 appendValue(field, count, 19, SignStyle.EXCEEDS_PAD);
2842             }
2843             break;
2844         case 'c':
2845             if (count == 1)
2846             {
2847                 appendValue(new WeekBasedFieldPrinterParser(cur, count, count, count));
2848                 break;
2849             }
2850             else if (count == 2)
2851             {
2852                 throw new IllegalArgumentException("Invalid pattern \"cc\"");
2853             }
2854             /*fallthrough*/
2855             goto case 'L';
2856         case 'L':
2857         case 'q':
2858             standalone = true;
2859             /*fallthrough*/
2860             goto case 'M';
2861         case 'M':
2862         case 'Q':
2863         case 'E':
2864         case 'e':
2865             switch (count)
2866             {
2867             case 1:
2868             case 2:
2869                 if (cur == 'e')
2870                 {
2871                     appendValue(new WeekBasedFieldPrinterParser(cur, count, count, count));
2872                 }
2873                 else if (cur == 'E')
2874                 {
2875                     appendText(field, TextStyle.SHORT);
2876                 }
2877                 else
2878                 {
2879                     if (count == 1)
2880                     {
2881                         appendValue(field);
2882                     }
2883                     else
2884                     {
2885                         appendValue(field, 2);
2886                     }
2887                 }
2888                 break;
2889             case 3:
2890                 appendText(field, standalone ? TextStyle.SHORT_STANDALONE : TextStyle.SHORT);
2891                 break;
2892             case 4:
2893                 appendText(field, standalone ? TextStyle.FULL_STANDALONE : TextStyle.FULL);
2894                 break;
2895             case 5:
2896                 appendText(field, standalone ? TextStyle.NARROW_STANDALONE : TextStyle.NARROW);
2897                 break;
2898             default:
2899                 throw new IllegalArgumentException("Too many pattern letters: " ~ cur);
2900             }
2901             break;
2902         case 'a':
2903             if (count == 1)
2904             {
2905                 appendText(field, TextStyle.SHORT);
2906             }
2907             else
2908             {
2909                 throw new IllegalArgumentException("Too many pattern letters: " ~ cur);
2910             }
2911             break;
2912         case 'G':
2913             switch (count)
2914             {
2915             case 1:
2916             case 2:
2917             case 3:
2918                 appendText(field, TextStyle.SHORT);
2919                 break;
2920             case 4:
2921                 appendText(field, TextStyle.FULL);
2922                 break;
2923             case 5:
2924                 appendText(field, TextStyle.NARROW);
2925                 break;
2926             default:
2927                 throw new IllegalArgumentException("Too many pattern letters: " ~ cur);
2928             }
2929             break;
2930         case 'S':
2931             appendFraction(ChronoField.NANO_OF_SECOND, count, count, false);
2932             break;
2933         case 'F':
2934             if (count == 1)
2935             {
2936                 appendValue(field);
2937             }
2938             else
2939             {
2940                 throw new IllegalArgumentException("Too many pattern letters: " ~ cur);
2941             }
2942             break;
2943         case 'd':
2944         case 'h':
2945         case 'H':
2946         case 'k':
2947         case 'K':
2948         case 'm':
2949         case 's':
2950             if (count == 1)
2951             {
2952                 appendValue(field);
2953             }
2954             else if (count == 2)
2955             {
2956                 appendValue(field, count);
2957             }
2958             else
2959             {
2960                 throw new IllegalArgumentException("Too many pattern letters: " ~ cur);
2961             }
2962             break;
2963         case 'D':
2964             if (count == 1)
2965             {
2966                 appendValue(field);
2967             }
2968             else if (count == 2 || count == 3)
2969             {
2970                 appendValue(field, count, 3, SignStyle.NOT_NEGATIVE);
2971             }
2972             else
2973             {
2974                 throw new IllegalArgumentException("Too many pattern letters: " ~ cur);
2975             }
2976             break;
2977         case 'g':
2978             appendValue(field, count, 19, SignStyle.NORMAL);
2979             break;
2980         case 'A':
2981         case 'n':
2982         case 'N':
2983             appendValue(field, count, 19, SignStyle.NOT_NEGATIVE);
2984             break;
2985         default:
2986             if (count == 1)
2987             {
2988                 appendValue(field);
2989             }
2990             else
2991             {
2992                 appendValue(field, count);
2993             }
2994             break;
2995         }
2996     }
2997 
2998     /** Map of letters to fields. */
2999     // __gshared Map!(char, TemporalField) FIELD_MAP;
3000 
3001     // shared static this()
3002     // {
3003     //     FIELD_MAP = new HashMap!(char, TemporalField)();
3004         mixin(MakeGlobalVar!(Map!(char, TemporalField))("FIELD_MAP",`new HashMap!(char, TemporalField)()`));
3005     // }
3006 
3007     static Comparator!(string) LENGTH_SORT;
3008 
3009     // static this()
3010     // {
3011     //     LENGTH_SORT = new class Comparator!(string)
3012     //     {
3013     //         override public int compare(string str1, string str2)
3014     //         {
3015     //             return str1.length == str2.length ? str1.compare(str2)
3016     //                 : cast(int)(str1.length - str2.length);
3017     //         }
3018     //     };
3019     //     // SDF = SimpleDateFormat
3020     //     FIELD_MAP.put('G', ChronoField.ERA); // SDF, LDML (different to both for 1/2 chars)
3021     //     FIELD_MAP.put('y', ChronoField.YEAR_OF_ERA); // SDF, LDML
3022     //     FIELD_MAP.put('u', ChronoField.YEAR); // LDML (different _in SDF)
3023     //     FIELD_MAP.put('Q', IsoFields.QUARTER_OF_YEAR); // LDML (removed quarter from 310)
3024     //     FIELD_MAP.put('q', IsoFields.QUARTER_OF_YEAR); // LDML (stand-alone)
3025     //     FIELD_MAP.put('M', ChronoField.MONTH_OF_YEAR); // SDF, LDML
3026     //     FIELD_MAP.put('L', ChronoField.MONTH_OF_YEAR); // SDF, LDML (stand-alone)
3027     //     FIELD_MAP.put('D', ChronoField.DAY_OF_YEAR); // SDF, LDML
3028     //     FIELD_MAP.put('d', ChronoField.DAY_OF_MONTH); // SDF, LDML
3029     //     FIELD_MAP.put('F', ChronoField.ALIGNED_DAY_OF_WEEK_IN_MONTH); // SDF, LDML
3030     //     FIELD_MAP.put('E', ChronoField.DAY_OF_WEEK); // SDF, LDML (different to both for 1/2 chars)
3031     //     FIELD_MAP.put('c', ChronoField.DAY_OF_WEEK); // LDML (stand-alone)
3032     //     FIELD_MAP.put('e', ChronoField.DAY_OF_WEEK); // LDML (needs localized week number)
3033     //     FIELD_MAP.put('a', ChronoField.AMPM_OF_DAY); // SDF, LDML
3034     //     FIELD_MAP.put('H', ChronoField.HOUR_OF_DAY); // SDF, LDML
3035     //     FIELD_MAP.put('k', ChronoField.CLOCK_HOUR_OF_DAY); // SDF, LDML
3036     //     FIELD_MAP.put('K', ChronoField.HOUR_OF_AMPM); // SDF, LDML
3037     //     FIELD_MAP.put('h', ChronoField.CLOCK_HOUR_OF_AMPM); // SDF, LDML
3038     //     FIELD_MAP.put('m', ChronoField.MINUTE_OF_HOUR); // SDF, LDML
3039     //     FIELD_MAP.put('s', ChronoField.SECOND_OF_MINUTE); // SDF, LDML
3040     //     FIELD_MAP.put('S', ChronoField.NANO_OF_SECOND); // LDML (SDF uses milli-of-second number)
3041     //     FIELD_MAP.put('A', ChronoField.MILLI_OF_DAY); // LDML
3042     //     FIELD_MAP.put('n', ChronoField.NANO_OF_SECOND); // 310 (proposed for LDML)
3043     //     FIELD_MAP.put('N', ChronoField.NANO_OF_DAY); // 310 (proposed for LDML)
3044     //     // FIELD_MAP.put('g', JulianFields.MODIFIED_JULIAN_DAY);
3045     //     // 310 - z - time-zone names, matches LDML and SimpleDateFormat 1 to 4
3046     //     // 310 - Z - matches SimpleDateFormat and LDML
3047     //     // 310 - V - time-zone id, matches LDML
3048     //     // 310 - v - general timezone names, not matching exactly with LDML because LDML specify to fall back
3049     //     //           to 'VVVV' if general-nonlocation unavailable but here it's not falling back because of lack of data
3050     //     // 310 - p - prefix for padding
3051     //     // 310 - X - matches LDML, almost matches SDF for 1, exact match 2&3, extended 4&5
3052     //     // 310 - x - matches LDML
3053     //     // 310 - w, W, and Y are localized forms matching LDML
3054     //     // LDML - U - cycle year name, not supported by 310 yet
3055     //     // LDML - l - deprecated
3056     //     // LDML - j - not relevant
3057     // }
3058 
3059     //-----------------------------------------------------------------------
3060     /**
3061      * Causes the next added printer/parser to pad to a fixed width using a space.
3062      * !(p)
3063      * This padding will pad to a fixed width using spaces.
3064      * !(p)
3065      * During formatting, the decorated element will be output and then padded
3066      * to the specified width. An exception will be thrown during formatting if
3067      * the pad width is exceeded.
3068      * !(p)
3069      * During parsing, the padding and decorated element are parsed.
3070      * If parsing is lenient, then the pad width is treated as a maximum.
3071      * The padding is parsed greedily. Thus, if the decorated element starts with
3072      * the pad character, it will not be parsed.
3073      *
3074      * @param padWidth  the pad width, 1 or greater
3075      * @return this, for chaining, not null
3076      * @throws IllegalArgumentException if pad width is too small
3077      */
3078     public DateTimeFormatterBuilder padNext(int padWidth)
3079     {
3080         return padNext(padWidth, ' ');
3081     }
3082 
3083     /**
3084      * Causes the next added printer/parser to pad to a fixed width.
3085      * !(p)
3086      * This padding is intended for padding other than zero-padding.
3087      * Zero-padding should be achieved using the appendValue methods.
3088      * !(p)
3089      * During formatting, the decorated element will be output and then padded
3090      * to the specified width. An exception will be thrown during formatting if
3091      * the pad width is exceeded.
3092      * !(p)
3093      * During parsing, the padding and decorated element are parsed.
3094      * If parsing is lenient, then the pad width is treated as a maximum.
3095      * If parsing is case insensitive, then the pad character is matched ignoring case.
3096      * The padding is parsed greedily. Thus, if the decorated element starts with
3097      * the pad character, it will not be parsed.
3098      *
3099      * @param padWidth  the pad width, 1 or greater
3100      * @param padChar  the pad character
3101      * @return this, for chaining, not null
3102      * @throws IllegalArgumentException if pad width is too small
3103      */
3104     public DateTimeFormatterBuilder padNext(int padWidth, char padChar)
3105     {
3106         if (padWidth < 1)
3107         {
3108             throw new IllegalArgumentException(
3109                     "The pad width must be at least one but was " ~ padWidth.to!string);
3110         }
3111         active.padNextWidth = padWidth;
3112         active.padNextChar = padChar;
3113         active.valueParserIndex = -1;
3114         return this;
3115     }
3116 
3117     //-----------------------------------------------------------------------
3118     /**
3119      * Mark the start of an optional section.
3120      * !(p)
3121      * The output of formatting can include optional sections, which may be nested.
3122      * An optional section is started by calling this method and ended by calling
3123      * {@link #optionalEnd()} or by ending the build process.
3124      * !(p)
3125      * All elements _in the optional section are treated as optional.
3126      * During formatting, the section is only output if data is available _in the
3127      * {@code TemporalAccessor} for all the elements _in the section.
3128      * During parsing, the whole section may be missing from the parsed string.
3129      * !(p)
3130      * For example, consider a builder setup as
3131      * {@code builder.appendValue(HOUR_OF_DAY,2).optionalStart().appendValue(MINUTE_OF_HOUR,2)}.
3132      * The optional section ends automatically at the end of the builder.
3133      * During formatting, the minute will only be output if its value can be obtained from the date-time.
3134      * During parsing, the input will be successfully parsed whether the minute is present or not.
3135      *
3136      * @return this, for chaining, not null
3137      */
3138     public DateTimeFormatterBuilder optionalStart()
3139     {
3140         active.valueParserIndex = -1;
3141 
3142         _active = new DateTimeFormatterBuilder(active, true);
3143         // tmp.optional = true;
3144         // tmp.parent = this;
3145         // active = tmp;
3146         return this;
3147     }
3148 
3149     /**
3150      * Ends an optional section.
3151      * !(p)
3152      * The output of formatting can include optional sections, which may be nested.
3153      * An optional section is started by calling {@link #optionalStart()} and ended
3154      * using this method (or at the end of the builder).
3155      * !(p)
3156      * Calling this method without having previously called {@code optionalStart}
3157      * will throw an exception.
3158      * Calling this method immediately after calling {@code optionalStart} has no effect
3159      * on the formatter other than ending the (empty) optional section.
3160      * !(p)
3161      * All elements _in the optional section are treated as optional.
3162      * During formatting, the section is only output if data is available _in the
3163      * {@code TemporalAccessor} for all the elements _in the section.
3164      * During parsing, the whole section may be missing from the parsed string.
3165      * !(p)
3166      * For example, consider a builder setup as
3167      * {@code builder.appendValue(HOUR_OF_DAY,2).optionalStart().appendValue(MINUTE_OF_HOUR,2).optionalEnd()}.
3168      * During formatting, the minute will only be output if its value can be obtained from the date-time.
3169      * During parsing, the input will be successfully parsed whether the minute is present or not.
3170      *
3171      * @return this, for chaining, not null
3172      * @throws IllegalStateException if there was no previous call to {@code optionalStart}
3173      */
3174     public DateTimeFormatterBuilder optionalEnd()
3175     {
3176         if (active.parent is null)
3177         {
3178             throw new IllegalStateException(
3179                     "Cannot call optionalEnd() as there was no previous call to optionalStart()");
3180         }
3181         if (active.printerParsers.size() > 0)
3182         {
3183             CompositePrinterParser cpp = new CompositePrinterParser(active.printerParsers,
3184                     active.optional);
3185             _active = active.parent;
3186             appendInternal(cpp);
3187         }
3188         else
3189         {
3190             _active = active.parent;
3191         }
3192         return this;
3193     }
3194 
3195     //-----------------------------------------------------------------------
3196     /**
3197      * Appends a printer and/or parser to the internal list handling padding.
3198      *
3199      * @param pp  the printer-parser to add, not null
3200      * @return the index into the active parsers list
3201      */
3202     private int appendInternal(DateTimePrinterParser pp)
3203     {
3204         assert(pp, "pp");
3205         if (active.padNextWidth > 0)
3206         {
3207             if (pp !is null)
3208             {
3209                 pp = new PadPrinterParserDecorator(pp, active.padNextWidth, active.padNextChar);
3210             }
3211             active.padNextWidth = 0;
3212             active.padNextChar = 0;
3213         }
3214         active.printerParsers.add(pp);
3215         active.valueParserIndex = -1;
3216         return active.printerParsers.size() - 1;
3217     }
3218 
3219     //-----------------------------------------------------------------------
3220     /**
3221      * Completes this builder by creating the {@code DateTimeFormatter}
3222      * using the default locale.
3223      * !(p)
3224      * This will create a formatter with the {@linkplain Locale#getDefault(Locale.Category) default FORMAT locale}.
3225      * Numbers will be printed and parsed using the standard DecimalStyle.
3226      * The resolver style will be {@link ResolverStyle#SMART SMART}.
3227      * !(p)
3228      * Calling this method will end any open optional sections by repeatedly
3229      * calling {@link #optionalEnd()} before creating the formatter.
3230      * !(p)
3231      * This builder can still be used after creating the formatter if desired,
3232      * although the state may have been changed by calls to {@code optionalEnd}.
3233      *
3234      * @return the created formatter, not null
3235      */
3236     public DateTimeFormatter toFormatter()
3237     {
3238         ///@gxc
3239         // return toFormatter(Locale.getDefault(Locale.Category.FORMAT));
3240         return toFormatter(Locale.CHINESE);
3241 
3242         // implementationMissing();
3243         // return null;
3244     }
3245 
3246     /**
3247      * Completes this builder by creating the {@code DateTimeFormatter}
3248      * using the specified locale.
3249      * !(p)
3250      * This will create a formatter with the specified locale.
3251      * Numbers will be printed and parsed using the standard DecimalStyle.
3252      * The resolver style will be {@link ResolverStyle#SMART SMART}.
3253      * !(p)
3254      * Calling this method will end any open optional sections by repeatedly
3255      * calling {@link #optionalEnd()} before creating the formatter.
3256      * !(p)
3257      * This builder can still be used after creating the formatter if desired,
3258      * although the state may have been changed by calls to {@code optionalEnd}.
3259      *
3260      * @param locale  the locale to use for formatting, not null
3261      * @return the created formatter, not null
3262      */
3263     public DateTimeFormatter toFormatter(Locale locale)
3264     {
3265         return toFormatter(locale, ResolverStyle.SMART, null);
3266     }
3267 
3268     /**
3269      * Completes this builder by creating the formatter.
3270      * This uses the default locale.
3271      *
3272      * @param resolverStyle  the resolver style to use, not null
3273      * @return the created formatter, not null
3274      */
3275     DateTimeFormatter toFormatter(ResolverStyle resolverStyle, Chronology chrono)
3276     {
3277         //@gxc
3278         // return toFormatter(Locale.getDefault(Locale.Category.FORMAT), resolverStyle, chrono);
3279         return toFormatter(Locale.CHINESE, resolverStyle, chrono);
3280 
3281         // implementationMissing();
3282         // return null;
3283     }
3284 
3285     /**
3286      * Completes this builder by creating the formatter.
3287      *
3288      * @param locale  the locale to use for formatting, not null
3289      * @param chrono  the chronology to use, may be null
3290      * @return the created formatter, not null
3291      */
3292     private DateTimeFormatter toFormatter(Locale locale,
3293             ResolverStyle resolverStyle, Chronology chrono)
3294     {
3295         assert(locale, "locale");
3296         while (active.parent !is null)
3297         {
3298             optionalEnd();
3299         }
3300         CompositePrinterParser pp = new CompositePrinterParser(printerParsers, false);
3301         return new DateTimeFormatter(pp, locale, DecimalStyle.STANDARD,
3302                 resolverStyle, null, chrono, null);
3303     }
3304         
3305     //-------------------------------------------------------------------------
3306     /**
3307      * Length comparator.
3308      */
3309 
3310     }