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