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