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