001/* 002 * Copyright (c) 2007-2013, Stephen Colebourne & Michael Nascimento Santos 003 * 004 * All rights reserved. 005 * 006 * Redistribution and use in source and binary forms, with or without 007 * modification, are permitted provided that the following conditions are met: 008 * 009 * * Redistributions of source code must retain the above copyright notice, 010 * this list of conditions and the following disclaimer. 011 * 012 * * Redistributions in binary form must reproduce the above copyright notice, 013 * this list of conditions and the following disclaimer in the documentation 014 * and/or other materials provided with the distribution. 015 * 016 * * Neither the name of JSR-310 nor the names of its contributors 017 * may be used to endorse or promote products derived from this software 018 * without specific prior written permission. 019 * 020 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 021 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 022 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 023 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR 024 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 025 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 026 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 027 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 028 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 029 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 030 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 031 */ 032package org.threeten.bp.format; 033 034import static org.threeten.bp.temporal.ChronoField.DAY_OF_MONTH; 035import static org.threeten.bp.temporal.ChronoField.HOUR_OF_DAY; 036import static org.threeten.bp.temporal.ChronoField.INSTANT_SECONDS; 037import static org.threeten.bp.temporal.ChronoField.MINUTE_OF_HOUR; 038import static org.threeten.bp.temporal.ChronoField.MONTH_OF_YEAR; 039import static org.threeten.bp.temporal.ChronoField.NANO_OF_SECOND; 040import static org.threeten.bp.temporal.ChronoField.OFFSET_SECONDS; 041import static org.threeten.bp.temporal.ChronoField.SECOND_OF_MINUTE; 042import static org.threeten.bp.temporal.ChronoField.YEAR; 043 044import java.math.BigDecimal; 045import java.math.BigInteger; 046import java.math.RoundingMode; 047import java.util.AbstractMap.SimpleImmutableEntry; 048import java.util.ArrayList; 049import java.util.Collections; 050import java.util.Comparator; 051import java.util.HashMap; 052import java.util.Iterator; 053import java.util.LinkedHashMap; 054import java.util.List; 055import java.util.Locale; 056import java.util.Map; 057import java.util.Map.Entry; 058import java.util.Objects; 059import java.util.Set; 060 061import org.threeten.bp.DateTimeException; 062import org.threeten.bp.LocalDateTime; 063import org.threeten.bp.ZoneId; 064import org.threeten.bp.ZoneOffset; 065import org.threeten.bp.format.SimpleDateTimeTextProvider.LocaleStore; 066import org.threeten.bp.jdk8.Jdk8Methods; 067import org.threeten.bp.temporal.Chrono; 068import org.threeten.bp.temporal.ChronoField; 069import org.threeten.bp.temporal.ISOChrono; 070import org.threeten.bp.temporal.ISOFields; 071import org.threeten.bp.temporal.TemporalAccessor; 072import org.threeten.bp.temporal.TemporalField; 073import org.threeten.bp.temporal.TemporalQueries; 074import org.threeten.bp.temporal.TemporalQuery; 075import org.threeten.bp.temporal.ValueRange; 076import org.threeten.bp.zone.ZoneRulesProvider; 077 078/** 079 * Builder to create date-time formatters. 080 * <p> 081 * This allows a {@code DateTimeFormatter} to be created. 082 * All date-time formatters are created ultimately using this builder. 083 * <p> 084 * The basic elements of date-time can all be added: 085 * <p><ul> 086 * <li>Value - a numeric value</li> 087 * <li>Fraction - a fractional value including the decimal place. Always use this when 088 * outputting fractions to ensure that the fraction is parsed correctly</li> 089 * <li>Text - the textual equivalent for the value</li> 090 * <li>OffsetId/Offset - the {@linkplain ZoneOffset zone offset}</li> 091 * <li>ZoneId - the {@linkplain ZoneId time-zone} id</li> 092 * <li>ZoneText - the name of the time-zone</li> 093 * <li>Literal - a text literal</li> 094 * <li>Nested and Optional - formats can be nested or made optional</li> 095 * <li>Other - the printer and parser interfaces can be used to add user supplied formatting</li> 096 * </ul><p> 097 * In addition, any of the elements may be decorated by padding, either with spaces or any other character. 098 * <p> 099 * Finally, a shorthand pattern, mostly compatible with {@code java.text.SimpleDateFormat SimpleDateFormat} 100 * can be used, see {@link #appendPattern(String)}. 101 * In practice, this simply parses the pattern and calls other methods on the builder. 102 * 103 * <h3>Specification for implementors</h3> 104 * This class is a mutable builder intended for use from a single thread. 105 */ 106public final class DateTimeFormatterBuilder { 107 108 /** 109 * Query for a time-zone that is region-only. 110 */ 111 private static final TemporalQuery<ZoneId> QUERY_REGION_ONLY = new TemporalQuery<ZoneId>() { 112 public ZoneId queryFrom(TemporalAccessor temporal) { 113 ZoneId zone = temporal.query(TemporalQueries.zoneId()); 114 return (zone != null && zone instanceof ZoneOffset == false ? zone : null); 115 } 116 }; 117 118 /** 119 * The currently active builder, used by the outermost builder. 120 */ 121 private DateTimeFormatterBuilder active = this; 122 /** 123 * The parent builder, null for the outermost builder. 124 */ 125 private final DateTimeFormatterBuilder parent; 126 /** 127 * The list of printers that will be used. 128 */ 129 private final List<DateTimePrinterParser> printerParsers = new ArrayList<>(); 130 /** 131 * Whether this builder produces an optional formatter. 132 */ 133 private final boolean optional; 134 /** 135 * The width to pad the next field to. 136 */ 137 private int padNextWidth; 138 /** 139 * The character to pad the next field with. 140 */ 141 private char padNextChar; 142 /** 143 * The index of the last variable width value parser. 144 */ 145 private int valueParserIndex = -1; 146 147 /** 148 * Constructs a new instance of the builder. 149 */ 150 public DateTimeFormatterBuilder() { 151 super(); 152 parent = null; 153 optional = false; 154 } 155 156 /** 157 * Constructs a new instance of the builder. 158 * 159 * @param parent the parent builder, not null 160 * @param optional whether the formatter is optional, not null 161 */ 162 private DateTimeFormatterBuilder(DateTimeFormatterBuilder parent, boolean optional) { 163 super(); 164 this.parent = parent; 165 this.optional = optional; 166 } 167 168 //----------------------------------------------------------------------- 169 /** 170 * Changes the parse style to be case sensitive for the remainder of the formatter. 171 * <p> 172 * Parsing can be case sensitive or insensitive - by default it is case sensitive. 173 * This method allows the case sensitivity setting of parsing to be changed. 174 * <p> 175 * Calling this method changes the state of the builder such that all 176 * subsequent builder method calls will parse text in case sensitive mode. 177 * See {@link #parseCaseInsensitive} for the opposite setting. 178 * The parse case sensitive/insensitive methods may be called at any point 179 * in the builder, thus the parser can swap between case parsing modes 180 * multiple times during the parse. 181 * <p> 182 * Since the default is case sensitive, this method should only be used after 183 * a previous call to {@code #parseCaseInsensitive}. 184 * 185 * @return this, for chaining, not null 186 */ 187 public DateTimeFormatterBuilder parseCaseSensitive() { 188 appendInternal(SettingsParser.SENSITIVE); 189 return this; 190 } 191 192 /** 193 * Changes the parse style to be case insensitive for the remainder of the formatter. 194 * <p> 195 * Parsing can be case sensitive or insensitive - by default it is case sensitive. 196 * This method allows the case sensitivity setting of parsing to be changed. 197 * <p> 198 * Calling this method changes the state of the builder such that all 199 * subsequent builder method calls will parse text in case sensitive mode. 200 * See {@link #parseCaseSensitive()} for the opposite setting. 201 * The parse case sensitive/insensitive methods may be called at any point 202 * in the builder, thus the parser can swap between case parsing modes 203 * multiple times during the parse. 204 * 205 * @return this, for chaining, not null 206 */ 207 public DateTimeFormatterBuilder parseCaseInsensitive() { 208 appendInternal(SettingsParser.INSENSITIVE); 209 return this; 210 } 211 212 //----------------------------------------------------------------------- 213 /** 214 * Changes the parse style to be strict for the remainder of the formatter. 215 * <p> 216 * Parsing can be strict or lenient - by default its strict. 217 * This controls the degree of flexibility in matching the text and sign styles. 218 * <p> 219 * When used, this method changes the parsing to be strict from this point onwards. 220 * As strict is the default, this is normally only needed after calling {@link #parseLenient()}. 221 * The change will remain in force until the end of the formatter that is eventually 222 * constructed or until {@code parseLenient} is called. 223 * 224 * @return this, for chaining, not null 225 */ 226 public DateTimeFormatterBuilder parseStrict() { 227 appendInternal(SettingsParser.STRICT); 228 return this; 229 } 230 231 /** 232 * Changes the parse style to be lenient for the remainder of the formatter. 233 * Note that case sensitivity is set separately to this method. 234 * <p> 235 * Parsing can be strict or lenient - by default its strict. 236 * This controls the degree of flexibility in matching the text and sign styles. 237 * Applications calling this method should typically also call {@link #parseCaseInsensitive()}. 238 * <p> 239 * When used, this method changes the parsing to be strict from this point onwards. 240 * The change will remain in force until the end of the formatter that is eventually 241 * constructed or until {@code parseStrict} is called. 242 * 243 * @return this, for chaining, not null 244 */ 245 public DateTimeFormatterBuilder parseLenient() { 246 appendInternal(SettingsParser.LENIENT); 247 return this; 248 } 249 250 //----------------------------------------------------------------------- 251 /** 252 * Appends the value of a date-time field to the formatter using a normal 253 * output style. 254 * <p> 255 * The value of the field will be output during a print. 256 * If the value cannot be obtained then an exception will be thrown. 257 * <p> 258 * The value will be printed as per the normal print of an integer value. 259 * Only negative numbers will be signed. No padding will be added. 260 * <p> 261 * The parser for a variable width value such as this normally behaves greedily, 262 * requiring one digit, but accepting as many digits as possible. 263 * This behavior can be affected by 'adjacent value parsing'. 264 * See {@link #appendValue(TemporalField, int)} for full details. 265 * 266 * @param field the field to append, not null 267 * @return this, for chaining, not null 268 */ 269 public DateTimeFormatterBuilder appendValue(TemporalField field) { 270 Objects.requireNonNull(field, "field"); 271 active.valueParserIndex = appendInternal(new NumberPrinterParser(field, 1, 19, SignStyle.NORMAL)); 272 return this; 273 } 274 275 /** 276 * Appends the value of a date-time field to the formatter using a fixed 277 * width, zero-padded approach. 278 * <p> 279 * The value of the field will be output during a print. 280 * If the value cannot be obtained then an exception will be thrown. 281 * <p> 282 * The value will be zero-padded on the left. If the size of the value 283 * means that it cannot be printed within the width then an exception is thrown. 284 * If the value of the field is negative then an exception is thrown during printing. 285 * <p> 286 * This method supports a special technique of parsing known as 'adjacent value parsing'. 287 * This technique solves the problem where a variable length value is followed by one or more 288 * fixed length values. The standard parser is greedy, and thus it would normally 289 * steal the digits that are needed by the fixed width value parsers that follow the 290 * variable width one. 291 * <p> 292 * No action is required to initiate 'adjacent value parsing'. 293 * When a call to {@code appendValue} with a variable width is made, the builder 294 * enters adjacent value parsing setup mode. If the immediately subsequent method 295 * call or calls on the same builder are to this method, then the parser will reserve 296 * space so that the fixed width values can be parsed. 297 * <p> 298 * For example, consider {@code builder.appendValue(YEAR).appendValue(MONTH_OF_YEAR, 2);} 299 * The year is a variable width parse of between 1 and 19 digits. 300 * The month is a fixed width parse of 2 digits. 301 * Because these were appended to the same builder immediately after one another, 302 * the year parser will reserve two digits for the month to parse. 303 * Thus, the text '201106' will correctly parse to a year of 2011 and a month of 6. 304 * Without adjacent value parsing, the year would greedily parse all six digits and leave 305 * nothing for the month. 306 * <p> 307 * Adjacent value parsing applies to each set of fixed width not-negative values in the parser 308 * that immediately follow any kind of variable width value. 309 * Calling any other append method will end the setup of adjacent value parsing. 310 * Thus, in the unlikely event that you need to avoid adjacent value parsing behavior, 311 * simply add the {@code appendValue} to another {@code DateTimeFormatterBuilder} 312 * and add that to this builder. 313 * <p> 314 * If adjacent parsing is active, then parsing must match exactly the specified 315 * number of digits in both strict and lenient modes. 316 * In addition, no positive or negative sign is permitted. 317 * 318 * @param field the field to append, not null 319 * @param width the width of the printed field, from 1 to 19 320 * @return this, for chaining, not null 321 * @throws IllegalArgumentException if the width is invalid 322 */ 323 public DateTimeFormatterBuilder appendValue(TemporalField field, int width) { 324 Objects.requireNonNull(field, "field"); 325 if (width < 1 || width > 19) { 326 throw new IllegalArgumentException("The width must be from 1 to 19 inclusive but was " + width); 327 } 328 NumberPrinterParser pp = new NumberPrinterParser(field, width, width, SignStyle.NOT_NEGATIVE); 329 return appendFixedWidth(width, pp); 330 } 331 332 /** 333 * Appends the value of a date-time field to the formatter providing full 334 * control over printing. 335 * <p> 336 * The value of the field will be output during a print. 337 * If the value cannot be obtained then an exception will be thrown. 338 * <p> 339 * This method provides full control of the numeric formatting, including 340 * zero-padding and the positive/negative sign. 341 * <p> 342 * The parser for a variable width value such as this normally behaves greedily, 343 * accepting as many digits as possible. 344 * This behavior can be affected by 'adjacent value parsing'. 345 * See {@link #appendValue(TemporalField, int)} for full details. 346 * <p> 347 * In strict parsing mode, the minimum number of parsed digits is {@code minWidth}. 348 * In lenient parsing mode, the minimum number of parsed digits is one. 349 * <p> 350 * If this method is invoked with equal minimum and maximum widths and a sign style of 351 * {@code NOT_NEGATIVE} then it delegates to {@code appendValue(TemporalField,int)}. 352 * In this scenario, the printing and parsing behavior described there occur. 353 * 354 * @param field the field to append, not null 355 * @param minWidth the minimum field width of the printed field, from 1 to 19 356 * @param maxWidth the maximum field width of the printed field, from 1 to 19 357 * @param signStyle the positive/negative output style, not null 358 * @return this, for chaining, not null 359 * @throws IllegalArgumentException if the widths are invalid 360 */ 361 public DateTimeFormatterBuilder appendValue( 362 TemporalField field, int minWidth, int maxWidth, SignStyle signStyle) { 363 if (minWidth == maxWidth && signStyle == SignStyle.NOT_NEGATIVE) { 364 return appendValue(field, maxWidth); 365 } 366 Objects.requireNonNull(field, "field"); 367 Objects.requireNonNull(signStyle, "signStyle"); 368 if (minWidth < 1 || minWidth > 19) { 369 throw new IllegalArgumentException("The minimum width must be from 1 to 19 inclusive but was " + minWidth); 370 } 371 if (maxWidth < 1 || maxWidth > 19) { 372 throw new IllegalArgumentException("The maximum width must be from 1 to 19 inclusive but was " + maxWidth); 373 } 374 if (maxWidth < minWidth) { 375 throw new IllegalArgumentException("The maximum width must exceed or equal the minimum width but " + 376 maxWidth + " < " + minWidth); 377 } 378 NumberPrinterParser pp = new NumberPrinterParser(field, minWidth, maxWidth, signStyle); 379 if (minWidth == maxWidth) { 380 appendInternal(pp); 381 } else { 382 active.valueParserIndex = appendInternal(pp); 383 } 384 return this; 385 } 386 387 //----------------------------------------------------------------------- 388 /** 389 * Appends the reduced value of a date-time field to the formatter. 390 * <p> 391 * This is typically used for printing and parsing a two digit year. 392 * The {@code width} is the printed and parsed width. 393 * The {@code baseValue} is used during parsing to determine the valid range. 394 * <p> 395 * For printing, the width is used to determine the number of characters to print. 396 * The rightmost characters are output to match the width, left padding with zero. 397 * <p> 398 * For parsing, exactly the number of characters specified by the width are parsed. 399 * This is incomplete information however, so the base value is used to complete the parse. 400 * The base value is the first valid value in a range of ten to the power of width. 401 * <p> 402 * For example, a base value of {@code 1980} and a width of {@code 2} will have 403 * valid values from {@code 1980} to {@code 2079}. 404 * During parsing, the text {@code "12"} will result in the value {@code 2012} as that 405 * is the value within the range where the last two digits are "12". 406 * <p> 407 * This is a fixed width parser operating using 'adjacent value parsing'. 408 * See {@link #appendValue(TemporalField, int)} for full details. 409 * 410 * @param field the field to append, not null 411 * @param width the width of the printed and parsed field, from 1 to 18 412 * @param baseValue the base value of the range of valid values 413 * @return this, for chaining, not null 414 * @throws IllegalArgumentException if the width or base value is invalid 415 */ 416 public DateTimeFormatterBuilder appendValueReduced( 417 TemporalField field, int width, int baseValue) { 418 Objects.requireNonNull(field, "field"); 419 ReducedPrinterParser pp = new ReducedPrinterParser(field, width, baseValue); 420 appendFixedWidth(width, pp); 421 return this; 422 } 423 424 /** 425 * Appends a fixed width printer-parser. 426 * 427 * @param width the width 428 * @param pp the printer-parser, not null 429 * @return this, for chaining, not null 430 */ 431 private DateTimeFormatterBuilder appendFixedWidth(int width, NumberPrinterParser pp) { 432 if (active.valueParserIndex >= 0) { 433 // adjacent parsing mode, update setting in previous parsers 434 NumberPrinterParser basePP = (NumberPrinterParser) active.printerParsers.get(active.valueParserIndex); 435 basePP = basePP.withSubsequentWidth(width); 436 int activeValueParser = active.valueParserIndex; 437 active.printerParsers.set(active.valueParserIndex, basePP); 438 appendInternal(pp.withFixedWidth()); 439 active.valueParserIndex = activeValueParser; 440 } else { 441 // not adjacent parsing 442 appendInternal(pp); 443 } 444 return this; 445 } 446 447 //----------------------------------------------------------------------- 448 /** 449 * Appends the fractional value of a date-time field to the formatter. 450 * <p> 451 * The fractional value of the field will be output including the 452 * preceding decimal point. The preceding value is not output. 453 * For example, the second-of-minute value of 15 would be output as {@code .25}. 454 * <p> 455 * The width of the printed fraction can be controlled. Setting the 456 * minimum width to zero will cause no output to be generated. 457 * The printed fraction will have the minimum width necessary between 458 * the minimum and maximum widths - trailing zeroes are omitted. 459 * No rounding occurs due to the maximum width - digits are simply dropped. 460 * <p> 461 * When parsing in strict mode, the number of parsed digits must be between 462 * the minimum and maximum width. When parsing in lenient mode, the minimum 463 * width is considered to be zero and the maximum is nine. 464 * <p> 465 * If the value cannot be obtained then an exception will be thrown. 466 * If the value is negative an exception will be thrown. 467 * If the field does not have a fixed set of valid values then an 468 * exception will be thrown. 469 * If the field value in the date-time to be printed is invalid it 470 * cannot be printed and an exception will be thrown. 471 * 472 * @param field the field to append, not null 473 * @param minWidth the minimum width of the field excluding the decimal point, from 0 to 9 474 * @param maxWidth the maximum width of the field excluding the decimal point, from 1 to 9 475 * @param decimalPoint whether to output the localized decimal point symbol 476 * @return this, for chaining, not null 477 * @throws IllegalArgumentException if the field has a variable set of valid values or 478 * either width is invalid 479 */ 480 public DateTimeFormatterBuilder appendFraction( 481 TemporalField field, int minWidth, int maxWidth, boolean decimalPoint) { 482 appendInternal(new FractionPrinterParser(field, minWidth, maxWidth, decimalPoint)); 483 return this; 484 } 485 486 //----------------------------------------------------------------------- 487 /** 488 * Appends the text of a date-time field to the formatter using the full 489 * text style. 490 * <p> 491 * The text of the field will be output during a print. 492 * The value must be within the valid range of the field. 493 * If the value cannot be obtained then an exception will be thrown. 494 * If the field has no textual representation, then the numeric value will be used. 495 * <p> 496 * The value will be printed as per the normal print of an integer value. 497 * Only negative numbers will be signed. No padding will be added. 498 * 499 * @param field the field to append, not null 500 * @return this, for chaining, not null 501 */ 502 public DateTimeFormatterBuilder appendText(TemporalField field) { 503 return appendText(field, TextStyle.FULL); 504 } 505 506 /** 507 * Appends the text of a date-time field to the formatter. 508 * <p> 509 * The text of the field will be output during a print. 510 * The value must be within the valid range of the field. 511 * If the value cannot be obtained then an exception will be thrown. 512 * If the field has no textual representation, then the numeric value will be used. 513 * <p> 514 * The value will be printed as per the normal print of an integer value. 515 * Only negative numbers will be signed. No padding will be added. 516 * 517 * @param field the field to append, not null 518 * @param textStyle the text style to use, not null 519 * @return this, for chaining, not null 520 */ 521 public DateTimeFormatterBuilder appendText(TemporalField field, TextStyle textStyle) { 522 Objects.requireNonNull(field, "field"); 523 Objects.requireNonNull(textStyle, "textStyle"); 524 appendInternal(new TextPrinterParser(field, textStyle, DateTimeTextProvider.getInstance())); 525 return this; 526 } 527 528 /** 529 * Appends the text of a date-time field to the formatter using the specified 530 * map to supply the text. 531 * <p> 532 * The standard text outputting methods use the localized text in the JDK. 533 * This method allows that text to be specified directly. 534 * The supplied map is not validated by the builder to ensure that printing or 535 * parsing is possible, thus an invalid map may throw an error during later use. 536 * <p> 537 * Supplying the map of text provides considerable flexibility in printing and parsing. 538 * For example, a legacy application might require or supply the months of the 539 * year as "JNY", "FBY", "MCH" etc. These do not match the standard set of text 540 * for localized month names. Using this method, a map can be created which 541 * defines the connection between each value and the text: 542 * <pre> 543 * Map<Long, String> map = new HashMap<>(); 544 * map.put(1, "JNY"); 545 * map.put(2, "FBY"); 546 * map.put(3, "MCH"); 547 * ... 548 * builder.appendText(MONTH_OF_YEAR, map); 549 * </pre> 550 * <p> 551 * Other uses might be to output the value with a suffix, such as "1st", "2nd", "3rd", 552 * or as Roman numerals "I", "II", "III", "IV". 553 * <p> 554 * During printing, the value is obtained and checked that it is in the valid range. 555 * If text is not available for the value then it is output as a number. 556 * During parsing, the parser will match against the map of text and numeric values. 557 * 558 * @param field the field to append, not null 559 * @param textLookup the map from the value to the text 560 * @return this, for chaining, not null 561 */ 562 public DateTimeFormatterBuilder appendText(TemporalField field, Map<Long, String> textLookup) { 563 Objects.requireNonNull(field, "field"); 564 Objects.requireNonNull(textLookup, "textLookup"); 565 Map<Long, String> copy = new LinkedHashMap<>(textLookup); 566 Map<TextStyle, Map<Long, String>> map = Collections.singletonMap(TextStyle.FULL, copy); 567 final LocaleStore store = new LocaleStore(map); 568 DateTimeTextProvider provider = new DateTimeTextProvider() { 569 @Override 570 public String getText(TemporalField field, long value, TextStyle style, Locale locale) { 571 return store.getText(value, style); 572 } 573 @Override 574 public Iterator<Entry<String, Long>> getTextIterator(TemporalField field, TextStyle style, Locale locale) { 575 return store.getTextIterator(style); 576 } 577 @Override 578 public Locale[] getAvailableLocales() { 579 throw new UnsupportedOperationException(); 580 } 581 }; 582 appendInternal(new TextPrinterParser(field, TextStyle.FULL, provider)); 583 return this; 584 } 585 586 //----------------------------------------------------------------------- 587 /** 588 * Appends an instant using ISO-8601 to the formatter. 589 * <p> 590 * Instants have a fixed output format. 591 * They are converted to a date-time with a zone-offset of UTC and printed 592 * using the standard ISO-8601 format. 593 * <p> 594 * An alternative to this method is to print/parse the instant as a single 595 * epoch-seconds value. That is achieved using {@code appendValue(INSTANT_SECONDS)}. 596 * 597 * @return this, for chaining, not null 598 */ 599 public DateTimeFormatterBuilder appendInstant() { 600 appendInternal(new InstantPrinterParser()); 601 return this; 602 } 603 604 /** 605 * Appends the zone offset, such as '+01:00', to the formatter. 606 * <p> 607 * This appends an instruction to print/parse the offset ID to the builder. 608 * This is equivalent to calling {@code appendOffset("HH:MM:ss", "Z")}. 609 * 610 * @return this, for chaining, not null 611 */ 612 public DateTimeFormatterBuilder appendOffsetId() { 613 appendInternal(OffsetIdPrinterParser.INSTANCE_ID); 614 return this; 615 } 616 617 /** 618 * Appends the zone offset, such as '+01:00', to the formatter. 619 * <p> 620 * This appends an instruction to print/parse the offset ID to the builder. 621 * <p> 622 * During printing, the offset is obtained using a mechanism equivalent 623 * to querying the temporal with {@link TemporalQueries#offset()}. 624 * It will be printed using the format defined below. 625 * If the offset cannot be obtained then an exception is thrown unless the 626 * section of the formatter is optional. 627 * <p> 628 * During parsing, the offset is parsed using the format defined below. 629 * If the offset cannot be parsed then an exception is thrown unless the 630 * section of the formatter is optional. 631 * <p> 632 * The format of the offset is controlled by a pattern which must be one 633 * of the following: 634 * <p><ul> 635 * <li>{@code +HH} - hour only, ignoring any minute 636 * <li>{@code +HHMM} - hour and minute, no colon 637 * <li>{@code +HH:MM} - hour and minute, with colon 638 * <li>{@code +HHMMss} - hour and minute, with second if non-zero and no colon 639 * <li>{@code +HH:MM:ss} - hour and minute, with second if non-zero and colon 640 * <li>{@code +HHMMSS} - hour, minute and second, no colon 641 * <li>{@code +HH:MM:SS} - hour, minute and second, with colon 642 * </ul><p> 643 * The "no offset" text controls what text is printed when the offset is zero. 644 * Example values would be 'Z', '+00:00', 'UTC' or 'GMT'. 645 * Three formats are accepted for parsing UTC - the "no offset" text, and the 646 * plus and minus versions of zero defined by the pattern. 647 * 648 * @param pattern the pattern to use, not null 649 * @param noOffsetText the text to use when the offset is zero, not null 650 * @return this, for chaining, not null 651 */ 652 public DateTimeFormatterBuilder appendOffset(String pattern, String noOffsetText) { 653 appendInternal(new OffsetIdPrinterParser(noOffsetText, pattern)); 654 return this; 655 } 656 657 //----------------------------------------------------------------------- 658 /** 659 * Appends the time-zone ID, such as 'Europe/Paris' or '+02:00', to the formatter. 660 * <p> 661 * This appends an instruction to print/parse the zone ID to the builder. 662 * The zone ID is obtained in a strict manner suitable for {@code ZonedDateTime}. 663 * By contrast, {@code OffsetDateTime} does not have a zone ID suitable 664 * for use with this method, see {@link #appendZoneOrOffsetId()}. 665 * <p> 666 * During printing, the zone is obtained using a mechanism equivalent 667 * to querying the temporal with {@link TemporalQueries#zoneId()}. 668 * It will be printed using the result of {@link ZoneId#getId()}. 669 * If the zone cannot be obtained then an exception is thrown unless the 670 * section of the formatter is optional. 671 * <p> 672 * During parsing, the zone is parsed and must match a known zone or offset. 673 * If the zone cannot be parsed then an exception is thrown unless the 674 * section of the formatter is optional. 675 * 676 * @return this, for chaining, not null 677 * @see #appendZoneRegionId() 678 */ 679 public DateTimeFormatterBuilder appendZoneId() { 680 appendInternal(new ZoneIdPrinterParser(TemporalQueries.zoneId(), "ZoneId()")); 681 return this; 682 } 683 684 /** 685 * Appends the time-zone region ID, such as 'Europe/Paris', to the formatter, 686 * rejecting the zone ID if it is a {@code ZoneOffset}. 687 * <p> 688 * This appends an instruction to print/parse the zone ID to the builder 689 * only if it is a region-based ID. 690 * <p> 691 * During printing, the zone is obtained using a mechanism equivalent 692 * to querying the temporal with {@link TemporalQueries#zoneId()}. 693 * If the zone is a {@code ZoneOffset} or it cannot be obtained then 694 * an exception is thrown unless the section of the formatter is optional. 695 * If the zone is not an offset, then the zone will be printed using 696 * the zone ID from {@link ZoneId#getId()}. 697 * <p> 698 * During parsing, the zone is parsed and must match a known zone or offset. 699 * If the zone cannot be parsed then an exception is thrown unless the 700 * section of the formatter is optional. 701 * Note that parsing accepts offsets, whereas printing will never produce 702 * one, thus parsing is equivalent to {@code appendZoneId}. 703 * 704 * @return this, for chaining, not null 705 * @see #appendZoneId() 706 */ 707 public DateTimeFormatterBuilder appendZoneRegionId() { 708 appendInternal(new ZoneIdPrinterParser(QUERY_REGION_ONLY, "ZoneRegionId()")); 709 return this; 710 } 711 712 /** 713 * Appends the time-zone ID, such as 'Europe/Paris' or '+02:00', to 714 * the formatter, using the best available zone ID. 715 * <p> 716 * This appends an instruction to print/parse the best available 717 * zone or offset ID to the builder. 718 * The zone ID is obtained in a lenient manner that first attempts to 719 * find a true zone ID, such as that on {@code ZonedDateTime}, and 720 * then attempts to find an offset, such as that on {@code OffsetDateTime}. 721 * <p> 722 * During printing, the zone is obtained using a mechanism equivalent 723 * to querying the temporal with {@link TemporalQueries#zone()}. 724 * It will be printed using the result of {@link ZoneId#getId()}. 725 * If the zone cannot be obtained then an exception is thrown unless the 726 * section of the formatter is optional. 727 * <p> 728 * During parsing, the zone is parsed and must match a known zone or offset. 729 * If the zone cannot be parsed then an exception is thrown unless the 730 * section of the formatter is optional. 731 * <p> 732 * This method is is identical to {@code appendZoneId()} except in the 733 * mechanism used to obtain the zone. 734 * 735 * @return this, for chaining, not null 736 * @see #appendZoneId() 737 */ 738 public DateTimeFormatterBuilder appendZoneOrOffsetId() { 739 appendInternal(new ZoneIdPrinterParser(TemporalQueries.zone(), "ZoneOrOffsetId()")); 740 return this; 741 } 742 743 /** 744 * Appends the time-zone name, such as 'British Summer Time', to the formatter. 745 * <p> 746 * This appends an instruction to print the textual name of the zone to the builder. 747 * <p> 748 * During printing, the zone is obtained using a mechanism equivalent 749 * to querying the temporal with {@link TemporalQueries#zoneId()}. 750 * If the zone is a {@code ZoneOffset} it will be printed using the 751 * result of {@link ZoneOffset#getId()}. 752 * If the zone is not an offset, the textual name will be looked up 753 * for the locale set in the {@link DateTimeFormatter}. 754 * If the temporal object being printed represents an instant, then the text 755 * will be the summer or winter time text as appropriate. 756 * If the lookup for text does not find any suitable reuslt, then the 757 * {@link ZoneId#getId() ID} will be printed instead. 758 * If the zone cannot be obtained then an exception is thrown unless the 759 * section of the formatter is optional. 760 * <p> 761 * Parsing is not currently supported. 762 * 763 * @param textStyle the text style to use, not null 764 * @return this, for chaining, not null 765 */ 766 public DateTimeFormatterBuilder appendZoneText(TextStyle textStyle) { 767 appendInternal(new ZoneTextPrinterParser(textStyle)); 768 return this; 769 } 770 771 //----------------------------------------------------------------------- 772 /** 773 * Appends the chronology ID to the formatter. 774 * <p> 775 * The chronology ID will be output during a print. 776 * If the chronology cannot be obtained then an exception will be thrown. 777 * 778 * @return this, for chaining, not null 779 */ 780 public DateTimeFormatterBuilder appendChronoId() { 781 appendInternal(new ChronoPrinterParser(null)); 782 return this; 783 } 784 785 /** 786 * Appends the chronology name to the formatter. 787 * <p> 788 * The calendar system name will be output during a print. 789 * If the chronology cannot be obtained then an exception will be thrown. 790 * The calendar system name is obtained from the formatting symbols. 791 * 792 * @param textStyle the text style to use, not null 793 * @return this, for chaining, not null 794 */ 795 public DateTimeFormatterBuilder appendChronoText(TextStyle textStyle) { 796 Objects.requireNonNull(textStyle, "textStyle"); 797 appendInternal(new ChronoPrinterParser(textStyle)); 798 return this; 799 } 800 801 //----------------------------------------------------------------------- 802 /** 803 * Appends a localized date-time pattern to the formatter. 804 * <p> 805 * The pattern is resolved lazily using the locale being used during the print/parse 806 * (stored in {@link DateTimeFormatter}. 807 * <p> 808 * The pattern can vary by chronology, although typically it doesn't. 809 * This method uses the standard ISO chronology patterns. 810 * 811 * @param dateStyle the date style to use, null means no date required 812 * @param timeStyle the time style to use, null means no time required 813 * @return this, for chaining, not null 814 */ 815 public DateTimeFormatterBuilder appendLocalized(FormatStyle dateStyle, FormatStyle timeStyle) { 816 return appendLocalized(dateStyle, timeStyle, ISOChrono.INSTANCE); 817 } 818 819 /** 820 * Appends a localized date-time pattern to the formatter. 821 * <p> 822 * The pattern is resolved lazily using the locale being used during the print/parse, 823 * stored in {@link DateTimeFormatter}. 824 * <p> 825 * The pattern can vary by chronology, although typically it doesn't. 826 * This method allows the chronology to be specified. 827 * 828 * @param dateStyle the date style to use, null means no date required 829 * @param timeStyle the time style to use, null means no time required 830 * @param chrono the chronology to use, not null 831 * @return this, for chaining, not null 832 */ 833 public DateTimeFormatterBuilder appendLocalized(FormatStyle dateStyle, FormatStyle timeStyle, Chrono<?> chrono) { 834 Objects.requireNonNull(chrono, "chrono"); 835 if (dateStyle != null || timeStyle != null) { 836 appendInternal(new LocalizedPrinterParser(dateStyle, timeStyle, chrono)); 837 } 838 return this; 839 } 840 841 //----------------------------------------------------------------------- 842 /** 843 * Appends a character literal to the formatter. 844 * <p> 845 * This character will be output during a print. 846 * 847 * @param literal the literal to append, not null 848 * @return this, for chaining, not null 849 */ 850 public DateTimeFormatterBuilder appendLiteral(char literal) { 851 appendInternal(new CharLiteralPrinterParser(literal)); 852 return this; 853 } 854 855 /** 856 * Appends a string literal to the formatter. 857 * <p> 858 * This string will be output during a print. 859 * <p> 860 * If the literal is empty, nothing is added to the formatter. 861 * 862 * @param literal the literal to append, not null 863 * @return this, for chaining, not null 864 */ 865 public DateTimeFormatterBuilder appendLiteral(String literal) { 866 Objects.requireNonNull(literal, "literal"); 867 if (literal.length() > 0) { 868 if (literal.length() == 1) { 869 appendInternal(new CharLiteralPrinterParser(literal.charAt(0))); 870 } else { 871 appendInternal(new StringLiteralPrinterParser(literal)); 872 } 873 } 874 return this; 875 } 876 877 //----------------------------------------------------------------------- 878 /** 879 * Appends all the elements of a formatter to the builder. 880 * <p> 881 * This method has the same effect as appending each of the constituent 882 * parts of the formatter directly to this builder. 883 * 884 * @param formatter the formatter to add, not null 885 * @return this, for chaining, not null 886 */ 887 public DateTimeFormatterBuilder append(DateTimeFormatter formatter) { 888 Objects.requireNonNull(formatter, "formatter"); 889 appendInternal(formatter.toPrinterParser(false)); 890 return this; 891 } 892 893 /** 894 * Appends a formatter to the builder which will optionally print/parse. 895 * <p> 896 * This method has the same effect as appending each of the constituent 897 * parts directly to this builder surrounded by an {@link #optionalStart()} and 898 * {@link #optionalEnd()}. 899 * <p> 900 * The formatter will print if data is available for all the fields contained within it. 901 * The formatter will parse if the string matches, otherwise no error is returned. 902 * 903 * @param formatter the formatter to add, not null 904 * @return this, for chaining, not null 905 */ 906 public DateTimeFormatterBuilder appendOptional(DateTimeFormatter formatter) { 907 Objects.requireNonNull(formatter, "formatter"); 908 appendInternal(formatter.toPrinterParser(true)); 909 return this; 910 } 911 912 //----------------------------------------------------------------------- 913 /** 914 * Appends the elements defined by the specified pattern to the builder. 915 * <p> 916 * All letters 'A' to 'Z' and 'a' to 'z' are reserved as pattern letters. 917 * The characters '{' and '}' are reserved for future use. 918 * The characters '[' and ']' indicate optional patterns. 919 * The following pattern letters are defined: 920 * <pre> 921 * Symbol Meaning Presentation Examples 922 * ------ ------- ------------ ------- 923 * G era number/text 1; 01; AD; Anno Domini 924 * y year year 2004; 04 925 * D day-of-year number 189 926 * M month-of-year number/text 7; 07; Jul; July; J 927 * d day-of-month number 10 928 * 929 * Q quarter-of-year number/text 3; 03; Q3 930 * Y week-based-year year 1996; 96 931 * w week-of-year number 27 932 * W week-of-month number 27 933 * e localized day-of-week number 2; Tue; Tuesday; T 934 * E day-of-week number/text 2; Tue; Tuesday; T 935 * F week-of-month number 3 936 * 937 * a am-pm-of-day text PM 938 * h clock-hour-of-am-pm (1-12) number 12 939 * K hour-of-am-pm (0-11) number 0 940 * k clock-hour-of-am-pm (1-24) number 0 941 * 942 * H hour-of-day (0-23) number 0 943 * m minute-of-hour number 30 944 * s second-of-minute number 55 945 * S fraction-of-second fraction 978 946 * A milli-of-day number 1234 947 * n nano-of-second number 987654321 948 * N nano-of-day number 1234000000 949 * 950 * I time-zone ID zoneId America/Los_Angeles 951 * z time-zone name text Pacific Standard Time; PST 952 * Z zone-offset offset-Z +0000; -0800; -08:00; 953 * X zone-offset 'Z' for zero offset-X Z; -08; -0830; -08:30; -083015; -08:30:15; 954 * 955 * p pad next pad modifier 1 956 * 957 * ' escape for text delimiter 958 * '' single quote literal ' 959 * [ optional section start 960 * ] optional section end 961 * {} reserved for future use 962 * </pre> 963 * <p> 964 * The count of pattern letters determine the format. 965 * <p> 966 * <b>Text</b>: The text style is determined based on the number of pattern letters used. 967 * Less than 4 pattern letters will use the {@link TextStyle#SHORT short form}. 968 * Exactly 4 pattern letters will use the {@link TextStyle#FULL full form}. 969 * Exactly 5 pattern letters will use the {@link TextStyle#NARROW narrow form}. 970 * <p> 971 * <b>Number</b>: If the count of letters is one, then the value is printed using the minimum number 972 * of digits and without padding as per {@link #appendValue(TemporalField)}. Otherwise, the 973 * count of digits is used as the width of the output field as per {@link #appendValue(TemporalField, int)}. 974 * <p> 975 * <b>Number/Text</b>: If the count of pattern letters is 3 or greater, use the Text rules above. 976 * Otherwise use the Number rules above. 977 * <p> 978 * <b>Fraction</b>: Outputs the nano-of-second field as a fraction-of-second. 979 * The nano-of-second value has nine digits, thus the count of pattern letters is from 1 to 9. 980 * If it is less than 9, then the nano-of-second value is truncated, with only the most 981 * significant digits being output. 982 * When parsing in strict mode, the number of parsed digits must match the count of pattern letters. 983 * When parsing in lenient mode, the number of parsed digits must be at least the count of pattern 984 * letters, up to 9 digits. 985 * <p> 986 * <b>Year</b>: The count of letters determines the minimum field width below which padding is used. 987 * If the count of letters is two, then a {@link #appendValueReduced reduced} two digit form is used. 988 * For printing, this outputs the rightmost two digits. For parsing, this will parse using the 989 * base value of 2000, resulting in a year within the range 2000 to 2099 inclusive. 990 * If the count of letters is less than four (but not two), then the sign is only output for negative 991 * years as per {@link SignStyle#NORMAL}. 992 * Otherwise, the sign is output if the pad width is exceeded, as per {@link SignStyle#EXCEEDS_PAD} 993 * <p> 994 * <b>ZoneId</b>: 'I' outputs the zone ID, such as 'Europe/Paris'. 995 * <p> 996 * <b>Offset X</b>: This formats the offset using 'Z' when the offset is zero. 997 * One letter outputs just the hour', such as '+01' 998 * Two letters outputs the hour and minute, without a colon, such as '+0130'. 999 * Three letters outputs the hour and minute, with a colon, such as '+01:30'. 1000 * Four letters outputs the hour and minute and optional second, without a colon, such as '+013015'. 1001 * Five letters outputs the hour and minute and optional second, with a colon, such as '+01:30:15'. 1002 * <p> 1003 * <b>Offset Z</b>: This formats the offset using '+0000' or '+00:00' when the offset is zero. 1004 * One or two letters outputs the hour and minute, without a colon, such as '+0130'. 1005 * Three letters outputs the hour and minute, with a colon, such as '+01:30'. 1006 * <p> 1007 * <b>Zone names</b>: Time zone names ('z') cannot be parsed. 1008 * <p> 1009 * <b>Optional section</b>: The optional section markers work exactly like calling {@link #optionalStart()} 1010 * and {@link #optionalEnd()}. 1011 * <p> 1012 * <b>Pad modifier</b>: Modifies the pattern that immediately follows to be padded with spaces. 1013 * The pad width is determined by the number of pattern letters. 1014 * This is the same as calling {@link #padNext(int)}. 1015 * <p> 1016 * For example, 'ppH' outputs the hour-of-day padded on the left with spaces to a width of 2. 1017 * <p> 1018 * Any unrecognized letter is an error. 1019 * Any non-letter character, other than '[', ']', '{', '}' and the single quote will be output directly. 1020 * Despite this, it is recommended to use single quotes around all characters that you want to 1021 * output directly to ensure that future changes do not break your application. 1022 * <p> 1023 * The pattern string is similar, but not identical, to {@link java.text.SimpleDateFormat SimpleDateFormat}. 1024 * Pattern letters 'E' and 'u' are merged, which changes the meaning of "E" and "EE" to be numeric. 1025 * Pattern letters 'Z' and 'X' are extended. 1026 * Pattern letter 'y' and 'Y' parse years of two digits and more than 4 digits differently. 1027 * Pattern letters 'n', 'A', 'N', 'I' and 'p' are added. 1028 * Number types will reject large numbers. 1029 * The pattern string is also similar, but not identical, to that defined by the 1030 * Unicode Common Locale Data Repository (CLDR). 1031 * 1032 * @param pattern the pattern to add, not null 1033 * @return this, for chaining, not null 1034 * @throws IllegalArgumentException if the pattern is invalid 1035 */ 1036 public DateTimeFormatterBuilder appendPattern(String pattern) { 1037 Objects.requireNonNull(pattern, "pattern"); 1038 parsePattern(pattern); 1039 return this; 1040 } 1041 1042 private void parsePattern(String pattern) { 1043 for (int pos = 0; pos < pattern.length(); pos++) { 1044 char cur = pattern.charAt(pos); 1045 if ((cur >= 'A' && cur <= 'Z') || (cur >= 'a' && cur <= 'z')) { 1046 int start = pos++; 1047 for ( ; pos < pattern.length() && pattern.charAt(pos) == cur; pos++); // short loop 1048 int count = pos - start; 1049 // padding 1050 if (cur == 'p') { 1051 int pad = 0; 1052 if (pos < pattern.length()) { 1053 cur = pattern.charAt(pos); 1054 if ((cur >= 'A' && cur <= 'Z') || (cur >= 'a' && cur <= 'z')) { 1055 pad = count; 1056 start = pos++; 1057 for ( ; pos < pattern.length() && pattern.charAt(pos) == cur; pos++); // short loop 1058 count = pos - start; 1059 } 1060 } 1061 if (pad == 0) { 1062 throw new IllegalArgumentException( 1063 "Pad letter 'p' must be followed by valid pad pattern: " + pattern); 1064 } 1065 padNext(pad); // pad and continue parsing 1066 } 1067 // main rules 1068 TemporalField field = FIELD_MAP.get(cur); 1069 if (field != null) { 1070 parseField(cur, count, field); 1071 } else if (cur == 'z') { 1072 if (count < 4) { 1073 appendZoneText(TextStyle.SHORT); 1074 } else { 1075 appendZoneText(TextStyle.FULL); 1076 } 1077 } else if (cur == 'I') { 1078 appendZoneId(); 1079 } else if (cur == 'Z') { 1080 if (count > 3) { 1081 throw new IllegalArgumentException("Too many pattern letters: " + cur); 1082 } 1083 if (count < 3) { 1084 appendOffset("+HHMM", "+0000"); 1085 } else { 1086 appendOffset("+HH:MM", "+00:00"); 1087 } 1088 } else if (cur == 'X') { 1089 if (count > 5) { 1090 throw new IllegalArgumentException("Too many pattern letters: " + cur); 1091 } 1092 appendOffset(OffsetIdPrinterParser.PATTERNS[count - 1], "Z"); 1093 } else { 1094 throw new IllegalArgumentException("Unknown pattern letter: " + cur); 1095 } 1096 pos--; 1097 1098 } else if (cur == '\'') { 1099 // parse literals 1100 int start = pos++; 1101 for ( ; pos < pattern.length(); pos++) { 1102 if (pattern.charAt(pos) == '\'') { 1103 if (pos + 1 < pattern.length() && pattern.charAt(pos + 1) == '\'') { 1104 pos++; 1105 } else { 1106 break; // end of literal 1107 } 1108 } 1109 } 1110 if (pos >= pattern.length()) { 1111 throw new IllegalArgumentException("Pattern ends with an incomplete string literal: " + pattern); 1112 } 1113 String str = pattern.substring(start + 1, pos); 1114 if (str.length() == 0) { 1115 appendLiteral('\''); 1116 } else { 1117 appendLiteral(str.replace("''", "'")); 1118 } 1119 1120 } else if (cur == '[') { 1121 optionalStart(); 1122 1123 } else if (cur == ']') { 1124 if (active.parent == null) { 1125 throw new IllegalArgumentException("Pattern invalid as it contains ] without previous ["); 1126 } 1127 optionalEnd(); 1128 1129 } else if (cur == '{' || cur == '}') { 1130 throw new IllegalArgumentException("Pattern includes reserved character: '" + cur + "'"); 1131 } else { 1132 appendLiteral(cur); 1133 } 1134 } 1135 } 1136 1137 private void parseField(char cur, int count, TemporalField field) { 1138 switch (cur) { 1139 case 'y': 1140 case 'Y': 1141 if (count == 2) { 1142 appendValueReduced(field, 2, 2000); 1143 } else if (count < 4) { 1144 appendValue(field, count, 19, SignStyle.NORMAL); 1145 } else { 1146 appendValue(field, count, 19, SignStyle.EXCEEDS_PAD); 1147 } 1148 break; 1149 case 'G': 1150 case 'M': 1151 case 'Q': 1152 case 'E': 1153 switch (count) { 1154 case 1: 1155 appendValue(field); 1156 break; 1157 case 2: 1158 appendValue(field, 2); 1159 break; 1160 case 3: 1161 appendText(field, TextStyle.SHORT); 1162 break; 1163 case 4: 1164 appendText(field, TextStyle.FULL); 1165 break; 1166 case 5: 1167 appendText(field, TextStyle.NARROW); 1168 break; 1169 default: 1170 throw new IllegalArgumentException("Too many pattern letters: " + cur); 1171 } 1172 break; 1173 case 'a': 1174 switch (count) { 1175 case 1: 1176 case 2: 1177 case 3: 1178 appendText(field, TextStyle.SHORT); 1179 break; 1180 case 4: 1181 appendText(field, TextStyle.FULL); 1182 break; 1183 case 5: 1184 appendText(field, TextStyle.NARROW); 1185 break; 1186 default: 1187 throw new IllegalArgumentException("Too many pattern letters: " + cur); 1188 } 1189 break; 1190 case 'S': 1191 appendFraction(NANO_OF_SECOND, count, count, false); 1192 break; 1193 default: 1194 if (count == 1) { 1195 appendValue(field); 1196 } else { 1197 appendValue(field, count); 1198 } 1199 break; 1200 } 1201 } 1202 1203 /** Map of letters to fields. */ 1204 private static final Map<Character, TemporalField> FIELD_MAP = new HashMap<>(); 1205 static { 1206 FIELD_MAP.put('G', ChronoField.ERA); // Java, CLDR (different to both for 1/2 chars) 1207 FIELD_MAP.put('y', ChronoField.YEAR); // CLDR 1208 // FIELD_MAP.put('y', ChronoField.YEAR_OF_ERA); // Java, CLDR // TODO redefine from above 1209 // FIELD_MAP.put('u', ChronoField.YEAR); // CLDR // TODO 1210 // FIELD_MAP.put('Y', ISODateTimeField.WEEK_BASED_YEAR); // Java7, CLDR (needs localized week number) // TODO 1211 FIELD_MAP.put('Q', ISOFields.QUARTER_OF_YEAR); // CLDR (removed quarter from 310) 1212 FIELD_MAP.put('M', ChronoField.MONTH_OF_YEAR); // Java, CLDR 1213 // FIELD_MAP.put('w', WeekFields.weekOfYear()); // Java, CLDR (needs localized week number) 1214 // FIELD_MAP.put('W', WeekFields.weekOfMonth()); // Java, CLDR (needs localized week number) 1215 FIELD_MAP.put('D', ChronoField.DAY_OF_YEAR); // Java, CLDR 1216 FIELD_MAP.put('d', ChronoField.DAY_OF_MONTH); // Java, CLDR 1217 FIELD_MAP.put('F', ChronoField.ALIGNED_WEEK_OF_MONTH); // Java, CLDR 1218 FIELD_MAP.put('E', ChronoField.DAY_OF_WEEK); // Java, CLDR (different to both for 1/2 chars) 1219 // FIELD_MAP.put('e', WeekFields.dayOfWeek()); // CLDR (needs localized week number) 1220 FIELD_MAP.put('a', ChronoField.AMPM_OF_DAY); // Java, CLDR 1221 FIELD_MAP.put('H', ChronoField.HOUR_OF_DAY); // Java, CLDR 1222 FIELD_MAP.put('k', ChronoField.CLOCK_HOUR_OF_DAY); // Java, CLDR 1223 FIELD_MAP.put('K', ChronoField.HOUR_OF_AMPM); // Java, CLDR 1224 FIELD_MAP.put('h', ChronoField.CLOCK_HOUR_OF_AMPM); // Java, CLDR 1225 FIELD_MAP.put('m', ChronoField.MINUTE_OF_HOUR); // Java, CLDR 1226 FIELD_MAP.put('s', ChronoField.SECOND_OF_MINUTE); // Java, CLDR 1227 FIELD_MAP.put('S', ChronoField.NANO_OF_SECOND); // CLDR (Java uses milli-of-second number) 1228 FIELD_MAP.put('A', ChronoField.MILLI_OF_DAY); // CLDR 1229 FIELD_MAP.put('n', ChronoField.NANO_OF_SECOND); // 310 1230 FIELD_MAP.put('N', ChronoField.NANO_OF_DAY); // 310 1231 // reserved - z,Z,X,I,p 1232 // Java - X - compatible, but extended to 4 and 5 letters 1233 // Java - u - clashes with CLDR, go with CLDR (year-proleptic) here 1234 // CLDR - U - cycle year name, not supported by 310 yet 1235 // CLDR - l - deprecated 1236 // CLDR - j - not relevant 1237 // CLDR - g - modified-julian-day 1238 // CLDR - z - time-zone names // TODO properly 1239 // CLDR - Z - different approach here // TODO bring 310 in line with CLDR 1240 // CLDR - v,V - extended time-zone names 1241 // CLDR - q/c/L - standalone quarter/day-of-week/month 1242 // 310 - I - time-zone id 1243 // 310 - p - prefix for padding 1244 } 1245 1246 //----------------------------------------------------------------------- 1247 /** 1248 * Causes the next added printer/parser to pad to a fixed width using a space. 1249 * <p> 1250 * This padding will pad to a fixed width using spaces. 1251 * <p> 1252 * An exception will be thrown during printing if the pad width 1253 * is exceeded. 1254 * 1255 * @param padWidth the pad width, 1 or greater 1256 * @return this, for chaining, not null 1257 * @throws IllegalArgumentException if pad width is too small 1258 */ 1259 public DateTimeFormatterBuilder padNext(int padWidth) { 1260 return padNext(padWidth, ' '); 1261 } 1262 1263 /** 1264 * Causes the next added printer/parser to pad to a fixed width. 1265 * <p> 1266 * This padding is intended for padding other than zero-padding. 1267 * Zero-padding should be achieved using the appendValue methods. 1268 * <p> 1269 * An exception will be thrown during printing if the pad width 1270 * is exceeded. 1271 * 1272 * @param padWidth the pad width, 1 or greater 1273 * @param padChar the pad character 1274 * @return this, for chaining, not null 1275 * @throws IllegalArgumentException if pad width is too small 1276 */ 1277 public DateTimeFormatterBuilder padNext(int padWidth, char padChar) { 1278 if (padWidth < 1) { 1279 throw new IllegalArgumentException("The pad width must be at least one but was " + padWidth); 1280 } 1281 active.padNextWidth = padWidth; 1282 active.padNextChar = padChar; 1283 active.valueParserIndex = -1; 1284 return this; 1285 } 1286 1287 //----------------------------------------------------------------------- 1288 /** 1289 * Mark the start of an optional section. 1290 * <p> 1291 * The output of printing can include optional sections, which may be nested. 1292 * An optional section is started by calling this method and ended by calling 1293 * {@link #optionalEnd()} or by ending the build process. 1294 * <p> 1295 * All elements in the optional section are treated as optional. 1296 * During printing, the section is only output if data is available in the 1297 * {@code TemporalAccessor} for all the elements in the section. 1298 * During parsing, the whole section may be missing from the parsed string. 1299 * <p> 1300 * For example, consider a builder setup as 1301 * {@code builder.appendValue(HOUR_OF_DAY,2).optionalStart().appendValue(MINUTE_OF_HOUR,2)}. 1302 * The optional section ends automatically at the end of the builder. 1303 * During printing, the minute will only be output if its value can be obtained from the date-time. 1304 * During parsing, the input will be successfully parsed whether the minute is present or not. 1305 * 1306 * @return this, for chaining, not null 1307 */ 1308 public DateTimeFormatterBuilder optionalStart() { 1309 active.valueParserIndex = -1; 1310 active = new DateTimeFormatterBuilder(active, true); 1311 return this; 1312 } 1313 1314 /** 1315 * Ends an optional section. 1316 * <p> 1317 * The output of printing can include optional sections, which may be nested. 1318 * An optional section is started by calling {@link #optionalStart()} and ended 1319 * using this method (or at the end of the builder). 1320 * <p> 1321 * Calling this method without having previously called {@code optionalStart} 1322 * will throw an exception. 1323 * Calling this method immediately after calling {@code optionalStart} has no effect 1324 * on the formatter other than ending the (empty) optional section. 1325 * <p> 1326 * All elements in the optional section are treated as optional. 1327 * During printing, the section is only output if data is available in the 1328 * {@code TemporalAccessor} for all the elements in the section. 1329 * During parsing, the whole section may be missing from the parsed string. 1330 * <p> 1331 * For example, consider a builder setup as 1332 * {@code builder.appendValue(HOUR_OF_DAY,2).optionalStart().appendValue(MINUTE_OF_HOUR,2).optionalEnd()}. 1333 * During printing, the minute will only be output if its value can be obtained from the date-time. 1334 * During parsing, the input will be successfully parsed whether the minute is present or not. 1335 * 1336 * @return this, for chaining, not null 1337 * @throws IllegalStateException if there was no previous call to {@code optionalStart} 1338 */ 1339 public DateTimeFormatterBuilder optionalEnd() { 1340 if (active.parent == null) { 1341 throw new IllegalStateException("Cannot call optionalEnd() as there was no previous call to optionalStart()"); 1342 } 1343 if (active.printerParsers.size() > 0) { 1344 CompositePrinterParser cpp = new CompositePrinterParser(active.printerParsers, active.optional); 1345 active = active.parent; 1346 appendInternal(cpp); 1347 } else { 1348 active = active.parent; 1349 } 1350 return this; 1351 } 1352 1353 //----------------------------------------------------------------------- 1354 /** 1355 * Appends a printer and/or parser to the internal list handling padding. 1356 * 1357 * @param pp the printer-parser to add, not null 1358 * @return the index into the active parsers list 1359 */ 1360 private int appendInternal(DateTimePrinterParser pp) { 1361 Objects.requireNonNull(pp, "pp"); 1362 if (active.padNextWidth > 0) { 1363 if (pp != null) { 1364 pp = new PadPrinterParserDecorator(pp, active.padNextWidth, active.padNextChar); 1365 } 1366 active.padNextWidth = 0; 1367 active.padNextChar = 0; 1368 } 1369 active.printerParsers.add(pp); 1370 active.valueParserIndex = -1; 1371 return active.printerParsers.size() - 1; 1372 } 1373 1374 //----------------------------------------------------------------------- 1375 /** 1376 * Completes this builder by creating the DateTimeFormatter using the default locale. 1377 * <p> 1378 * This will create a formatter with the default locale. 1379 * Numbers will be printed and parsed using the standard non-localized set of symbols. 1380 * <p> 1381 * Calling this method will end any open optional sections by repeatedly 1382 * calling {@link #optionalEnd()} before creating the formatter. 1383 * <p> 1384 * This builder can still be used after creating the formatter if desired, 1385 * although the state may have been changed by calls to {@code optionalEnd}. 1386 * 1387 * @return the created formatter, not null 1388 */ 1389 public DateTimeFormatter toFormatter() { 1390 return toFormatter(Locale.getDefault()); 1391 } 1392 1393 /** 1394 * Completes this builder by creating the DateTimeFormatter using the specified locale. 1395 * <p> 1396 * This will create a formatter with the specified locale. 1397 * Numbers will be printed and parsed using the standard non-localized set of symbols. 1398 * <p> 1399 * Calling this method will end any open optional sections by repeatedly 1400 * calling {@link #optionalEnd()} before creating the formatter. 1401 * <p> 1402 * This builder can still be used after creating the formatter if desired, 1403 * although the state may have been changed by calls to {@code optionalEnd}. 1404 * 1405 * @param locale the locale to use for formatting, not null 1406 * @return the created formatter, not null 1407 */ 1408 public DateTimeFormatter toFormatter(Locale locale) { 1409 Objects.requireNonNull(locale, "locale"); 1410 while (active.parent != null) { 1411 optionalEnd(); 1412 } 1413 CompositePrinterParser pp = new CompositePrinterParser(printerParsers, false); 1414 return new DateTimeFormatter(pp, locale, DateTimeFormatSymbols.STANDARD, null, null); 1415 } 1416 1417 //----------------------------------------------------------------------- 1418 /** 1419 * Strategy for printing/parsing date-time information. 1420 * <p> 1421 * The printer may print any part, or the whole, of the input date-time object. 1422 * Typically, a complete print is constructed from a number of smaller 1423 * units, each outputting a single field. 1424 * <p> 1425 * The parser may parse any piece of text from the input, storing the result 1426 * in the context. Typically, each individual parser will just parse one 1427 * field, such as the day-of-month, storing the value in the context. 1428 * Once the parse is complete, the caller will then convert the context 1429 * to a {@link DateTimeBuilder} to merge the parsed values to create the 1430 * desired object, such as a {@code LocalDate}. 1431 * <p> 1432 * The parse position will be updated during the parse. Parsing will start at 1433 * the specified index and the return value specifies the new parse position 1434 * for the next parser. If an error occurs, the returned index will be negative 1435 * and will have the error position encoded using the complement operator. 1436 * 1437 * <h3>Specification for implementors</h3> 1438 * This interface must be implemented with care to ensure other classes operate correctly. 1439 * All implementations that can be instantiated must be final, immutable and thread-safe. 1440 * <p> 1441 * The context is not a thread-safe object and a new instance will be created 1442 * for each print that occurs. The context must not be stored in an instance 1443 * variable or shared with any other threads. 1444 */ 1445 interface DateTimePrinterParser { 1446 1447 /** 1448 * Prints the date-time object to the buffer. 1449 * <p> 1450 * The context holds information to use during the print. 1451 * It also contains the date-time information to be printed. 1452 * <p> 1453 * The buffer must not be mutated beyond the content controlled by the implementation. 1454 * 1455 * @param context the context to print using, not null 1456 * @param buf the buffer to append to, not null 1457 * @return false if unable to query the value from the date-time, true otherwise 1458 * @throws DateTimeException if the date-time cannot be printed successfully 1459 */ 1460 boolean print(DateTimePrintContext context, StringBuilder buf); 1461 1462 /** 1463 * Parses text into date-time information. 1464 * <p> 1465 * The context holds information to use during the parse. 1466 * It is also used to store the parsed date-time information. 1467 * 1468 * @param context the context to use and parse into, not null 1469 * @param text the input text to parse, not null 1470 * @param position the position to start parsing at, from 0 to the text length 1471 * @return the new parse position, where negative means an error with the 1472 * error position encoded using the complement ~ operator 1473 * @throws NullPointerException if the context or text is null 1474 * @throws IndexOutOfBoundsException if the position is invalid 1475 */ 1476 int parse(DateTimeParseContext context, CharSequence text, int position); 1477 } 1478 1479 //----------------------------------------------------------------------- 1480 /** 1481 * Composite printer and parser. 1482 */ 1483 static final class CompositePrinterParser implements DateTimePrinterParser { 1484 private final DateTimePrinterParser[] printerParsers; 1485 private final boolean optional; 1486 1487 CompositePrinterParser(List<DateTimePrinterParser> printerParsers, boolean optional) { 1488 this(printerParsers.toArray(new DateTimePrinterParser[printerParsers.size()]), optional); 1489 } 1490 1491 CompositePrinterParser(DateTimePrinterParser[] printerParsers, boolean optional) { 1492 this.printerParsers = printerParsers; 1493 this.optional = optional; 1494 } 1495 1496 /** 1497 * Returns a copy of this printer-parser with the optional flag changed. 1498 * 1499 * @param optional the optional flag to set in the copy 1500 * @return the new printer-parser, not null 1501 */ 1502 public CompositePrinterParser withOptional(boolean optional) { 1503 if (optional == this.optional) { 1504 return this; 1505 } 1506 return new CompositePrinterParser(printerParsers, optional); 1507 } 1508 1509 @Override 1510 public boolean print(DateTimePrintContext context, StringBuilder buf) { 1511 int length = buf.length(); 1512 if (optional) { 1513 context.startOptional(); 1514 } 1515 try { 1516 for (DateTimePrinterParser pp : printerParsers) { 1517 if (pp.print(context, buf) == false) { 1518 buf.setLength(length); // reset buffer 1519 return true; 1520 } 1521 } 1522 } finally { 1523 if (optional) { 1524 context.endOptional(); 1525 } 1526 } 1527 return true; 1528 } 1529 1530 @Override 1531 public int parse(DateTimeParseContext context, CharSequence text, int position) { 1532 if (optional) { 1533 context.startOptional(); 1534 int pos = position; 1535 for (DateTimePrinterParser pp : printerParsers) { 1536 pos = pp.parse(context, text, pos); 1537 if (pos < 0) { 1538 context.endOptional(false); 1539 return position; // return original position 1540 } 1541 } 1542 context.endOptional(true); 1543 return pos; 1544 } else { 1545 for (DateTimePrinterParser pp : printerParsers) { 1546 position = pp.parse(context, text, position); 1547 if (position < 0) { 1548 break; 1549 } 1550 } 1551 return position; 1552 } 1553 } 1554 1555 @Override 1556 public String toString() { 1557 StringBuilder buf = new StringBuilder(); 1558 if (printerParsers != null) { 1559 buf.append(optional ? "[" : "("); 1560 for (DateTimePrinterParser pp : printerParsers) { 1561 buf.append(pp); 1562 } 1563 buf.append(optional ? "]" : ")"); 1564 } 1565 return buf.toString(); 1566 } 1567 } 1568 1569 //----------------------------------------------------------------------- 1570 /** 1571 * Pads the output to a fixed width. 1572 */ 1573 static final class PadPrinterParserDecorator implements DateTimePrinterParser { 1574 private final DateTimePrinterParser printerParser; 1575 private final int padWidth; 1576 private final char padChar; 1577 1578 /** 1579 * Constructor. 1580 * 1581 * @param printerParser the printer, not null 1582 * @param padWidth the width to pad to, 1 or greater 1583 * @param padChar the pad character 1584 */ 1585 PadPrinterParserDecorator(DateTimePrinterParser printerParser, int padWidth, char padChar) { 1586 // input checked by DateTimeFormatterBuilder 1587 this.printerParser = printerParser; 1588 this.padWidth = padWidth; 1589 this.padChar = padChar; 1590 } 1591 1592 @Override 1593 public boolean print(DateTimePrintContext context, StringBuilder buf) { 1594 int preLen = buf.length(); 1595 if (printerParser.print(context, buf) == false) { 1596 return false; 1597 } 1598 int len = buf.length() - preLen; 1599 if (len > padWidth) { 1600 throw new DateTimePrintException( 1601 "Cannot print as output of " + len + " characters exceeds pad width of " + padWidth); 1602 } 1603 for (int i = 0; i < padWidth - len; i++) { 1604 buf.insert(preLen, padChar); 1605 } 1606 return true; 1607 } 1608 1609 @Override 1610 public int parse(DateTimeParseContext context, CharSequence text, int position) { 1611 if (position > text.length()) { 1612 throw new IndexOutOfBoundsException(); 1613 } 1614 int endPos = position + padWidth; 1615 if (endPos > text.length()) { 1616 return ~position; // not enough characters in the string to meet the parse width 1617 } 1618 int pos = position; 1619 while (pos < endPos && text.charAt(pos) == padChar) { 1620 pos++; 1621 } 1622 text = text.subSequence(0, endPos); 1623 int firstError = 0; 1624 while (pos >= position) { 1625 int resultPos = printerParser.parse(context, text, pos); 1626 if (resultPos < 0) { 1627 // parse of decorated field had an error 1628 if (firstError == 0) { 1629 firstError = resultPos; 1630 } 1631 // loop around in case the decorated parser can handle the padChar at the start 1632 pos--; 1633 continue; 1634 } 1635 if (resultPos != endPos) { 1636 return ~position; // parse of decorated field didn't parse to the end 1637 } 1638 return resultPos; 1639 } 1640 // loop runs at least once, so firstError must be set by the time we get here 1641 return firstError; // return error from first parse of decorated field 1642 } 1643 1644 @Override 1645 public String toString() { 1646 return "Pad(" + printerParser + "," + padWidth + (padChar == ' ' ? ")" : ",'" + padChar + "')"); 1647 } 1648 } 1649 1650 //----------------------------------------------------------------------- 1651 /** 1652 * Enumeration to apply simple parse settings. 1653 */ 1654 static enum SettingsParser implements DateTimePrinterParser { 1655 SENSITIVE, 1656 INSENSITIVE, 1657 STRICT, 1658 LENIENT; 1659 1660 @Override 1661 public boolean print(DateTimePrintContext context, StringBuilder buf) { 1662 return true; // nothing to do here 1663 } 1664 1665 @Override 1666 public int parse(DateTimeParseContext context, CharSequence text, int position) { 1667 // using ordinals to avoid javac synthetic inner class 1668 switch (ordinal()) { 1669 case 0: context.setCaseSensitive(true); break; 1670 case 1: context.setCaseSensitive(false); break; 1671 case 2: context.setStrict(true); break; 1672 case 3: context.setStrict(false); break; 1673 } 1674 return position; 1675 } 1676 1677 @Override 1678 public String toString() { 1679 // using ordinals to avoid javac synthetic inner class 1680 switch (ordinal()) { 1681 case 0: return "ParseCaseSensitive(true)"; 1682 case 1: return "ParseCaseSensitive(false)"; 1683 case 2: return "ParseStrict(true)"; 1684 case 3: return "ParseStrict(false)"; 1685 } 1686 throw new IllegalStateException("Unreachable"); 1687 } 1688 } 1689 1690 //----------------------------------------------------------------------- 1691 /** 1692 * Prints or parses a character literal. 1693 */ 1694 static final class CharLiteralPrinterParser implements DateTimePrinterParser { 1695 private final char literal; 1696 1697 CharLiteralPrinterParser(char literal) { 1698 this.literal = literal; 1699 } 1700 1701 @Override 1702 public boolean print(DateTimePrintContext context, StringBuilder buf) { 1703 buf.append(literal); 1704 return true; 1705 } 1706 1707 @Override 1708 public int parse(DateTimeParseContext context, CharSequence text, int position) { 1709 int length = text.length(); 1710 if (position == length) { 1711 return ~position; 1712 } 1713 char ch = text.charAt(position); 1714 if (ch != literal) { 1715 if (context.isCaseSensitive() || 1716 (Character.toUpperCase(ch) != Character.toUpperCase(literal) && 1717 Character.toLowerCase(ch) != Character.toLowerCase(literal))) { 1718 return ~position; 1719 } 1720 } 1721 return position + 1; 1722 } 1723 1724 @Override 1725 public String toString() { 1726 if (literal == '\'') { 1727 return "''"; 1728 } 1729 return "'" + literal + "'"; 1730 } 1731 } 1732 1733 //----------------------------------------------------------------------- 1734 /** 1735 * Prints or parses a string literal. 1736 */ 1737 static final class StringLiteralPrinterParser implements DateTimePrinterParser { 1738 private final String literal; 1739 1740 StringLiteralPrinterParser(String literal) { 1741 this.literal = literal; // validated by caller 1742 } 1743 1744 @Override 1745 public boolean print(DateTimePrintContext context, StringBuilder buf) { 1746 buf.append(literal); 1747 return true; 1748 } 1749 1750 @Override 1751 public int parse(DateTimeParseContext context, CharSequence text, int position) { 1752 int length = text.length(); 1753 if (position > length || position < 0) { 1754 throw new IndexOutOfBoundsException(); 1755 } 1756 if (context.subSequenceEquals(text, position, literal, 0, literal.length()) == false) { 1757 return ~position; 1758 } 1759 return position + literal.length(); 1760 } 1761 1762 @Override 1763 public String toString() { 1764 String converted = literal.replace("'", "''"); 1765 return "'" + converted + "'"; 1766 } 1767 } 1768 1769 //----------------------------------------------------------------------- 1770 /** 1771 * Prints and parses a numeric date-time field with optional padding. 1772 */ 1773 static class NumberPrinterParser implements DateTimePrinterParser { 1774 1775 /** 1776 * Array of 10 to the power of n. 1777 */ 1778 static final int[] EXCEED_POINTS = new int[] { 1779 0, 1780 10, 1781 100, 1782 1000, 1783 10000, 1784 100000, 1785 1000000, 1786 10000000, 1787 100000000, 1788 1000000000, 1789 }; 1790 1791 final TemporalField field; 1792 final int minWidth; 1793 private final int maxWidth; 1794 private final SignStyle signStyle; 1795 private final int subsequentWidth; 1796 1797 /** 1798 * Constructor. 1799 * 1800 * @param field the field to print, not null 1801 * @param minWidth the minimum field width, from 1 to 19 1802 * @param maxWidth the maximum field width, from minWidth to 19 1803 * @param signStyle the positive/negative sign style, not null 1804 */ 1805 NumberPrinterParser(TemporalField field, int minWidth, int maxWidth, SignStyle signStyle) { 1806 // validated by caller 1807 this.field = field; 1808 this.minWidth = minWidth; 1809 this.maxWidth = maxWidth; 1810 this.signStyle = signStyle; 1811 this.subsequentWidth = 0; 1812 } 1813 1814 /** 1815 * Constructor. 1816 * 1817 * @param field the field to print, not null 1818 * @param minWidth the minimum field width, from 1 to 19 1819 * @param maxWidth the maximum field width, from minWidth to 19 1820 * @param signStyle the positive/negative sign style, not null 1821 * @param subsequentWidth the width of subsequent non-negative numbers, 0 or greater, 1822 * -1 if fixed width due to active adjacent parsing 1823 */ 1824 private NumberPrinterParser(TemporalField field, int minWidth, int maxWidth, SignStyle signStyle, int subsequentWidth) { 1825 // validated by caller 1826 this.field = field; 1827 this.minWidth = minWidth; 1828 this.maxWidth = maxWidth; 1829 this.signStyle = signStyle; 1830 this.subsequentWidth = subsequentWidth; 1831 } 1832 1833 /** 1834 * Returns a new instance with fixed width flag set. 1835 * 1836 * @return a new updated printer-parser, not null 1837 */ 1838 NumberPrinterParser withFixedWidth() { 1839 return new NumberPrinterParser(field, minWidth, maxWidth, signStyle, -1); 1840 } 1841 1842 /** 1843 * Returns a new instance with an updated subsequent width. 1844 * 1845 * @param subsequentWidth the width of subsequent non-negative numbers, 0 or greater 1846 * @return a new updated printer-parser, not null 1847 */ 1848 NumberPrinterParser withSubsequentWidth(int subsequentWidth) { 1849 return new NumberPrinterParser(field, minWidth, maxWidth, signStyle, this.subsequentWidth + subsequentWidth); 1850 } 1851 1852 @Override 1853 public boolean print(DateTimePrintContext context, StringBuilder buf) { 1854 Long valueLong = context.getValue(field); 1855 if (valueLong == null) { 1856 return false; 1857 } 1858 long value = getValue(valueLong); 1859 DateTimeFormatSymbols symbols = context.getSymbols(); 1860 String str = (value == Long.MIN_VALUE ? "9223372036854775808" : Long.toString(Math.abs(value))); 1861 if (str.length() > maxWidth) { 1862 throw new DateTimePrintException("Field " + field.getName() + 1863 " cannot be printed as the value " + value + 1864 " exceeds the maximum print width of " + maxWidth); 1865 } 1866 str = symbols.convertNumberToI18N(str); 1867 1868 if (value >= 0) { 1869 switch (signStyle) { 1870 case EXCEEDS_PAD: 1871 if (minWidth < 19 && value >= EXCEED_POINTS[minWidth]) { 1872 buf.append(symbols.getPositiveSign()); 1873 } 1874 break; 1875 case ALWAYS: 1876 buf.append(symbols.getPositiveSign()); 1877 break; 1878 } 1879 } else { 1880 switch (signStyle) { 1881 case NORMAL: 1882 case EXCEEDS_PAD: 1883 case ALWAYS: 1884 buf.append(symbols.getNegativeSign()); 1885 break; 1886 case NOT_NEGATIVE: 1887 throw new DateTimePrintException("Field " + field.getName() + 1888 " cannot be printed as the value " + value + 1889 " cannot be negative according to the SignStyle"); 1890 } 1891 } 1892 for (int i = 0; i < minWidth - str.length(); i++) { 1893 buf.append(symbols.getZeroDigit()); 1894 } 1895 buf.append(str); 1896 return true; 1897 } 1898 1899 /** 1900 * Gets the value to output. 1901 * 1902 * @param value the base value of the field, not null 1903 * @return the value 1904 */ 1905 long getValue(long value) { 1906 return value; 1907 } 1908 1909 boolean isFixedWidth() { 1910 return subsequentWidth == -1; 1911 } 1912 1913 @Override 1914 public int parse(DateTimeParseContext context, CharSequence text, int position) { 1915 int length = text.length(); 1916 if (position == length) { 1917 return ~position; 1918 } 1919 char sign = text.charAt(position); // IOOBE if invalid position 1920 boolean negative = false; 1921 boolean positive = false; 1922 if (sign == context.getSymbols().getPositiveSign()) { 1923 if (signStyle.parse(true, context.isStrict(), minWidth == maxWidth) == false) { 1924 return ~position; 1925 } 1926 positive = true; 1927 position++; 1928 } else if (sign == context.getSymbols().getNegativeSign()) { 1929 if (signStyle.parse(false, context.isStrict(), minWidth == maxWidth) == false) { 1930 return ~position; 1931 } 1932 negative = true; 1933 position++; 1934 } else { 1935 if (signStyle == SignStyle.ALWAYS && context.isStrict()) { 1936 return ~position; 1937 } 1938 } 1939 int effMinWidth = (context.isStrict() || isFixedWidth() ? minWidth : 1); 1940 int minEndPos = position + effMinWidth; 1941 if (minEndPos > length) { 1942 return ~position; 1943 } 1944 int effMaxWidth = maxWidth + Math.max(subsequentWidth, 0); 1945 long total = 0; 1946 BigInteger totalBig = null; 1947 int pos = position; 1948 for (int pass = 0; pass < 2; pass++) { 1949 int maxEndPos = Math.min(pos + effMaxWidth, length); 1950 while (pos < maxEndPos) { 1951 char ch = text.charAt(pos++); 1952 int digit = context.getSymbols().convertToDigit(ch); 1953 if (digit < 0) { 1954 pos--; 1955 if (pos < minEndPos) { 1956 return ~position; // need at least min width digits 1957 } 1958 break; 1959 } 1960 if ((pos - position) > 18) { 1961 if (totalBig == null) { 1962 totalBig = BigInteger.valueOf(total); 1963 } 1964 totalBig = totalBig.multiply(BigInteger.TEN).add(BigInteger.valueOf(digit)); 1965 } else { 1966 total = total * 10 + digit; 1967 } 1968 } 1969 if (subsequentWidth > 0 && pass == 0) { 1970 // re-parse now we know the correct width 1971 int parseLen = pos - position; 1972 effMaxWidth = Math.max(effMinWidth, parseLen - subsequentWidth); 1973 pos = position; 1974 total = 0; 1975 totalBig = null; 1976 } else { 1977 break; 1978 } 1979 } 1980 if (negative) { 1981 if (totalBig != null) { 1982 if (totalBig.equals(BigInteger.ZERO) && context.isStrict()) { 1983 return ~(position - 1); // minus zero not allowed 1984 } 1985 totalBig = totalBig.negate(); 1986 } else { 1987 if (total == 0 && context.isStrict()) { 1988 return ~(position - 1); // minus zero not allowed 1989 } 1990 total = -total; 1991 } 1992 } else if (signStyle == SignStyle.EXCEEDS_PAD && context.isStrict()) { 1993 int parseLen = pos - position; 1994 if (positive) { 1995 if (parseLen <= minWidth) { 1996 return ~(position - 1); // '+' only parsed if minWidth exceeded 1997 } 1998 } else { 1999 if (parseLen > minWidth) { 2000 return ~position; // '+' must be parsed if minWidth exceeded 2001 } 2002 } 2003 } 2004 if (totalBig != null) { 2005 if (totalBig.bitLength() > 63) { 2006 // overflow, parse 1 less digit 2007 totalBig = totalBig.divide(BigInteger.TEN); 2008 pos--; 2009 } 2010 setValue(context, totalBig.longValue()); 2011 } else { 2012 setValue(context, total); 2013 } 2014 return pos; 2015 } 2016 2017 /** 2018 * Stores the value. 2019 * 2020 * @param context the context to store into, not null 2021 * @param value the value 2022 */ 2023 void setValue(DateTimeParseContext context, long value) { 2024 context.setParsedField(field, value); 2025 } 2026 2027 @Override 2028 public String toString() { 2029 if (minWidth == 1 && maxWidth == 19 && signStyle == SignStyle.NORMAL) { 2030 return "Value(" + field.getName() + ")"; 2031 } 2032 if (minWidth == maxWidth && signStyle == SignStyle.NOT_NEGATIVE) { 2033 return "Value(" + field.getName() + "," + minWidth + ")"; 2034 } 2035 return "Value(" + field.getName() + "," + minWidth + "," + maxWidth + "," + signStyle + ")"; 2036 } 2037 } 2038 2039 //----------------------------------------------------------------------- 2040 /** 2041 * Prints and parses a reduced numeric date-time field. 2042 */ 2043 static final class ReducedPrinterParser extends NumberPrinterParser { 2044 private final int baseValue; 2045 private final int range; 2046 2047 /** 2048 * Constructor. 2049 * 2050 * @param field the field to print, validated not null 2051 * @param width the field width, from 1 to 18 2052 * @param baseValue the base value 2053 */ 2054 ReducedPrinterParser(TemporalField field, int width, int baseValue) { 2055 super(field, width, width, SignStyle.NOT_NEGATIVE); 2056 if (width < 1 || width > 18) { 2057 throw new IllegalArgumentException("The width must be from 1 to 18 inclusive but was " + width); 2058 } 2059 if (field.range().isValidValue(baseValue) == false) { 2060 throw new IllegalArgumentException("The base value must be within the range of the field"); 2061 } 2062 this.baseValue = baseValue; 2063 this.range = EXCEED_POINTS[width]; 2064 if ((((long) baseValue) + range) > Integer.MAX_VALUE) { 2065 throw new DateTimeException("Unable to add printer-parser as the range exceeds the capacity of an int"); 2066 } 2067 } 2068 2069 @Override 2070 long getValue(long value) { 2071 return Math.abs(value % range); 2072 } 2073 2074 @Override 2075 void setValue(DateTimeParseContext context, long value) { 2076 int lastPart = baseValue % range; 2077 if (baseValue > 0) { 2078 value = baseValue - lastPart + value; 2079 } else { 2080 value = baseValue - lastPart - value; 2081 } 2082 if (value < baseValue) { 2083 value += range; 2084 } 2085 context.setParsedField(field, value); 2086 } 2087 2088 @Override 2089 NumberPrinterParser withFixedWidth() { 2090 return this; 2091 } 2092 2093 @Override 2094 boolean isFixedWidth() { 2095 return true; 2096 } 2097 2098 @Override 2099 public String toString() { 2100 return "ReducedValue(" + field.getName() + "," + minWidth + "," + baseValue + ")"; 2101 } 2102 } 2103 2104 //----------------------------------------------------------------------- 2105 /** 2106 * Prints and parses a numeric date-time field with optional padding. 2107 */ 2108 static final class FractionPrinterParser implements DateTimePrinterParser { 2109 private final TemporalField field; 2110 private final int minWidth; 2111 private final int maxWidth; 2112 private final boolean decimalPoint; 2113 2114 /** 2115 * Constructor. 2116 * 2117 * @param field the field to output, not null 2118 * @param minWidth the minimum width to output, from 0 to 9 2119 * @param maxWidth the maximum width to output, from 0 to 9 2120 * @param decimalPoint whether to output the localized decimal point symbol 2121 */ 2122 FractionPrinterParser(TemporalField field, int minWidth, int maxWidth, boolean decimalPoint) { 2123 Objects.requireNonNull(field, "field"); 2124 if (field.range().isFixed() == false) { 2125 throw new IllegalArgumentException("Field must have a fixed set of values: " + field.getName()); 2126 } 2127 if (minWidth < 0 || minWidth > 9) { 2128 throw new IllegalArgumentException("Minimum width must be from 0 to 9 inclusive but was " + minWidth); 2129 } 2130 if (maxWidth < 1 || maxWidth > 9) { 2131 throw new IllegalArgumentException("Maximum width must be from 1 to 9 inclusive but was " + maxWidth); 2132 } 2133 if (maxWidth < minWidth) { 2134 throw new IllegalArgumentException("Maximum width must exceed or equal the minimum width but " + 2135 maxWidth + " < " + minWidth); 2136 } 2137 this.field = field; 2138 this.minWidth = minWidth; 2139 this.maxWidth = maxWidth; 2140 this.decimalPoint = decimalPoint; 2141 } 2142 2143 @Override 2144 public boolean print(DateTimePrintContext context, StringBuilder buf) { 2145 Long value = context.getValue(field); 2146 if (value == null) { 2147 return false; 2148 } 2149 DateTimeFormatSymbols symbols = context.getSymbols(); 2150 BigDecimal fraction = convertToFraction(value); 2151 if (fraction.scale() == 0) { // scale is zero if value is zero 2152 if (minWidth > 0) { 2153 if (decimalPoint) { 2154 buf.append(symbols.getDecimalSeparator()); 2155 } 2156 for (int i = 0; i < minWidth; i++) { 2157 buf.append(symbols.getZeroDigit()); 2158 } 2159 } 2160 } else { 2161 int outputScale = Math.min(Math.max(fraction.scale(), minWidth), maxWidth); 2162 fraction = fraction.setScale(outputScale, RoundingMode.FLOOR); 2163 String str = fraction.toPlainString().substring(2); 2164 str = symbols.convertNumberToI18N(str); 2165 if (decimalPoint) { 2166 buf.append(symbols.getDecimalSeparator()); 2167 } 2168 buf.append(str); 2169 } 2170 return true; 2171 } 2172 2173 @Override 2174 public int parse(DateTimeParseContext context, CharSequence text, int position) { 2175 int effectiveMin = (context.isStrict() ? minWidth : 0); 2176 int effectiveMax = (context.isStrict() ? maxWidth : 9); 2177 int length = text.length(); 2178 if (position == length) { 2179 // valid if whole field is optional, invalid if minimum width 2180 return (effectiveMin > 0 ? ~position : position); 2181 } 2182 if (decimalPoint) { 2183 if (text.charAt(position) != context.getSymbols().getDecimalSeparator()) { 2184 // valid if whole field is optional, invalid if minimum width 2185 return (effectiveMin > 0 ? ~position : position); 2186 } 2187 position++; 2188 } 2189 int minEndPos = position + effectiveMin; 2190 if (minEndPos > length) { 2191 return ~position; // need at least min width digits 2192 } 2193 int maxEndPos = Math.min(position + effectiveMax, length); 2194 int total = 0; // can use int because we are only parsing up to 9 digits 2195 int pos = position; 2196 while (pos < maxEndPos) { 2197 char ch = text.charAt(pos++); 2198 int digit = context.getSymbols().convertToDigit(ch); 2199 if (digit < 0) { 2200 if (pos < minEndPos) { 2201 return ~position; // need at least min width digits 2202 } 2203 pos--; 2204 break; 2205 } 2206 total = total * 10 + digit; 2207 } 2208 BigDecimal fraction = new BigDecimal(total).movePointLeft(pos - position); 2209 long value = convertFromFraction(fraction); 2210 context.setParsedField(field, value); 2211 return pos; 2212 } 2213 2214 /** 2215 * Converts a value for this field to a fraction between 0 and 1. 2216 * <p> 2217 * The fractional value is between 0 (inclusive) and 1 (exclusive). 2218 * It can only be returned if the {@link TemporalField#range() value range} is fixed. 2219 * The fraction is obtained by calculation from the field range using 9 decimal 2220 * places and a rounding mode of {@link RoundingMode#FLOOR FLOOR}. 2221 * The calculation is inaccurate if the values do not run continuously from smallest to largest. 2222 * <p> 2223 * For example, the second-of-minute value of 15 would be returned as 0.25, 2224 * assuming the standard definition of 60 seconds in a minute. 2225 * 2226 * @param value the value to convert, must be valid for this rule 2227 * @return the value as a fraction within the range, from 0 to 1, not null 2228 * @throws DateTimeException if the value cannot be converted to a fraction 2229 */ 2230 private BigDecimal convertToFraction(long value) { 2231 ValueRange range = field.range(); 2232 range.checkValidValue(value, field); 2233 BigDecimal minBD = BigDecimal.valueOf(range.getMinimum()); 2234 BigDecimal rangeBD = BigDecimal.valueOf(range.getMaximum()).subtract(minBD).add(BigDecimal.ONE); 2235 BigDecimal valueBD = BigDecimal.valueOf(value).subtract(minBD); 2236 BigDecimal fraction = valueBD.divide(rangeBD, 9, RoundingMode.FLOOR); 2237 // stripTrailingZeros bug 2238 return fraction.compareTo(BigDecimal.ZERO) == 0 ? BigDecimal.ZERO : fraction.stripTrailingZeros(); 2239 } 2240 2241 /** 2242 * Converts a fraction from 0 to 1 for this field to a value. 2243 * <p> 2244 * The fractional value must be between 0 (inclusive) and 1 (exclusive). 2245 * It can only be returned if the {@link TemporalField#range() value range} is fixed. 2246 * The value is obtained by calculation from the field range and a rounding 2247 * mode of {@link RoundingMode#FLOOR FLOOR}. 2248 * The calculation is inaccurate if the values do not run continuously from smallest to largest. 2249 * <p> 2250 * For example, the fractional second-of-minute of 0.25 would be converted to 15, 2251 * assuming the standard definition of 60 seconds in a minute. 2252 * 2253 * @param fraction the fraction to convert, not null 2254 * @return the value of the field, valid for this rule 2255 * @throws DateTimeException if the value cannot be converted 2256 */ 2257 private long convertFromFraction(BigDecimal fraction) { 2258 ValueRange range = field.range(); 2259 BigDecimal minBD = BigDecimal.valueOf(range.getMinimum()); 2260 BigDecimal rangeBD = BigDecimal.valueOf(range.getMaximum()).subtract(minBD).add(BigDecimal.ONE); 2261 BigDecimal valueBD = fraction.multiply(rangeBD).setScale(0, RoundingMode.FLOOR).add(minBD); 2262 return valueBD.longValueExact(); 2263 } 2264 2265 @Override 2266 public String toString() { 2267 String decimal = (decimalPoint ? ",DecimalPoint" : ""); 2268 return "Fraction(" + field.getName() + "," + minWidth + "," + maxWidth + decimal + ")"; 2269 } 2270 } 2271 2272 //----------------------------------------------------------------------- 2273 /** 2274 * Prints or parses field text. 2275 */ 2276 static final class TextPrinterParser implements DateTimePrinterParser { 2277 private final TemporalField field; 2278 private final TextStyle textStyle; 2279 private final DateTimeTextProvider provider; 2280 /** 2281 * The cached number printer parser. 2282 * Immutable and volatile, so no synchronization needed. 2283 */ 2284 private volatile NumberPrinterParser numberPrinterParser; 2285 2286 /** 2287 * Constructor. 2288 * 2289 * @param field the field to output, not null 2290 * @param textStyle the text style, not null 2291 * @param provider the text provider, not null 2292 */ 2293 TextPrinterParser(TemporalField field, TextStyle textStyle, DateTimeTextProvider provider) { 2294 // validated by caller 2295 this.field = field; 2296 this.textStyle = textStyle; 2297 this.provider = provider; 2298 } 2299 2300 @Override 2301 public boolean print(DateTimePrintContext context, StringBuilder buf) { 2302 Long value = context.getValue(field); 2303 if (value == null) { 2304 return false; 2305 } 2306 String text = provider.getText(field, value, textStyle, context.getLocale()); 2307 if (text == null) { 2308 return numberPrinterParser().print(context, buf); 2309 } 2310 buf.append(text); 2311 return true; 2312 } 2313 2314 @Override 2315 public int parse(DateTimeParseContext context, CharSequence parseText, int position) { 2316 int length = parseText.length(); 2317 if (position < 0 || position > length) { 2318 throw new IndexOutOfBoundsException(); 2319 } 2320 TextStyle style = (context.isStrict() ? textStyle : null); 2321 Iterator<Entry<String, Long>> it = provider.getTextIterator(field, style, context.getLocale()); 2322 if (it != null) { 2323 while (it.hasNext()) { 2324 Entry<String, Long> entry = it.next(); 2325 String itText = entry.getKey(); 2326 if (context.subSequenceEquals(itText, 0, parseText, position, itText.length())) { 2327 context.setParsedField(field, entry.getValue()); 2328 return position + itText.length(); 2329 } 2330 } 2331 if (context.isStrict()) { 2332 return ~position; 2333 } 2334 } 2335 return numberPrinterParser().parse(context, parseText, position); 2336 } 2337 2338 /** 2339 * Create and cache a number printer parser. 2340 * @return the number printer parser for this field, not null 2341 */ 2342 private NumberPrinterParser numberPrinterParser() { 2343 if (numberPrinterParser == null) { 2344 numberPrinterParser = new NumberPrinterParser(field, 1, 19, SignStyle.NORMAL); 2345 } 2346 return numberPrinterParser; 2347 } 2348 2349 @Override 2350 public String toString() { 2351 if (textStyle == TextStyle.FULL) { 2352 return "Text(" + field.getName() + ")"; 2353 } 2354 return "Text(" + field.getName() + "," + textStyle + ")"; 2355 } 2356 } 2357 2358 //----------------------------------------------------------------------- 2359 /** 2360 * Prints or parses an ISO-8601 instant. 2361 */ 2362 static final class InstantPrinterParser implements DateTimePrinterParser { 2363 // days in a 400 year cycle = 146097 2364 // days in a 10,000 year cycle = 146097 * 25 2365 // seconds per day = 86400 2366 private static final long SECONDS_PER_10000_YEARS = 146097L * 25L * 86400L; 2367 private static final long SECONDS_0000_TO_1970 = ((146097L * 5L) - (30L * 365L + 7L)) * 86400L; 2368 private static final CompositePrinterParser PARSER = new DateTimeFormatterBuilder() 2369 .parseCaseInsensitive() 2370 .append(DateTimeFormatters.isoLocalDate()).appendLiteral('T') 2371 .append(DateTimeFormatters.isoLocalTime()).appendLiteral('Z') 2372 .toFormatter().toPrinterParser(false); 2373 2374 InstantPrinterParser() { 2375 } 2376 2377 @Override 2378 public boolean print(DateTimePrintContext context, StringBuilder buf) { 2379 // use INSTANT_SECONDS, thus this code is not bound by Instant.MAX 2380 Long inSecs = context.getValue(INSTANT_SECONDS); 2381 Long inNanos = context.getValue(NANO_OF_SECOND); 2382 if (inSecs == null || inNanos == null) { 2383 return false; 2384 } 2385 long inSec = inSecs; 2386 int inNano = NANO_OF_SECOND.checkValidIntValue(inNanos); 2387 if (inSec >= -SECONDS_0000_TO_1970) { 2388 // current era 2389 long zeroSecs = inSec - SECONDS_PER_10000_YEARS + SECONDS_0000_TO_1970; 2390 long hi = Jdk8Methods.floorDiv(zeroSecs, SECONDS_PER_10000_YEARS) + 1; 2391 long lo = Jdk8Methods.floorMod(zeroSecs, SECONDS_PER_10000_YEARS); 2392 LocalDateTime ldt = LocalDateTime.ofEpochSecond(lo - SECONDS_0000_TO_1970, inNano, ZoneOffset.UTC); 2393 if (hi > 0) { 2394 buf.append('+').append(hi); 2395 } 2396 buf.append(ldt).append('Z'); 2397 } else { 2398 // before current era 2399 long zeroSecs = inSec + SECONDS_0000_TO_1970; 2400 long hi = zeroSecs / SECONDS_PER_10000_YEARS; 2401 long lo = zeroSecs % SECONDS_PER_10000_YEARS; 2402 LocalDateTime ldt = LocalDateTime.ofEpochSecond(lo - SECONDS_0000_TO_1970, inNano, ZoneOffset.UTC); 2403 int pos = buf.length(); 2404 buf.append(ldt).append('Z'); 2405 if (hi < 0) { 2406 if (ldt.getYear() == -10_000) { 2407 buf.replace(pos, pos + 2, Long.toString(hi - 1)); 2408 } else if (lo == 0) { 2409 buf.insert(pos, hi); 2410 } else { 2411 buf.insert(pos + 1, Math.abs(hi)); 2412 } 2413 } 2414 } 2415 return true; 2416 } 2417 2418 @Override 2419 public int parse(DateTimeParseContext context, CharSequence text, int position) { 2420 // new context to avoid overwriting fields like year/month/day 2421 DateTimeParseContext newContext = context.copy(); 2422 int pos = PARSER.parse(newContext, text, position); 2423 if (pos < 0) { 2424 return pos; 2425 } 2426 // parser restricts most fields to 2 digits, so definitely int 2427 // correctly parsed nano is also guaranteed to be valid 2428 long yearParsed = newContext.getParsed(YEAR); 2429 int month = newContext.getParsed(MONTH_OF_YEAR).intValue(); 2430 int day = newContext.getParsed(DAY_OF_MONTH).intValue(); 2431 int hour = newContext.getParsed(HOUR_OF_DAY).intValue(); 2432 int min = newContext.getParsed(MINUTE_OF_HOUR).intValue(); 2433 Long secVal = newContext.getParsed(SECOND_OF_MINUTE); 2434 Long nanoVal = newContext.getParsed(NANO_OF_SECOND); 2435 int sec = (secVal != null ? secVal.intValue() : 0); 2436 int nano = (nanoVal != null ? nanoVal.intValue() : 0); 2437 int year = (int) yearParsed % 10_000; 2438 long instantSecs; 2439 try { 2440 LocalDateTime ldt = LocalDateTime.of(year, month, day, hour, min, sec, 0); 2441 instantSecs = ldt.toEpochSecond(ZoneOffset.UTC); 2442 instantSecs += Jdk8Methods.safeMultiply(yearParsed / 10_000L, SECONDS_PER_10000_YEARS); 2443 } catch (RuntimeException ex) { 2444 return ~position; 2445 } 2446 context.setParsedField(INSTANT_SECONDS, instantSecs); 2447 context.setParsedField(NANO_OF_SECOND, nano); 2448 return text.length(); 2449 } 2450 2451 @Override 2452 public String toString() { 2453 return "Instant()"; 2454 } 2455 } 2456 2457 //----------------------------------------------------------------------- 2458 /** 2459 * Prints or parses an offset ID. 2460 */ 2461 static final class OffsetIdPrinterParser implements DateTimePrinterParser { 2462 static final String[] PATTERNS = new String[] { 2463 "+HH", "+HHMM", "+HH:MM", "+HHMMss", "+HH:MM:ss", "+HHMMSS", "+HH:MM:SS", 2464 }; // order used in pattern builder 2465 static final OffsetIdPrinterParser INSTANCE_ID = new OffsetIdPrinterParser("Z", "+HH:MM:ss"); 2466 2467 private final String noOffsetText; 2468 private final int type; 2469 2470 /** 2471 * Constructor. 2472 * 2473 * @param noOffsetText the text to use for UTC, not null 2474 * @param pattern the pattern 2475 */ 2476 OffsetIdPrinterParser(String noOffsetText, String pattern) { 2477 Objects.requireNonNull(noOffsetText, "noOffsetText"); 2478 Objects.requireNonNull(pattern, "pattern"); 2479 this.noOffsetText = noOffsetText; 2480 this.type = checkPattern(pattern); 2481 } 2482 2483 private int checkPattern(String pattern) { 2484 for (int i = 0; i < PATTERNS.length; i++) { 2485 if (PATTERNS[i].equals(pattern)) { 2486 return i; 2487 } 2488 } 2489 throw new IllegalArgumentException("Invalid zone offset pattern: " + pattern); 2490 } 2491 2492 @Override 2493 public boolean print(DateTimePrintContext context, StringBuilder buf) { 2494 Long offsetSecs = context.getValue(OFFSET_SECONDS); 2495 if (offsetSecs == null) { 2496 return false; 2497 } 2498 int totalSecs = Jdk8Methods.safeToInt(offsetSecs); 2499 if (totalSecs == 0) { 2500 buf.append(noOffsetText); 2501 } else { 2502 int absHours = Math.abs((totalSecs / 3600) % 100); // anything larger than 99 silently dropped 2503 int absMinutes = Math.abs((totalSecs / 60) % 60); 2504 int absSeconds = Math.abs(totalSecs % 60); 2505 buf.append(totalSecs < 0 ? "-" : "+") 2506 .append((char) (absHours / 10 + '0')).append((char) (absHours % 10 + '0')); 2507 if (type >= 1) { 2508 buf.append((type % 2) == 0 ? ":" : "") 2509 .append((char) (absMinutes / 10 + '0')).append((char) (absMinutes % 10 + '0')); 2510 if (type >= 5 || (type >= 3 && absSeconds > 0)) { 2511 buf.append((type % 2) == 0 ? ":" : "") 2512 .append((char) (absSeconds / 10 + '0')).append((char) (absSeconds % 10 + '0')); 2513 } 2514 } 2515 } 2516 return true; 2517 } 2518 2519 @Override 2520 public int parse(DateTimeParseContext context, CharSequence text, int position) { 2521 int length = text.length(); 2522 int noOffsetLen = noOffsetText.length(); 2523 if (noOffsetLen == 0) { 2524 if (position == length) { 2525 context.setParsedField(OFFSET_SECONDS, 0); 2526 return position; 2527 } 2528 } else { 2529 if (position == length) { 2530 return ~position; 2531 } 2532 if (context.subSequenceEquals(text, position, noOffsetText, 0, noOffsetLen)) { 2533 context.setParsedField(OFFSET_SECONDS, 0); 2534 return position + noOffsetLen; 2535 } 2536 } 2537 2538 // parse normal plus/minus offset 2539 char sign = text.charAt(position); // IOOBE if invalid position 2540 if (sign == '+' || sign == '-') { 2541 // starts 2542 int negative = (sign == '-' ? -1 : 1); 2543 int[] array = new int[4]; 2544 array[0] = position + 1; 2545 if (parseNumber(array, 1, text, true) || 2546 parseNumber(array, 2, text, type > 0) || 2547 parseNumber(array, 3, text, false)) { 2548 return ~position; 2549 } 2550 long offsetSecs = negative * (array[1] * 3600L + array[2] * 60L + array[3]); 2551 context.setParsedField(OFFSET_SECONDS, offsetSecs); 2552 return array[0]; 2553 } else { 2554 // handle special case of empty no offset text 2555 if (noOffsetLen == 0) { 2556 context.setParsedField(OFFSET_SECONDS, 0); 2557 return position + noOffsetLen; 2558 } 2559 return ~position; 2560 } 2561 } 2562 2563 /** 2564 * Parse a two digit zero-prefixed number. 2565 * 2566 * @param array the array of parsed data, 0=pos,1=hours,2=mins,3=secs, not null 2567 * @param arrayIndex the index to parse the value into 2568 * @param parseText the offset ID, not null 2569 * @param required whether this number is required 2570 * @return true if an error occurred 2571 */ 2572 private boolean parseNumber(int[] array, int arrayIndex, CharSequence parseText, boolean required) { 2573 if ((type + 3) / 2 < arrayIndex) { 2574 return false; // ignore seconds/minutes 2575 } 2576 int pos = array[0]; 2577 if ((type % 2) == 0 && arrayIndex > 1) { 2578 if (pos + 1 > parseText.length() || parseText.charAt(pos) != ':') { 2579 return required; 2580 } 2581 pos++; 2582 } 2583 if (pos + 2 > parseText.length()) { 2584 return required; 2585 } 2586 char ch1 = parseText.charAt(pos++); 2587 char ch2 = parseText.charAt(pos++); 2588 if (ch1 < '0' || ch1 > '9' || ch2 < '0' || ch2 > '9') { 2589 return required; 2590 } 2591 int value = (ch1 - 48) * 10 + (ch2 - 48); 2592 if (value < 0 || value > 59) { 2593 return required; 2594 } 2595 array[arrayIndex] = value; 2596 array[0] = pos; 2597 return false; 2598 } 2599 2600 @Override 2601 public String toString() { 2602 String converted = noOffsetText.replace("'", "''"); 2603 return "Offset('" + converted + "'," + PATTERNS[type] + ")"; 2604 } 2605 } 2606 2607 //----------------------------------------------------------------------- 2608 /** 2609 * Prints or parses a zone ID. 2610 */ 2611 static final class ZoneTextPrinterParser implements DateTimePrinterParser { 2612 // TODO: remove this as it is incomplete 2613 /** The text style to output. */ 2614 private final TextStyle textStyle; 2615 2616 ZoneTextPrinterParser(TextStyle textStyle) { 2617 this.textStyle = Objects.requireNonNull(textStyle, "textStyle"); 2618 } 2619 2620 //----------------------------------------------------------------------- 2621 @Override 2622 public boolean print(DateTimePrintContext context, StringBuilder buf) { 2623 ZoneId zone = context.getValue(TemporalQueries.zoneId()); 2624 if (zone == null) { 2625 return false; 2626 } 2627 // TODO: fix getText(textStyle, context.getLocale()) 2628 buf.append(zone.getId()); // TODO: Use symbols 2629 return true; 2630 } 2631 2632 @Override 2633 public int parse(DateTimeParseContext context, CharSequence text, int position) { 2634 throw new UnsupportedOperationException(); 2635 } 2636 2637 @Override 2638 public String toString() { 2639 return "ZoneText(" + textStyle + ")"; 2640 } 2641 } 2642 2643 //----------------------------------------------------------------------- 2644 /** 2645 * Prints or parses a zone ID. 2646 */ 2647 static final class ZoneIdPrinterParser implements DateTimePrinterParser { 2648 private final TemporalQuery<ZoneId> query; 2649 private final String description; 2650 2651 ZoneIdPrinterParser(TemporalQuery<ZoneId> query, String description) { 2652 this.query = query; 2653 this.description = description; 2654 } 2655 2656 //----------------------------------------------------------------------- 2657 @Override 2658 public boolean print(DateTimePrintContext context, StringBuilder buf) { 2659 ZoneId zone = context.getValue(query); 2660 if (zone == null) { 2661 return false; 2662 } 2663 buf.append(zone.getId()); 2664 return true; 2665 } 2666 2667 //----------------------------------------------------------------------- 2668 /** 2669 * The cached tree to speed up parsing. 2670 */ 2671 private static volatile Entry<Integer, SubstringTree> cachedSubstringTree; 2672 2673 /** 2674 * This implementation looks for the longest matching string. 2675 * For example, parsing Etc/GMT-2 will return Etc/GMC-2 rather than just 2676 * Etc/GMC although both are valid. 2677 * <p> 2678 * This implementation uses a tree to search for valid time-zone names in 2679 * the parseText. The top level node of the tree has a length equal to the 2680 * length of the shortest time-zone as well as the beginning characters of 2681 * all other time-zones. 2682 */ 2683 @Override 2684 public int parse(DateTimeParseContext context, CharSequence text, int position) { 2685 // TODO case insensitive? 2686 int length = text.length(); 2687 if (position > length) { 2688 throw new IndexOutOfBoundsException(); 2689 } 2690 if (position == length) { 2691 return ~position; 2692 } 2693 2694 // handle fixed time-zone IDs 2695 char nextChar = text.charAt(position); 2696 if (nextChar == '+' || nextChar == '-') { 2697 DateTimeParseContext newContext = context.copy(); 2698 int endPos = OffsetIdPrinterParser.INSTANCE_ID.parse(newContext, text, position); 2699 if (endPos < 0) { 2700 return endPos; 2701 } 2702 int offset = (int) newContext.getParsed(OFFSET_SECONDS).longValue(); 2703 ZoneId zone = ZoneOffset.ofTotalSeconds(offset); 2704 context.setParsed(zone); 2705 return endPos; 2706 } else if (length >= position + 2) { 2707 char nextNextChar = text.charAt(position + 1); 2708 if (nextChar == 'U' && nextNextChar == 'T') { 2709 if (length >= position + 3 && text.charAt(position + 2) == 'C') { 2710 return parsePrefixedOffset(context, text, position + 3); 2711 } 2712 return parsePrefixedOffset(context, text, position + 2); 2713 } else if (nextChar == 'G' && length >= position + 3 && 2714 nextNextChar == 'M' && text.charAt(position + 2) == 'T') { 2715 return parsePrefixedOffset(context, text, position + 3); 2716 } 2717 } 2718 2719 // prepare parse tree 2720 Set<String> regionIds = ZoneRulesProvider.getAvailableZoneIds(); 2721 final int regionIdsSize = regionIds.size(); 2722 Entry<Integer, SubstringTree> cached = cachedSubstringTree; 2723 if (cached == null || cached.getKey() != regionIdsSize) { 2724 synchronized (this) { 2725 cached = cachedSubstringTree; 2726 if (cached == null || cached.getKey() != regionIdsSize) { 2727 cachedSubstringTree = cached = new SimpleImmutableEntry<>(regionIdsSize, prepareParser(regionIds)); 2728 } 2729 } 2730 } 2731 SubstringTree tree = cached.getValue(); 2732 2733 // parse 2734 String parsedZoneId = null; 2735 while (tree != null) { 2736 int nodeLength = tree.length; 2737 if (position + nodeLength > length) { 2738 break; 2739 } 2740 parsedZoneId = text.subSequence(position, position + nodeLength).toString(); 2741 tree = tree.get(parsedZoneId); 2742 } 2743 2744 if (parsedZoneId == null || regionIds.contains(parsedZoneId) == false) { 2745 if (nextChar == 'Z') { 2746 context.setParsed(ZoneOffset.UTC); 2747 return position + 1; 2748 } 2749 return ~position; 2750 } 2751 context.setParsed(ZoneId.of(parsedZoneId)); 2752 return position + parsedZoneId.length(); 2753 } 2754 2755 private int parsePrefixedOffset(DateTimeParseContext context, CharSequence text, int position) { 2756 DateTimeParseContext newContext = context.copy(); 2757 int endPos = OffsetIdPrinterParser.INSTANCE_ID.parse(newContext, text, position); 2758 if (endPos < 0) { 2759 context.setParsed(ZoneOffset.UTC); 2760 return position; 2761 } 2762 int offset = (int) newContext.getParsed(OFFSET_SECONDS).longValue(); 2763 ZoneId zone = ZoneOffset.ofTotalSeconds(offset); 2764 context.setParsed(zone); 2765 return endPos; 2766 } 2767 2768 //----------------------------------------------------------------------- 2769 /** 2770 * Model a tree of substrings to make the parsing easier. Due to the nature 2771 * of time-zone names, it can be faster to parse based in unique substrings 2772 * rather than just a character by character match. 2773 * <p> 2774 * For example, to parse America/Denver we can look at the first two 2775 * character "Am". We then notice that the shortest time-zone that starts 2776 * with Am is America/Nome which is 12 characters long. Checking the first 2777 * 12 characters of America/Denver gives America/Denv which is a substring 2778 * of only 1 time-zone: America/Denver. Thus, with just 3 comparisons that 2779 * match can be found. 2780 * <p> 2781 * This structure maps substrings to substrings of a longer length. Each 2782 * node of the tree contains a length and a map of valid substrings to 2783 * sub-nodes. The parser gets the length from the root node. It then 2784 * extracts a substring of that length from the parseText. If the map 2785 * contains the substring, it is set as the possible time-zone and the 2786 * sub-node for that substring is retrieved. The process continues until the 2787 * substring is no longer found, at which point the matched text is checked 2788 * against the real time-zones. 2789 */ 2790 private static final class SubstringTree { 2791 /** 2792 * The length of the substring this node of the tree contains. 2793 * Subtrees will have a longer length. 2794 */ 2795 final int length; 2796 /** 2797 * Map of a substring to a set of substrings that contain the key. 2798 */ 2799 private final Map<CharSequence, SubstringTree> substringMap = new HashMap<>(); 2800 2801 /** 2802 * Constructor. 2803 * 2804 * @param length the length of this tree 2805 */ 2806 private SubstringTree(int length) { 2807 this.length = length; 2808 } 2809 2810 private SubstringTree get(CharSequence substring2) { 2811 return substringMap.get(substring2); 2812 2813 } 2814 2815 /** 2816 * Values must be added from shortest to longest. 2817 * 2818 * @param newSubstring the substring to add, not null 2819 */ 2820 private void add(String newSubstring) { 2821 int idLen = newSubstring.length(); 2822 if (idLen == length) { 2823 substringMap.put(newSubstring, null); 2824 } else if (idLen > length) { 2825 String substring = newSubstring.substring(0, length); 2826 SubstringTree parserTree = substringMap.get(substring); 2827 if (parserTree == null) { 2828 parserTree = new SubstringTree(idLen); 2829 substringMap.put(substring, parserTree); 2830 } 2831 parserTree.add(newSubstring); 2832 } 2833 } 2834 } 2835 2836 /** 2837 * Builds an optimized parsing tree. 2838 * 2839 * @param availableIDs the available IDs, not null, not empty 2840 * @return the tree, not null 2841 */ 2842 private static SubstringTree prepareParser(Set<String> availableIDs) { 2843 // sort by length 2844 List<String> ids = new ArrayList<>(availableIDs); 2845 Collections.sort(ids, LENGTH_SORT); 2846 2847 // build the tree 2848 SubstringTree tree = new SubstringTree(ids.get(0).length()); 2849 for (String id : ids) { 2850 tree.add(id); 2851 } 2852 return tree; 2853 } 2854 2855 //----------------------------------------------------------------------- 2856 @Override 2857 public String toString() { 2858 return description; 2859 } 2860 } 2861 2862 //----------------------------------------------------------------------- 2863 /** 2864 * Prints or parses a chronology. 2865 */ 2866 static final class ChronoPrinterParser implements DateTimePrinterParser { 2867 /** The text style to output, null means the ID. */ 2868 private final TextStyle textStyle; 2869 2870 ChronoPrinterParser(TextStyle textStyle) { 2871 // validated by caller 2872 this.textStyle = textStyle; 2873 } 2874 2875 @Override 2876 public boolean print(DateTimePrintContext context, StringBuilder buf) { 2877 Chrono<?> chrono = context.getValue(TemporalQueries.chrono()); 2878 if (chrono == null) { 2879 return false; 2880 } 2881 if (textStyle == null) { 2882 buf.append(chrono.getId()); 2883 } else { 2884 buf.append(chrono.getId()); // TODO: Use symbols 2885 } 2886 return true; 2887 } 2888 2889 @Override 2890 public int parse(DateTimeParseContext context, CharSequence text, int position) { 2891 return ~position; // TODO, including case insensitive 2892 } 2893 } 2894 2895 //----------------------------------------------------------------------- 2896 /** 2897 * Prints or parses a localized pattern. 2898 */ 2899 static final class LocalizedPrinterParser implements DateTimePrinterParser { 2900 private final FormatStyle dateStyle; 2901 private final FormatStyle timeStyle; 2902 private final Chrono<?> chrono; 2903 2904 /** 2905 * Constructor. 2906 * 2907 * @param dateStyle the date style to use, may be null 2908 * @param timeStyle the time style to use, may be null 2909 * @param chrono the chronology to use, not null 2910 */ 2911 LocalizedPrinterParser(FormatStyle dateStyle, FormatStyle timeStyle, Chrono<?> chrono) { 2912 // validated by caller 2913 this.dateStyle = dateStyle; 2914 this.timeStyle = timeStyle; 2915 this.chrono = chrono; 2916 } 2917 2918 @Override 2919 public boolean print(DateTimePrintContext context, StringBuilder buf) { 2920 return formatter(context.getLocale()).toPrinterParser(false).print(context, buf); 2921 } 2922 2923 @Override 2924 public int parse(DateTimeParseContext context, CharSequence text, int position) { 2925 return formatter(context.getLocale()).toPrinterParser(false).parse(context, text, position); 2926 } 2927 2928 /** 2929 * Gets the formatter to use. 2930 * 2931 * @param locale the locale to use, not null 2932 * @return the formatter, not null 2933 * @throws IllegalArgumentException if the formatter cannot be found 2934 */ 2935 private DateTimeFormatter formatter(Locale locale) { 2936 return DateTimeFormatStyleProvider.getInstance() 2937 .getFormatter(dateStyle, timeStyle, chrono, locale); 2938 } 2939 2940 @Override 2941 public String toString() { 2942 return "Localized(" + (dateStyle != null ? dateStyle : "") + "," + 2943 (timeStyle != null ? timeStyle : "") + "," + chrono.getId() + ")"; 2944 } 2945 } 2946 2947 //------------------------------------------------------------------------- 2948 /** 2949 * Length comparator. 2950 */ 2951 static final Comparator<String> LENGTH_SORT = new Comparator<String>() { 2952 @Override 2953 public int compare(String str1, String str2) { 2954 return str1.length() == str2.length() ? str1.compareTo(str2) : str1.length() - str2.length(); 2955 } 2956 }; 2957 2958}