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