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