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