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