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