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