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