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