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