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