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