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