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