001 /* 002 * Copyright 2001-2011 Stephen Colebourne 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016 package org.joda.time.format; 017 018 import java.io.IOException; 019 import java.io.Writer; 020 import java.util.ArrayList; 021 import java.util.HashMap; 022 import java.util.HashSet; 023 import java.util.List; 024 import java.util.Locale; 025 import java.util.Map; 026 import java.util.Set; 027 028 import org.joda.time.Chronology; 029 import org.joda.time.DateTimeConstants; 030 import org.joda.time.DateTimeField; 031 import org.joda.time.DateTimeFieldType; 032 import org.joda.time.DateTimeUtils; 033 import org.joda.time.DateTimeZone; 034 import org.joda.time.MutableDateTime; 035 import org.joda.time.ReadablePartial; 036 import org.joda.time.MutableDateTime.Property; 037 import org.joda.time.field.MillisDurationField; 038 import org.joda.time.field.PreciseDateTimeField; 039 040 /** 041 * Factory that creates complex instances of DateTimeFormatter via method calls. 042 * <p> 043 * Datetime formatting is performed by the {@link DateTimeFormatter} class. 044 * Three classes provide factory methods to create formatters, and this is one. 045 * The others are {@link DateTimeFormat} and {@link ISODateTimeFormat}. 046 * <p> 047 * DateTimeFormatterBuilder is used for constructing formatters which are then 048 * used to print or parse. The formatters are built by appending specific fields 049 * or other formatters to an instance of this builder. 050 * <p> 051 * For example, a formatter that prints month and year, like "January 1970", 052 * can be constructed as follows: 053 * <p> 054 * <pre> 055 * DateTimeFormatter monthAndYear = new DateTimeFormatterBuilder() 056 * .appendMonthOfYearText() 057 * .appendLiteral(' ') 058 * .appendYear(4, 4) 059 * .toFormatter(); 060 * </pre> 061 * <p> 062 * DateTimeFormatterBuilder itself is mutable and not thread-safe, but the 063 * formatters that it builds are thread-safe and immutable. 064 * 065 * @author Brian S O'Neill 066 * @author Stephen Colebourne 067 * @author Fredrik Borgh 068 * @since 1.0 069 * @see DateTimeFormat 070 * @see ISODateTimeFormat 071 */ 072 public class DateTimeFormatterBuilder { 073 074 /** Array of printers and parsers (alternating). */ 075 private ArrayList<Object> iElementPairs; 076 /** Cache of the last returned formatter. */ 077 private Object iFormatter; 078 079 //----------------------------------------------------------------------- 080 /** 081 * Creates a DateTimeFormatterBuilder. 082 */ 083 public DateTimeFormatterBuilder() { 084 super(); 085 iElementPairs = new ArrayList<Object>(); 086 } 087 088 //----------------------------------------------------------------------- 089 /** 090 * Constructs a DateTimeFormatter using all the appended elements. 091 * <p> 092 * This is the main method used by applications at the end of the build 093 * process to create a usable formatter. 094 * <p> 095 * Subsequent changes to this builder do not affect the returned formatter. 096 * <p> 097 * The returned formatter may not support both printing and parsing. 098 * The methods {@link DateTimeFormatter#isPrinter()} and 099 * {@link DateTimeFormatter#isParser()} will help you determine the state 100 * of the formatter. 101 * 102 * @throws UnsupportedOperationException if neither printing nor parsing is supported 103 */ 104 public DateTimeFormatter toFormatter() { 105 Object f = getFormatter(); 106 DateTimePrinter printer = null; 107 if (isPrinter(f)) { 108 printer = (DateTimePrinter) f; 109 } 110 DateTimeParser parser = null; 111 if (isParser(f)) { 112 parser = (DateTimeParser) f; 113 } 114 if (printer != null || parser != null) { 115 return new DateTimeFormatter(printer, parser); 116 } 117 throw new UnsupportedOperationException("Both printing and parsing not supported"); 118 } 119 120 /** 121 * Internal method to create a DateTimePrinter instance using all the 122 * appended elements. 123 * <p> 124 * Most applications will not use this method. 125 * If you want a printer in an application, call {@link #toFormatter()} 126 * and just use the printing API. 127 * <p> 128 * Subsequent changes to this builder do not affect the returned printer. 129 * 130 * @throws UnsupportedOperationException if printing is not supported 131 */ 132 public DateTimePrinter toPrinter() { 133 Object f = getFormatter(); 134 if (isPrinter(f)) { 135 return (DateTimePrinter) f; 136 } 137 throw new UnsupportedOperationException("Printing is not supported"); 138 } 139 140 /** 141 * Internal method to create a DateTimeParser instance using all the 142 * appended elements. 143 * <p> 144 * Most applications will not use this method. 145 * If you want a parser in an application, call {@link #toFormatter()} 146 * and just use the parsing API. 147 * <p> 148 * Subsequent changes to this builder do not affect the returned parser. 149 * 150 * @throws UnsupportedOperationException if parsing is not supported 151 */ 152 public DateTimeParser toParser() { 153 Object f = getFormatter(); 154 if (isParser(f)) { 155 return (DateTimeParser) f; 156 } 157 throw new UnsupportedOperationException("Parsing is not supported"); 158 } 159 160 //----------------------------------------------------------------------- 161 /** 162 * Returns true if toFormatter can be called without throwing an 163 * UnsupportedOperationException. 164 * 165 * @return true if a formatter can be built 166 */ 167 public boolean canBuildFormatter() { 168 return isFormatter(getFormatter()); 169 } 170 171 /** 172 * Returns true if toPrinter can be called without throwing an 173 * UnsupportedOperationException. 174 * 175 * @return true if a printer can be built 176 */ 177 public boolean canBuildPrinter() { 178 return isPrinter(getFormatter()); 179 } 180 181 /** 182 * Returns true if toParser can be called without throwing an 183 * UnsupportedOperationException. 184 * 185 * @return true if a parser can be built 186 */ 187 public boolean canBuildParser() { 188 return isParser(getFormatter()); 189 } 190 191 //----------------------------------------------------------------------- 192 /** 193 * Clears out all the appended elements, allowing this builder to be 194 * reused. 195 */ 196 public void clear() { 197 iFormatter = null; 198 iElementPairs.clear(); 199 } 200 201 //----------------------------------------------------------------------- 202 /** 203 * Appends another formatter. 204 * <p> 205 * This extracts the underlying printer and parser and appends them 206 * The printer and parser interfaces are the low-level part of the formatting API. 207 * Normally, instances are extracted from another formatter. 208 * Note however that any formatter specific information, such as the locale, 209 * time-zone, chronology, offset parsing or pivot/default year, will not be 210 * extracted by this method. 211 * 212 * @param formatter the formatter to add 213 * @return this DateTimeFormatterBuilder, for chaining 214 * @throws IllegalArgumentException if formatter is null or of an invalid type 215 */ 216 public DateTimeFormatterBuilder append(DateTimeFormatter formatter) { 217 if (formatter == null) { 218 throw new IllegalArgumentException("No formatter supplied"); 219 } 220 return append0(formatter.getPrinter(), formatter.getParser()); 221 } 222 223 /** 224 * Appends just a printer. With no matching parser, a parser cannot be 225 * built from this DateTimeFormatterBuilder. 226 * <p> 227 * The printer interface is part of the low-level part of the formatting API. 228 * Normally, instances are extracted from another formatter. 229 * Note however that any formatter specific information, such as the locale, 230 * time-zone, chronology, offset parsing or pivot/default year, will not be 231 * extracted by this method. 232 * 233 * @param printer the printer to add, not null 234 * @return this DateTimeFormatterBuilder, for chaining 235 * @throws IllegalArgumentException if printer is null or of an invalid type 236 */ 237 public DateTimeFormatterBuilder append(DateTimePrinter printer) { 238 checkPrinter(printer); 239 return append0(printer, null); 240 } 241 242 /** 243 * Appends just a parser. With no matching printer, a printer cannot be 244 * built from this builder. 245 * <p> 246 * The parser interface is part of the low-level part of the formatting API. 247 * Normally, instances are extracted from another formatter. 248 * Note however that any formatter specific information, such as the locale, 249 * time-zone, chronology, offset parsing or pivot/default year, will not be 250 * extracted by this method. 251 * 252 * @param parser the parser to add, not null 253 * @return this DateTimeFormatterBuilder, for chaining 254 * @throws IllegalArgumentException if parser is null or of an invalid type 255 */ 256 public DateTimeFormatterBuilder append(DateTimeParser parser) { 257 checkParser(parser); 258 return append0(null, parser); 259 } 260 261 /** 262 * Appends a printer/parser pair. 263 * <p> 264 * The printer and parser interfaces are the low-level part of the formatting API. 265 * Normally, instances are extracted from another formatter. 266 * Note however that any formatter specific information, such as the locale, 267 * time-zone, chronology, offset parsing or pivot/default year, will not be 268 * extracted by this method. 269 * 270 * @param printer the printer to add, not null 271 * @param parser the parser to add, not null 272 * @return this DateTimeFormatterBuilder, for chaining 273 * @throws IllegalArgumentException if printer or parser is null or of an invalid type 274 */ 275 public DateTimeFormatterBuilder append(DateTimePrinter printer, DateTimeParser parser) { 276 checkPrinter(printer); 277 checkParser(parser); 278 return append0(printer, parser); 279 } 280 281 /** 282 * Appends a printer and a set of matching parsers. When parsing, the first 283 * parser in the list is selected for parsing. If it fails, the next is 284 * chosen, and so on. If none of these parsers succeeds, then the failed 285 * position of the parser that made the greatest progress is returned. 286 * <p> 287 * Only the printer is optional. In addition, it is illegal for any but the 288 * last of the parser array elements to be null. If the last element is 289 * null, this represents the empty parser. The presence of an empty parser 290 * indicates that the entire array of parse formats is optional. 291 * <p> 292 * The printer and parser interfaces are the low-level part of the formatting API. 293 * Normally, instances are extracted from another formatter. 294 * Note however that any formatter specific information, such as the locale, 295 * time-zone, chronology, offset parsing or pivot/default year, will not be 296 * extracted by this method. 297 * 298 * @param printer the printer to add 299 * @param parsers the parsers to add 300 * @return this DateTimeFormatterBuilder, for chaining 301 * @throws IllegalArgumentException if any printer or parser is of an invalid type 302 * @throws IllegalArgumentException if any parser element but the last is null 303 */ 304 public DateTimeFormatterBuilder append(DateTimePrinter printer, DateTimeParser[] parsers) { 305 if (printer != null) { 306 checkPrinter(printer); 307 } 308 if (parsers == null) { 309 throw new IllegalArgumentException("No parsers supplied"); 310 } 311 int length = parsers.length; 312 if (length == 1) { 313 if (parsers[0] == null) { 314 throw new IllegalArgumentException("No parser supplied"); 315 } 316 return append0(printer, parsers[0]); 317 } 318 319 DateTimeParser[] copyOfParsers = new DateTimeParser[length]; 320 int i; 321 for (i = 0; i < length - 1; i++) { 322 if ((copyOfParsers[i] = parsers[i]) == null) { 323 throw new IllegalArgumentException("Incomplete parser array"); 324 } 325 } 326 copyOfParsers[i] = parsers[i]; 327 328 return append0(printer, new MatchingParser(copyOfParsers)); 329 } 330 331 /** 332 * Appends just a parser element which is optional. With no matching 333 * printer, a printer cannot be built from this DateTimeFormatterBuilder. 334 * <p> 335 * The parser interface is part of the low-level part of the formatting API. 336 * Normally, instances are extracted from another formatter. 337 * Note however that any formatter specific information, such as the locale, 338 * time-zone, chronology, offset parsing or pivot/default year, will not be 339 * extracted by this method. 340 * 341 * @return this DateTimeFormatterBuilder, for chaining 342 * @throws IllegalArgumentException if parser is null or of an invalid type 343 */ 344 public DateTimeFormatterBuilder appendOptional(DateTimeParser parser) { 345 checkParser(parser); 346 DateTimeParser[] parsers = new DateTimeParser[] {parser, null}; 347 return append0(null, new MatchingParser(parsers)); 348 } 349 350 //----------------------------------------------------------------------- 351 /** 352 * Checks if the parser is non null and a provider. 353 * 354 * @param parser the parser to check 355 */ 356 private void checkParser(DateTimeParser parser) { 357 if (parser == null) { 358 throw new IllegalArgumentException("No parser supplied"); 359 } 360 } 361 362 /** 363 * Checks if the printer is non null and a provider. 364 * 365 * @param printer the printer to check 366 */ 367 private void checkPrinter(DateTimePrinter printer) { 368 if (printer == null) { 369 throw new IllegalArgumentException("No printer supplied"); 370 } 371 } 372 373 private DateTimeFormatterBuilder append0(Object element) { 374 iFormatter = null; 375 // Add the element as both a printer and parser. 376 iElementPairs.add(element); 377 iElementPairs.add(element); 378 return this; 379 } 380 381 private DateTimeFormatterBuilder append0( 382 DateTimePrinter printer, DateTimeParser parser) { 383 iFormatter = null; 384 iElementPairs.add(printer); 385 iElementPairs.add(parser); 386 return this; 387 } 388 389 //----------------------------------------------------------------------- 390 /** 391 * Instructs the printer to emit a specific character, and the parser to 392 * expect it. The parser is case-insensitive. 393 * 394 * @return this DateTimeFormatterBuilder, for chaining 395 */ 396 public DateTimeFormatterBuilder appendLiteral(char c) { 397 return append0(new CharacterLiteral(c)); 398 } 399 400 /** 401 * Instructs the printer to emit specific text, and the parser to expect 402 * it. The parser is case-insensitive. 403 * 404 * @return this DateTimeFormatterBuilder, for chaining 405 * @throws IllegalArgumentException if text is null 406 */ 407 public DateTimeFormatterBuilder appendLiteral(String text) { 408 if (text == null) { 409 throw new IllegalArgumentException("Literal must not be null"); 410 } 411 switch (text.length()) { 412 case 0: 413 return this; 414 case 1: 415 return append0(new CharacterLiteral(text.charAt(0))); 416 default: 417 return append0(new StringLiteral(text)); 418 } 419 } 420 421 /** 422 * Instructs the printer to emit a field value as a decimal number, and the 423 * parser to expect an unsigned decimal number. 424 * 425 * @param fieldType type of field to append 426 * @param minDigits minimum number of digits to <i>print</i> 427 * @param maxDigits maximum number of digits to <i>parse</i>, or the estimated 428 * maximum number of digits to print 429 * @return this DateTimeFormatterBuilder, for chaining 430 * @throws IllegalArgumentException if field type is null 431 */ 432 public DateTimeFormatterBuilder appendDecimal( 433 DateTimeFieldType fieldType, int minDigits, int maxDigits) { 434 if (fieldType == null) { 435 throw new IllegalArgumentException("Field type must not be null"); 436 } 437 if (maxDigits < minDigits) { 438 maxDigits = minDigits; 439 } 440 if (minDigits < 0 || maxDigits <= 0) { 441 throw new IllegalArgumentException(); 442 } 443 if (minDigits <= 1) { 444 return append0(new UnpaddedNumber(fieldType, maxDigits, false)); 445 } else { 446 return append0(new PaddedNumber(fieldType, maxDigits, false, minDigits)); 447 } 448 } 449 450 /** 451 * Instructs the printer to emit a field value as a fixed-width decimal 452 * number (smaller numbers will be left-padded with zeros), and the parser 453 * to expect an unsigned decimal number with the same fixed width. 454 * 455 * @param fieldType type of field to append 456 * @param numDigits the exact number of digits to parse or print, except if 457 * printed value requires more digits 458 * @return this DateTimeFormatterBuilder, for chaining 459 * @throws IllegalArgumentException if field type is null or if <code>numDigits <= 0</code> 460 * @since 1.5 461 */ 462 public DateTimeFormatterBuilder appendFixedDecimal( 463 DateTimeFieldType fieldType, int numDigits) { 464 if (fieldType == null) { 465 throw new IllegalArgumentException("Field type must not be null"); 466 } 467 if (numDigits <= 0) { 468 throw new IllegalArgumentException("Illegal number of digits: " + numDigits); 469 } 470 return append0(new FixedNumber(fieldType, numDigits, false)); 471 } 472 473 /** 474 * Instructs the printer to emit a field value as a decimal number, and the 475 * parser to expect a signed decimal number. 476 * 477 * @param fieldType type of field to append 478 * @param minDigits minimum number of digits to <i>print</i> 479 * @param maxDigits maximum number of digits to <i>parse</i>, or the estimated 480 * maximum number of digits to print 481 * @return this DateTimeFormatterBuilder, for chaining 482 * @throws IllegalArgumentException if field type is null 483 */ 484 public DateTimeFormatterBuilder appendSignedDecimal( 485 DateTimeFieldType fieldType, int minDigits, int maxDigits) { 486 if (fieldType == null) { 487 throw new IllegalArgumentException("Field type must not be null"); 488 } 489 if (maxDigits < minDigits) { 490 maxDigits = minDigits; 491 } 492 if (minDigits < 0 || maxDigits <= 0) { 493 throw new IllegalArgumentException(); 494 } 495 if (minDigits <= 1) { 496 return append0(new UnpaddedNumber(fieldType, maxDigits, true)); 497 } else { 498 return append0(new PaddedNumber(fieldType, maxDigits, true, minDigits)); 499 } 500 } 501 502 /** 503 * Instructs the printer to emit a field value as a fixed-width decimal 504 * number (smaller numbers will be left-padded with zeros), and the parser 505 * to expect an signed decimal number with the same fixed width. 506 * 507 * @param fieldType type of field to append 508 * @param numDigits the exact number of digits to parse or print, except if 509 * printed value requires more digits 510 * @return this DateTimeFormatterBuilder, for chaining 511 * @throws IllegalArgumentException if field type is null or if <code>numDigits <= 0</code> 512 * @since 1.5 513 */ 514 public DateTimeFormatterBuilder appendFixedSignedDecimal( 515 DateTimeFieldType fieldType, int numDigits) { 516 if (fieldType == null) { 517 throw new IllegalArgumentException("Field type must not be null"); 518 } 519 if (numDigits <= 0) { 520 throw new IllegalArgumentException("Illegal number of digits: " + numDigits); 521 } 522 return append0(new FixedNumber(fieldType, numDigits, true)); 523 } 524 525 /** 526 * Instructs the printer to emit a field value as text, and the 527 * parser to expect text. 528 * 529 * @param fieldType type of field to append 530 * @return this DateTimeFormatterBuilder, for chaining 531 * @throws IllegalArgumentException if field type is null 532 */ 533 public DateTimeFormatterBuilder appendText(DateTimeFieldType fieldType) { 534 if (fieldType == null) { 535 throw new IllegalArgumentException("Field type must not be null"); 536 } 537 return append0(new TextField(fieldType, false)); 538 } 539 540 /** 541 * Instructs the printer to emit a field value as short text, and the 542 * parser to expect text. 543 * 544 * @param fieldType type of field to append 545 * @return this DateTimeFormatterBuilder, for chaining 546 * @throws IllegalArgumentException if field type is null 547 */ 548 public DateTimeFormatterBuilder appendShortText(DateTimeFieldType fieldType) { 549 if (fieldType == null) { 550 throw new IllegalArgumentException("Field type must not be null"); 551 } 552 return append0(new TextField(fieldType, true)); 553 } 554 555 /** 556 * Instructs the printer to emit a remainder of time as a decimal fraction, 557 * without decimal point. For example, if the field is specified as 558 * minuteOfHour and the time is 12:30:45, the value printed is 75. A 559 * decimal point is implied, so the fraction is 0.75, or three-quarters of 560 * a minute. 561 * 562 * @param fieldType type of field to append 563 * @param minDigits minimum number of digits to print. 564 * @param maxDigits maximum number of digits to print or parse. 565 * @return this DateTimeFormatterBuilder, for chaining 566 * @throws IllegalArgumentException if field type is null 567 */ 568 public DateTimeFormatterBuilder appendFraction( 569 DateTimeFieldType fieldType, int minDigits, int maxDigits) { 570 if (fieldType == null) { 571 throw new IllegalArgumentException("Field type must not be null"); 572 } 573 if (maxDigits < minDigits) { 574 maxDigits = minDigits; 575 } 576 if (minDigits < 0 || maxDigits <= 0) { 577 throw new IllegalArgumentException(); 578 } 579 return append0(new Fraction(fieldType, minDigits, maxDigits)); 580 } 581 582 /** 583 * Appends the print/parse of a fractional second. 584 * <p> 585 * This reliably handles the case where fractional digits are being handled 586 * beyond a visible decimal point. The digits parsed will always be treated 587 * as the most significant (numerically largest) digits. 588 * Thus '23' will be parsed as 230 milliseconds. 589 * Contrast this behaviour to {@link #appendMillisOfSecond}. 590 * This method does not print or parse the decimal point itself. 591 * 592 * @param minDigits minimum number of digits to print 593 * @param maxDigits maximum number of digits to print or parse 594 * @return this DateTimeFormatterBuilder, for chaining 595 */ 596 public DateTimeFormatterBuilder appendFractionOfSecond(int minDigits, int maxDigits) { 597 return appendFraction(DateTimeFieldType.secondOfDay(), minDigits, maxDigits); 598 } 599 600 /** 601 * Appends the print/parse of a fractional minute. 602 * <p> 603 * This reliably handles the case where fractional digits are being handled 604 * beyond a visible decimal point. The digits parsed will always be treated 605 * as the most significant (numerically largest) digits. 606 * Thus '23' will be parsed as 0.23 minutes (converted to milliseconds). 607 * This method does not print or parse the decimal point itself. 608 * 609 * @param minDigits minimum number of digits to print 610 * @param maxDigits maximum number of digits to print or parse 611 * @return this DateTimeFormatterBuilder, for chaining 612 */ 613 public DateTimeFormatterBuilder appendFractionOfMinute(int minDigits, int maxDigits) { 614 return appendFraction(DateTimeFieldType.minuteOfDay(), minDigits, maxDigits); 615 } 616 617 /** 618 * Appends the print/parse of a fractional hour. 619 * <p> 620 * This reliably handles the case where fractional digits are being handled 621 * beyond a visible decimal point. The digits parsed will always be treated 622 * as the most significant (numerically largest) digits. 623 * Thus '23' will be parsed as 0.23 hours (converted to milliseconds). 624 * This method does not print or parse the decimal point itself. 625 * 626 * @param minDigits minimum number of digits to print 627 * @param maxDigits maximum number of digits to print or parse 628 * @return this DateTimeFormatterBuilder, for chaining 629 */ 630 public DateTimeFormatterBuilder appendFractionOfHour(int minDigits, int maxDigits) { 631 return appendFraction(DateTimeFieldType.hourOfDay(), minDigits, maxDigits); 632 } 633 634 /** 635 * Appends the print/parse of a fractional day. 636 * <p> 637 * This reliably handles the case where fractional digits are being handled 638 * beyond a visible decimal point. The digits parsed will always be treated 639 * as the most significant (numerically largest) digits. 640 * Thus '23' will be parsed as 0.23 days (converted to milliseconds). 641 * This method does not print or parse the decimal point itself. 642 * 643 * @param minDigits minimum number of digits to print 644 * @param maxDigits maximum number of digits to print or parse 645 * @return this DateTimeFormatterBuilder, for chaining 646 */ 647 public DateTimeFormatterBuilder appendFractionOfDay(int minDigits, int maxDigits) { 648 return appendFraction(DateTimeFieldType.dayOfYear(), minDigits, maxDigits); 649 } 650 651 /** 652 * Instructs the printer to emit a numeric millisOfSecond field. 653 * <p> 654 * This method will append a field that prints a three digit value. 655 * During parsing the value that is parsed is assumed to be three digits. 656 * If less than three digits are present then they will be counted as the 657 * smallest parts of the millisecond. This is probably not what you want 658 * if you are using the field as a fraction. Instead, a fractional 659 * millisecond should be produced using {@link #appendFractionOfSecond}. 660 * 661 * @param minDigits minimum number of digits to print 662 * @return this DateTimeFormatterBuilder, for chaining 663 */ 664 public DateTimeFormatterBuilder appendMillisOfSecond(int minDigits) { 665 return appendDecimal(DateTimeFieldType.millisOfSecond(), minDigits, 3); 666 } 667 668 /** 669 * Instructs the printer to emit a numeric millisOfDay field. 670 * 671 * @param minDigits minimum number of digits to print 672 * @return this DateTimeFormatterBuilder, for chaining 673 */ 674 public DateTimeFormatterBuilder appendMillisOfDay(int minDigits) { 675 return appendDecimal(DateTimeFieldType.millisOfDay(), minDigits, 8); 676 } 677 678 /** 679 * Instructs the printer to emit a numeric secondOfMinute field. 680 * 681 * @param minDigits minimum number of digits to print 682 * @return this DateTimeFormatterBuilder, for chaining 683 */ 684 public DateTimeFormatterBuilder appendSecondOfMinute(int minDigits) { 685 return appendDecimal(DateTimeFieldType.secondOfMinute(), minDigits, 2); 686 } 687 688 /** 689 * Instructs the printer to emit a numeric secondOfDay field. 690 * 691 * @param minDigits minimum number of digits to print 692 * @return this DateTimeFormatterBuilder, for chaining 693 */ 694 public DateTimeFormatterBuilder appendSecondOfDay(int minDigits) { 695 return appendDecimal(DateTimeFieldType.secondOfDay(), minDigits, 5); 696 } 697 698 /** 699 * Instructs the printer to emit a numeric minuteOfHour field. 700 * 701 * @param minDigits minimum number of digits to print 702 * @return this DateTimeFormatterBuilder, for chaining 703 */ 704 public DateTimeFormatterBuilder appendMinuteOfHour(int minDigits) { 705 return appendDecimal(DateTimeFieldType.minuteOfHour(), minDigits, 2); 706 } 707 708 /** 709 * Instructs the printer to emit a numeric minuteOfDay field. 710 * 711 * @param minDigits minimum number of digits to print 712 * @return this DateTimeFormatterBuilder, for chaining 713 */ 714 public DateTimeFormatterBuilder appendMinuteOfDay(int minDigits) { 715 return appendDecimal(DateTimeFieldType.minuteOfDay(), minDigits, 4); 716 } 717 718 /** 719 * Instructs the printer to emit a numeric hourOfDay field. 720 * 721 * @param minDigits minimum number of digits to print 722 * @return this DateTimeFormatterBuilder, for chaining 723 */ 724 public DateTimeFormatterBuilder appendHourOfDay(int minDigits) { 725 return appendDecimal(DateTimeFieldType.hourOfDay(), minDigits, 2); 726 } 727 728 /** 729 * Instructs the printer to emit a numeric clockhourOfDay field. 730 * 731 * @param minDigits minimum number of digits to print 732 * @return this DateTimeFormatterBuilder, for chaining 733 */ 734 public DateTimeFormatterBuilder appendClockhourOfDay(int minDigits) { 735 return appendDecimal(DateTimeFieldType.clockhourOfDay(), minDigits, 2); 736 } 737 738 /** 739 * Instructs the printer to emit a numeric hourOfHalfday field. 740 * 741 * @param minDigits minimum number of digits to print 742 * @return this DateTimeFormatterBuilder, for chaining 743 */ 744 public DateTimeFormatterBuilder appendHourOfHalfday(int minDigits) { 745 return appendDecimal(DateTimeFieldType.hourOfHalfday(), minDigits, 2); 746 } 747 748 /** 749 * Instructs the printer to emit a numeric clockhourOfHalfday field. 750 * 751 * @param minDigits minimum number of digits to print 752 * @return this DateTimeFormatterBuilder, for chaining 753 */ 754 public DateTimeFormatterBuilder appendClockhourOfHalfday(int minDigits) { 755 return appendDecimal(DateTimeFieldType.clockhourOfHalfday(), minDigits, 2); 756 } 757 758 /** 759 * Instructs the printer to emit a numeric dayOfWeek field. 760 * 761 * @param minDigits minimum number of digits to print 762 * @return this DateTimeFormatterBuilder, for chaining 763 */ 764 public DateTimeFormatterBuilder appendDayOfWeek(int minDigits) { 765 return appendDecimal(DateTimeFieldType.dayOfWeek(), minDigits, 1); 766 } 767 768 /** 769 * Instructs the printer to emit a numeric dayOfMonth field. 770 * 771 * @param minDigits minimum number of digits to print 772 * @return this DateTimeFormatterBuilder, for chaining 773 */ 774 public DateTimeFormatterBuilder appendDayOfMonth(int minDigits) { 775 return appendDecimal(DateTimeFieldType.dayOfMonth(), minDigits, 2); 776 } 777 778 /** 779 * Instructs the printer to emit a numeric dayOfYear field. 780 * 781 * @param minDigits minimum number of digits to print 782 * @return this DateTimeFormatterBuilder, for chaining 783 */ 784 public DateTimeFormatterBuilder appendDayOfYear(int minDigits) { 785 return appendDecimal(DateTimeFieldType.dayOfYear(), minDigits, 3); 786 } 787 788 /** 789 * Instructs the printer to emit a numeric weekOfWeekyear field. 790 * 791 * @param minDigits minimum number of digits to print 792 * @return this DateTimeFormatterBuilder, for chaining 793 */ 794 public DateTimeFormatterBuilder appendWeekOfWeekyear(int minDigits) { 795 return appendDecimal(DateTimeFieldType.weekOfWeekyear(), minDigits, 2); 796 } 797 798 /** 799 * Instructs the printer to emit a numeric weekyear field. 800 * 801 * @param minDigits minimum number of digits to <i>print</i> 802 * @param maxDigits maximum number of digits to <i>parse</i>, or the estimated 803 * maximum number of digits to print 804 * @return this DateTimeFormatterBuilder, for chaining 805 */ 806 public DateTimeFormatterBuilder appendWeekyear(int minDigits, int maxDigits) { 807 return appendSignedDecimal(DateTimeFieldType.weekyear(), minDigits, maxDigits); 808 } 809 810 /** 811 * Instructs the printer to emit a numeric monthOfYear field. 812 * 813 * @param minDigits minimum number of digits to print 814 * @return this DateTimeFormatterBuilder, for chaining 815 */ 816 public DateTimeFormatterBuilder appendMonthOfYear(int minDigits) { 817 return appendDecimal(DateTimeFieldType.monthOfYear(), minDigits, 2); 818 } 819 820 /** 821 * Instructs the printer to emit a numeric year field. 822 * 823 * @param minDigits minimum number of digits to <i>print</i> 824 * @param maxDigits maximum number of digits to <i>parse</i>, or the estimated 825 * maximum number of digits to print 826 * @return this DateTimeFormatterBuilder, for chaining 827 */ 828 public DateTimeFormatterBuilder appendYear(int minDigits, int maxDigits) { 829 return appendSignedDecimal(DateTimeFieldType.year(), minDigits, maxDigits); 830 } 831 832 /** 833 * Instructs the printer to emit a numeric year field which always prints 834 * and parses two digits. A pivot year is used during parsing to determine 835 * the range of supported years as <code>(pivot - 50) .. (pivot + 49)</code>. 836 * 837 * <pre> 838 * pivot supported range 00 is 20 is 40 is 60 is 80 is 839 * --------------------------------------------------------------- 840 * 1950 1900..1999 1900 1920 1940 1960 1980 841 * 1975 1925..2024 2000 2020 1940 1960 1980 842 * 2000 1950..2049 2000 2020 2040 1960 1980 843 * 2025 1975..2074 2000 2020 2040 2060 1980 844 * 2050 2000..2099 2000 2020 2040 2060 2080 845 * </pre> 846 * 847 * @param pivot pivot year to use when parsing 848 * @return this DateTimeFormatterBuilder, for chaining 849 */ 850 public DateTimeFormatterBuilder appendTwoDigitYear(int pivot) { 851 return appendTwoDigitYear(pivot, false); 852 } 853 854 /** 855 * Instructs the printer to emit a numeric year field which always prints 856 * two digits. A pivot year is used during parsing to determine the range 857 * of supported years as <code>(pivot - 50) .. (pivot + 49)</code>. If 858 * parse is instructed to be lenient and the digit count is not two, it is 859 * treated as an absolute year. With lenient parsing, specifying a positive 860 * or negative sign before the year also makes it absolute. 861 * 862 * @param pivot pivot year to use when parsing 863 * @param lenientParse when true, if digit count is not two, it is treated 864 * as an absolute year 865 * @return this DateTimeFormatterBuilder, for chaining 866 * @since 1.1 867 */ 868 public DateTimeFormatterBuilder appendTwoDigitYear(int pivot, boolean lenientParse) { 869 return append0(new TwoDigitYear(DateTimeFieldType.year(), pivot, lenientParse)); 870 } 871 872 /** 873 * Instructs the printer to emit a numeric weekyear field which always prints 874 * and parses two digits. A pivot year is used during parsing to determine 875 * the range of supported years as <code>(pivot - 50) .. (pivot + 49)</code>. 876 * 877 * <pre> 878 * pivot supported range 00 is 20 is 40 is 60 is 80 is 879 * --------------------------------------------------------------- 880 * 1950 1900..1999 1900 1920 1940 1960 1980 881 * 1975 1925..2024 2000 2020 1940 1960 1980 882 * 2000 1950..2049 2000 2020 2040 1960 1980 883 * 2025 1975..2074 2000 2020 2040 2060 1980 884 * 2050 2000..2099 2000 2020 2040 2060 2080 885 * </pre> 886 * 887 * @param pivot pivot weekyear to use when parsing 888 * @return this DateTimeFormatterBuilder, for chaining 889 */ 890 public DateTimeFormatterBuilder appendTwoDigitWeekyear(int pivot) { 891 return appendTwoDigitWeekyear(pivot, false); 892 } 893 894 /** 895 * Instructs the printer to emit a numeric weekyear field which always prints 896 * two digits. A pivot year is used during parsing to determine the range 897 * of supported years as <code>(pivot - 50) .. (pivot + 49)</code>. If 898 * parse is instructed to be lenient and the digit count is not two, it is 899 * treated as an absolute weekyear. With lenient parsing, specifying a positive 900 * or negative sign before the weekyear also makes it absolute. 901 * 902 * @param pivot pivot weekyear to use when parsing 903 * @param lenientParse when true, if digit count is not two, it is treated 904 * as an absolute weekyear 905 * @return this DateTimeFormatterBuilder, for chaining 906 * @since 1.1 907 */ 908 public DateTimeFormatterBuilder appendTwoDigitWeekyear(int pivot, boolean lenientParse) { 909 return append0(new TwoDigitYear(DateTimeFieldType.weekyear(), pivot, lenientParse)); 910 } 911 912 /** 913 * Instructs the printer to emit a numeric yearOfEra field. 914 * 915 * @param minDigits minimum number of digits to <i>print</i> 916 * @param maxDigits maximum number of digits to <i>parse</i>, or the estimated 917 * maximum number of digits to print 918 * @return this DateTimeFormatterBuilder, for chaining 919 */ 920 public DateTimeFormatterBuilder appendYearOfEra(int minDigits, int maxDigits) { 921 return appendDecimal(DateTimeFieldType.yearOfEra(), minDigits, maxDigits); 922 } 923 924 /** 925 * Instructs the printer to emit a numeric year of century field. 926 * 927 * @param minDigits minimum number of digits to print 928 * @param maxDigits maximum number of digits to <i>parse</i>, or the estimated 929 * maximum number of digits to print 930 * @return this DateTimeFormatterBuilder, for chaining 931 */ 932 public DateTimeFormatterBuilder appendYearOfCentury(int minDigits, int maxDigits) { 933 return appendDecimal(DateTimeFieldType.yearOfCentury(), minDigits, maxDigits); 934 } 935 936 /** 937 * Instructs the printer to emit a numeric century of era field. 938 * 939 * @param minDigits minimum number of digits to print 940 * @param maxDigits maximum number of digits to <i>parse</i>, or the estimated 941 * maximum number of digits to print 942 * @return this DateTimeFormatterBuilder, for chaining 943 */ 944 public DateTimeFormatterBuilder appendCenturyOfEra(int minDigits, int maxDigits) { 945 return appendSignedDecimal(DateTimeFieldType.centuryOfEra(), minDigits, maxDigits); 946 } 947 948 /** 949 * Instructs the printer to emit a locale-specific AM/PM text, and the 950 * parser to expect it. The parser is case-insensitive. 951 * 952 * @return this DateTimeFormatterBuilder, for chaining 953 */ 954 public DateTimeFormatterBuilder appendHalfdayOfDayText() { 955 return appendText(DateTimeFieldType.halfdayOfDay()); 956 } 957 958 /** 959 * Instructs the printer to emit a locale-specific dayOfWeek text. The 960 * parser will accept a long or short dayOfWeek text, case-insensitive. 961 * 962 * @return this DateTimeFormatterBuilder, for chaining 963 */ 964 public DateTimeFormatterBuilder appendDayOfWeekText() { 965 return appendText(DateTimeFieldType.dayOfWeek()); 966 } 967 968 /** 969 * Instructs the printer to emit a short locale-specific dayOfWeek 970 * text. The parser will accept a long or short dayOfWeek text, 971 * case-insensitive. 972 * 973 * @return this DateTimeFormatterBuilder, for chaining 974 */ 975 public DateTimeFormatterBuilder appendDayOfWeekShortText() { 976 return appendShortText(DateTimeFieldType.dayOfWeek()); 977 } 978 979 /** 980 * Instructs the printer to emit a short locale-specific monthOfYear 981 * text. The parser will accept a long or short monthOfYear text, 982 * case-insensitive. 983 * 984 * @return this DateTimeFormatterBuilder, for chaining 985 */ 986 public DateTimeFormatterBuilder appendMonthOfYearText() { 987 return appendText(DateTimeFieldType.monthOfYear()); 988 } 989 990 /** 991 * Instructs the printer to emit a locale-specific monthOfYear text. The 992 * parser will accept a long or short monthOfYear text, case-insensitive. 993 * 994 * @return this DateTimeFormatterBuilder, for chaining 995 */ 996 public DateTimeFormatterBuilder appendMonthOfYearShortText() { 997 return appendShortText(DateTimeFieldType.monthOfYear()); 998 } 999 1000 /** 1001 * Instructs the printer to emit a locale-specific era text (BC/AD), and 1002 * the parser to expect it. The parser is case-insensitive. 1003 * 1004 * @return this DateTimeFormatterBuilder, for chaining 1005 */ 1006 public DateTimeFormatterBuilder appendEraText() { 1007 return appendText(DateTimeFieldType.era()); 1008 } 1009 1010 /** 1011 * Instructs the printer to emit a locale-specific time zone name. 1012 * Using this method prevents parsing, because time zone names are not unique. 1013 * See {@link #appendTimeZoneName(Map)}. 1014 * 1015 * @return this DateTimeFormatterBuilder, for chaining 1016 */ 1017 public DateTimeFormatterBuilder appendTimeZoneName() { 1018 return append0(new TimeZoneName(TimeZoneName.LONG_NAME, null), null); 1019 } 1020 1021 /** 1022 * Instructs the printer to emit a locale-specific time zone name, providing a lookup for parsing. 1023 * Time zone names are not unique, thus the API forces you to supply the lookup. 1024 * The names are searched in the order of the map, thus it is strongly recommended 1025 * to use a {@code LinkedHashMap} or similar. 1026 * 1027 * @param parseLookup the table of names, not null 1028 * @return this DateTimeFormatterBuilder, for chaining 1029 */ 1030 public DateTimeFormatterBuilder appendTimeZoneName(Map<String, DateTimeZone> parseLookup) { 1031 TimeZoneName pp = new TimeZoneName(TimeZoneName.LONG_NAME, parseLookup); 1032 return append0(pp, pp); 1033 } 1034 1035 /** 1036 * Instructs the printer to emit a short locale-specific time zone name. 1037 * Using this method prevents parsing, because time zone names are not unique. 1038 * See {@link #appendTimeZoneShortName(Map)}. 1039 * 1040 * @return this DateTimeFormatterBuilder, for chaining 1041 */ 1042 public DateTimeFormatterBuilder appendTimeZoneShortName() { 1043 return append0(new TimeZoneName(TimeZoneName.SHORT_NAME, null), null); 1044 } 1045 1046 /** 1047 * Instructs the printer to emit a short locale-specific time zone 1048 * name, providing a lookup for parsing. 1049 * Time zone names are not unique, thus the API forces you to supply the lookup. 1050 * The names are searched in the order of the map, thus it is strongly recommended 1051 * to use a {@code LinkedHashMap} or similar. 1052 * 1053 * @param parseLookup the table of names, null to use the {@link DateTimeUtils#getDefaultTimeZoneNames() default names} 1054 * @return this DateTimeFormatterBuilder, for chaining 1055 */ 1056 public DateTimeFormatterBuilder appendTimeZoneShortName(Map<String, DateTimeZone> parseLookup) { 1057 TimeZoneName pp = new TimeZoneName(TimeZoneName.SHORT_NAME, parseLookup); 1058 return append0(pp, pp); 1059 } 1060 1061 /** 1062 * Instructs the printer to emit the identifier of the time zone. 1063 * From version 2.0, this field can be parsed. 1064 * 1065 * @return this DateTimeFormatterBuilder, for chaining 1066 */ 1067 public DateTimeFormatterBuilder appendTimeZoneId() { 1068 return append0(TimeZoneId.INSTANCE, TimeZoneId.INSTANCE); 1069 } 1070 1071 /** 1072 * Instructs the printer to emit text and numbers to display time zone 1073 * offset from UTC. A parser will use the parsed time zone offset to adjust 1074 * the datetime. 1075 * <p> 1076 * If zero offset text is supplied, then it will be printed when the zone is zero. 1077 * During parsing, either the zero offset text, or the offset will be parsed. 1078 * 1079 * @param zeroOffsetText the text to use if time zone offset is zero. If 1080 * null, offset is always shown. 1081 * @param showSeparators if true, prints ':' separator before minute and 1082 * second field and prints '.' separator before fraction field. 1083 * @param minFields minimum number of fields to print, stopping when no 1084 * more precision is required. 1=hours, 2=minutes, 3=seconds, 4=fraction 1085 * @param maxFields maximum number of fields to print 1086 * @return this DateTimeFormatterBuilder, for chaining 1087 */ 1088 public DateTimeFormatterBuilder appendTimeZoneOffset( 1089 String zeroOffsetText, boolean showSeparators, 1090 int minFields, int maxFields) { 1091 return append0(new TimeZoneOffset 1092 (zeroOffsetText, zeroOffsetText, showSeparators, minFields, maxFields)); 1093 } 1094 1095 /** 1096 * Instructs the printer to emit text and numbers to display time zone 1097 * offset from UTC. A parser will use the parsed time zone offset to adjust 1098 * the datetime. 1099 * <p> 1100 * If zero offset print text is supplied, then it will be printed when the zone is zero. 1101 * If zero offset parse text is supplied, then either it or the offset will be parsed. 1102 * 1103 * @param zeroOffsetPrintText the text to print if time zone offset is zero. If 1104 * null, offset is always shown. 1105 * @param zeroOffsetParseText the text to optionally parse to indicate that the time 1106 * zone offset is zero. If null, then always use the offset. 1107 * @param showSeparators if true, prints ':' separator before minute and 1108 * second field and prints '.' separator before fraction field. 1109 * @param minFields minimum number of fields to print, stopping when no 1110 * more precision is required. 1=hours, 2=minutes, 3=seconds, 4=fraction 1111 * @param maxFields maximum number of fields to print 1112 * @return this DateTimeFormatterBuilder, for chaining 1113 * @since 2.0 1114 */ 1115 public DateTimeFormatterBuilder appendTimeZoneOffset( 1116 String zeroOffsetPrintText, String zeroOffsetParseText, boolean showSeparators, 1117 int minFields, int maxFields) { 1118 return append0(new TimeZoneOffset 1119 (zeroOffsetPrintText, zeroOffsetParseText, showSeparators, minFields, maxFields)); 1120 } 1121 1122 //----------------------------------------------------------------------- 1123 /** 1124 * Calls upon {@link DateTimeFormat} to parse the pattern and append the 1125 * results into this builder. 1126 * 1127 * @param pattern pattern specification 1128 * @throws IllegalArgumentException if the pattern is invalid 1129 * @see DateTimeFormat 1130 */ 1131 public DateTimeFormatterBuilder appendPattern(String pattern) { 1132 DateTimeFormat.appendPatternTo(this, pattern); 1133 return this; 1134 } 1135 1136 //----------------------------------------------------------------------- 1137 private Object getFormatter() { 1138 Object f = iFormatter; 1139 1140 if (f == null) { 1141 if (iElementPairs.size() == 2) { 1142 Object printer = iElementPairs.get(0); 1143 Object parser = iElementPairs.get(1); 1144 1145 if (printer != null) { 1146 if (printer == parser || parser == null) { 1147 f = printer; 1148 } 1149 } else { 1150 f = parser; 1151 } 1152 } 1153 1154 if (f == null) { 1155 f = new Composite(iElementPairs); 1156 } 1157 1158 iFormatter = f; 1159 } 1160 1161 return f; 1162 } 1163 1164 private boolean isPrinter(Object f) { 1165 if (f instanceof DateTimePrinter) { 1166 if (f instanceof Composite) { 1167 return ((Composite)f).isPrinter(); 1168 } 1169 return true; 1170 } 1171 return false; 1172 } 1173 1174 private boolean isParser(Object f) { 1175 if (f instanceof DateTimeParser) { 1176 if (f instanceof Composite) { 1177 return ((Composite)f).isParser(); 1178 } 1179 return true; 1180 } 1181 return false; 1182 } 1183 1184 private boolean isFormatter(Object f) { 1185 return (isPrinter(f) || isParser(f)); 1186 } 1187 1188 static void appendUnknownString(StringBuffer buf, int len) { 1189 for (int i = len; --i >= 0;) { 1190 buf.append('\ufffd'); 1191 } 1192 } 1193 1194 static void printUnknownString(Writer out, int len) throws IOException { 1195 for (int i = len; --i >= 0;) { 1196 out.write('\ufffd'); 1197 } 1198 } 1199 1200 //----------------------------------------------------------------------- 1201 static class CharacterLiteral 1202 implements DateTimePrinter, DateTimeParser { 1203 1204 private final char iValue; 1205 1206 CharacterLiteral(char value) { 1207 super(); 1208 iValue = value; 1209 } 1210 1211 public int estimatePrintedLength() { 1212 return 1; 1213 } 1214 1215 public void printTo( 1216 StringBuffer buf, long instant, Chronology chrono, 1217 int displayOffset, DateTimeZone displayZone, Locale locale) { 1218 buf.append(iValue); 1219 } 1220 1221 public void printTo( 1222 Writer out, long instant, Chronology chrono, 1223 int displayOffset, DateTimeZone displayZone, Locale locale) throws IOException { 1224 out.write(iValue); 1225 } 1226 1227 public void printTo(StringBuffer buf, ReadablePartial partial, Locale locale) { 1228 buf.append(iValue); 1229 } 1230 1231 public void printTo(Writer out, ReadablePartial partial, Locale locale) throws IOException { 1232 out.write(iValue); 1233 } 1234 1235 public int estimateParsedLength() { 1236 return 1; 1237 } 1238 1239 public int parseInto(DateTimeParserBucket bucket, String text, int position) { 1240 if (position >= text.length()) { 1241 return ~position; 1242 } 1243 1244 char a = text.charAt(position); 1245 char b = iValue; 1246 1247 if (a != b) { 1248 a = Character.toUpperCase(a); 1249 b = Character.toUpperCase(b); 1250 if (a != b) { 1251 a = Character.toLowerCase(a); 1252 b = Character.toLowerCase(b); 1253 if (a != b) { 1254 return ~position; 1255 } 1256 } 1257 } 1258 1259 return position + 1; 1260 } 1261 } 1262 1263 //----------------------------------------------------------------------- 1264 static class StringLiteral 1265 implements DateTimePrinter, DateTimeParser { 1266 1267 private final String iValue; 1268 1269 StringLiteral(String value) { 1270 super(); 1271 iValue = value; 1272 } 1273 1274 public int estimatePrintedLength() { 1275 return iValue.length(); 1276 } 1277 1278 public void printTo( 1279 StringBuffer buf, long instant, Chronology chrono, 1280 int displayOffset, DateTimeZone displayZone, Locale locale) { 1281 buf.append(iValue); 1282 } 1283 1284 public void printTo( 1285 Writer out, long instant, Chronology chrono, 1286 int displayOffset, DateTimeZone displayZone, Locale locale) throws IOException { 1287 out.write(iValue); 1288 } 1289 1290 public void printTo(StringBuffer buf, ReadablePartial partial, Locale locale) { 1291 buf.append(iValue); 1292 } 1293 1294 public void printTo(Writer out, ReadablePartial partial, Locale locale) throws IOException { 1295 out.write(iValue); 1296 } 1297 1298 public int estimateParsedLength() { 1299 return iValue.length(); 1300 } 1301 1302 public int parseInto(DateTimeParserBucket bucket, String text, int position) { 1303 if (text.regionMatches(true, position, iValue, 0, iValue.length())) { 1304 return position + iValue.length(); 1305 } 1306 return ~position; 1307 } 1308 } 1309 1310 //----------------------------------------------------------------------- 1311 static abstract class NumberFormatter 1312 implements DateTimePrinter, DateTimeParser { 1313 protected final DateTimeFieldType iFieldType; 1314 protected final int iMaxParsedDigits; 1315 protected final boolean iSigned; 1316 1317 NumberFormatter(DateTimeFieldType fieldType, 1318 int maxParsedDigits, boolean signed) { 1319 super(); 1320 iFieldType = fieldType; 1321 iMaxParsedDigits = maxParsedDigits; 1322 iSigned = signed; 1323 } 1324 1325 public int estimateParsedLength() { 1326 return iMaxParsedDigits; 1327 } 1328 1329 public int parseInto(DateTimeParserBucket bucket, String text, int position) { 1330 int limit = Math.min(iMaxParsedDigits, text.length() - position); 1331 1332 boolean negative = false; 1333 int length = 0; 1334 while (length < limit) { 1335 char c = text.charAt(position + length); 1336 if (length == 0 && (c == '-' || c == '+') && iSigned) { 1337 negative = c == '-'; 1338 1339 // Next character must be a digit. 1340 if (length + 1 >= limit || 1341 (c = text.charAt(position + length + 1)) < '0' || c > '9') 1342 { 1343 break; 1344 } 1345 1346 if (negative) { 1347 length++; 1348 } else { 1349 // Skip the '+' for parseInt to succeed. 1350 position++; 1351 } 1352 // Expand the limit to disregard the sign character. 1353 limit = Math.min(limit + 1, text.length() - position); 1354 continue; 1355 } 1356 if (c < '0' || c > '9') { 1357 break; 1358 } 1359 length++; 1360 } 1361 1362 if (length == 0) { 1363 return ~position; 1364 } 1365 1366 int value; 1367 if (length >= 9) { 1368 // Since value may exceed integer limits, use stock parser 1369 // which checks for this. 1370 value = Integer.parseInt(text.substring(position, position += length)); 1371 } else { 1372 int i = position; 1373 if (negative) { 1374 i++; 1375 } 1376 try { 1377 value = text.charAt(i++) - '0'; 1378 } catch (StringIndexOutOfBoundsException e) { 1379 return ~position; 1380 } 1381 position += length; 1382 while (i < position) { 1383 value = ((value << 3) + (value << 1)) + text.charAt(i++) - '0'; 1384 } 1385 if (negative) { 1386 value = -value; 1387 } 1388 } 1389 1390 bucket.saveField(iFieldType, value); 1391 return position; 1392 } 1393 } 1394 1395 //----------------------------------------------------------------------- 1396 static class UnpaddedNumber extends NumberFormatter { 1397 1398 protected UnpaddedNumber(DateTimeFieldType fieldType, 1399 int maxParsedDigits, boolean signed) 1400 { 1401 super(fieldType, maxParsedDigits, signed); 1402 } 1403 1404 public int estimatePrintedLength() { 1405 return iMaxParsedDigits; 1406 } 1407 1408 public void printTo( 1409 StringBuffer buf, long instant, Chronology chrono, 1410 int displayOffset, DateTimeZone displayZone, Locale locale) { 1411 try { 1412 DateTimeField field = iFieldType.getField(chrono); 1413 FormatUtils.appendUnpaddedInteger(buf, field.get(instant)); 1414 } catch (RuntimeException e) { 1415 buf.append('\ufffd'); 1416 } 1417 } 1418 1419 public void printTo( 1420 Writer out, long instant, Chronology chrono, 1421 int displayOffset, DateTimeZone displayZone, Locale locale) throws IOException { 1422 try { 1423 DateTimeField field = iFieldType.getField(chrono); 1424 FormatUtils.writeUnpaddedInteger(out, field.get(instant)); 1425 } catch (RuntimeException e) { 1426 out.write('\ufffd'); 1427 } 1428 } 1429 1430 public void printTo(StringBuffer buf, ReadablePartial partial, Locale locale) { 1431 if (partial.isSupported(iFieldType)) { 1432 try { 1433 FormatUtils.appendUnpaddedInteger(buf, partial.get(iFieldType)); 1434 } catch (RuntimeException e) { 1435 buf.append('\ufffd'); 1436 } 1437 } else { 1438 buf.append('\ufffd'); 1439 } 1440 } 1441 1442 public void printTo(Writer out, ReadablePartial partial, Locale locale) throws IOException { 1443 if (partial.isSupported(iFieldType)) { 1444 try { 1445 FormatUtils.writeUnpaddedInteger(out, partial.get(iFieldType)); 1446 } catch (RuntimeException e) { 1447 out.write('\ufffd'); 1448 } 1449 } else { 1450 out.write('\ufffd'); 1451 } 1452 } 1453 } 1454 1455 //----------------------------------------------------------------------- 1456 static class PaddedNumber extends NumberFormatter { 1457 1458 protected final int iMinPrintedDigits; 1459 1460 protected PaddedNumber(DateTimeFieldType fieldType, int maxParsedDigits, 1461 boolean signed, int minPrintedDigits) 1462 { 1463 super(fieldType, maxParsedDigits, signed); 1464 iMinPrintedDigits = minPrintedDigits; 1465 } 1466 1467 public int estimatePrintedLength() { 1468 return iMaxParsedDigits; 1469 } 1470 1471 public void printTo( 1472 StringBuffer buf, long instant, Chronology chrono, 1473 int displayOffset, DateTimeZone displayZone, Locale locale) { 1474 try { 1475 DateTimeField field = iFieldType.getField(chrono); 1476 FormatUtils.appendPaddedInteger(buf, field.get(instant), iMinPrintedDigits); 1477 } catch (RuntimeException e) { 1478 appendUnknownString(buf, iMinPrintedDigits); 1479 } 1480 } 1481 1482 public void printTo( 1483 Writer out, long instant, Chronology chrono, 1484 int displayOffset, DateTimeZone displayZone, Locale locale) throws IOException { 1485 try { 1486 DateTimeField field = iFieldType.getField(chrono); 1487 FormatUtils.writePaddedInteger(out, field.get(instant), iMinPrintedDigits); 1488 } catch (RuntimeException e) { 1489 printUnknownString(out, iMinPrintedDigits); 1490 } 1491 } 1492 1493 public void printTo(StringBuffer buf, ReadablePartial partial, Locale locale) { 1494 if (partial.isSupported(iFieldType)) { 1495 try { 1496 FormatUtils.appendPaddedInteger(buf, partial.get(iFieldType), iMinPrintedDigits); 1497 } catch (RuntimeException e) { 1498 appendUnknownString(buf, iMinPrintedDigits); 1499 } 1500 } else { 1501 appendUnknownString(buf, iMinPrintedDigits); 1502 } 1503 } 1504 1505 public void printTo(Writer out, ReadablePartial partial, Locale locale) throws IOException { 1506 if (partial.isSupported(iFieldType)) { 1507 try { 1508 FormatUtils.writePaddedInteger(out, partial.get(iFieldType), iMinPrintedDigits); 1509 } catch (RuntimeException e) { 1510 printUnknownString(out, iMinPrintedDigits); 1511 } 1512 } else { 1513 printUnknownString(out, iMinPrintedDigits); 1514 } 1515 } 1516 } 1517 1518 //----------------------------------------------------------------------- 1519 static class FixedNumber extends PaddedNumber { 1520 1521 protected FixedNumber(DateTimeFieldType fieldType, int numDigits, boolean signed) { 1522 super(fieldType, numDigits, signed, numDigits); 1523 } 1524 1525 public int parseInto(DateTimeParserBucket bucket, String text, int position) { 1526 int newPos = super.parseInto(bucket, text, position); 1527 if (newPos < 0) { 1528 return newPos; 1529 } 1530 int expectedPos = position + iMaxParsedDigits; 1531 if (newPos != expectedPos) { 1532 if (iSigned) { 1533 char c = text.charAt(position); 1534 if (c == '-' || c == '+') { 1535 expectedPos++; 1536 } 1537 } 1538 if (newPos > expectedPos) { 1539 // The failure is at the position of the first extra digit. 1540 return ~(expectedPos + 1); 1541 } else if (newPos < expectedPos) { 1542 // The failure is at the position where the next digit should be. 1543 return ~newPos; 1544 } 1545 } 1546 return newPos; 1547 } 1548 } 1549 1550 //----------------------------------------------------------------------- 1551 static class TwoDigitYear 1552 implements DateTimePrinter, DateTimeParser { 1553 1554 /** The field to print/parse. */ 1555 private final DateTimeFieldType iType; 1556 /** The pivot year. */ 1557 private final int iPivot; 1558 private final boolean iLenientParse; 1559 1560 TwoDigitYear(DateTimeFieldType type, int pivot, boolean lenientParse) { 1561 super(); 1562 iType = type; 1563 iPivot = pivot; 1564 iLenientParse = lenientParse; 1565 } 1566 1567 public int estimateParsedLength() { 1568 return iLenientParse ? 4 : 2; 1569 } 1570 1571 public int parseInto(DateTimeParserBucket bucket, String text, int position) { 1572 int limit = text.length() - position; 1573 1574 if (!iLenientParse) { 1575 limit = Math.min(2, limit); 1576 if (limit < 2) { 1577 return ~position; 1578 } 1579 } else { 1580 boolean hasSignChar = false; 1581 boolean negative = false; 1582 int length = 0; 1583 while (length < limit) { 1584 char c = text.charAt(position + length); 1585 if (length == 0 && (c == '-' || c == '+')) { 1586 hasSignChar = true; 1587 negative = c == '-'; 1588 if (negative) { 1589 length++; 1590 } else { 1591 // Skip the '+' for parseInt to succeed. 1592 position++; 1593 limit--; 1594 } 1595 continue; 1596 } 1597 if (c < '0' || c > '9') { 1598 break; 1599 } 1600 length++; 1601 } 1602 1603 if (length == 0) { 1604 return ~position; 1605 } 1606 1607 if (hasSignChar || length != 2) { 1608 int value; 1609 if (length >= 9) { 1610 // Since value may exceed integer limits, use stock 1611 // parser which checks for this. 1612 value = Integer.parseInt(text.substring(position, position += length)); 1613 } else { 1614 int i = position; 1615 if (negative) { 1616 i++; 1617 } 1618 try { 1619 value = text.charAt(i++) - '0'; 1620 } catch (StringIndexOutOfBoundsException e) { 1621 return ~position; 1622 } 1623 position += length; 1624 while (i < position) { 1625 value = ((value << 3) + (value << 1)) + text.charAt(i++) - '0'; 1626 } 1627 if (negative) { 1628 value = -value; 1629 } 1630 } 1631 1632 bucket.saveField(iType, value); 1633 return position; 1634 } 1635 } 1636 1637 int year; 1638 char c = text.charAt(position); 1639 if (c < '0' || c > '9') { 1640 return ~position; 1641 } 1642 year = c - '0'; 1643 c = text.charAt(position + 1); 1644 if (c < '0' || c > '9') { 1645 return ~position; 1646 } 1647 year = ((year << 3) + (year << 1)) + c - '0'; 1648 1649 int pivot = iPivot; 1650 // If the bucket pivot year is non-null, use that when parsing 1651 if (bucket.getPivotYear() != null) { 1652 pivot = bucket.getPivotYear().intValue(); 1653 } 1654 1655 int low = pivot - 50; 1656 1657 int t; 1658 if (low >= 0) { 1659 t = low % 100; 1660 } else { 1661 t = 99 + ((low + 1) % 100); 1662 } 1663 1664 year += low + ((year < t) ? 100 : 0) - t; 1665 1666 bucket.saveField(iType, year); 1667 return position + 2; 1668 } 1669 1670 public int estimatePrintedLength() { 1671 return 2; 1672 } 1673 1674 public void printTo( 1675 StringBuffer buf, long instant, Chronology chrono, 1676 int displayOffset, DateTimeZone displayZone, Locale locale) { 1677 int year = getTwoDigitYear(instant, chrono); 1678 if (year < 0) { 1679 buf.append('\ufffd'); 1680 buf.append('\ufffd'); 1681 } else { 1682 FormatUtils.appendPaddedInteger(buf, year, 2); 1683 } 1684 } 1685 1686 public void printTo( 1687 Writer out, long instant, Chronology chrono, 1688 int displayOffset, DateTimeZone displayZone, Locale locale) throws IOException { 1689 int year = getTwoDigitYear(instant, chrono); 1690 if (year < 0) { 1691 out.write('\ufffd'); 1692 out.write('\ufffd'); 1693 } else { 1694 FormatUtils.writePaddedInteger(out, year, 2); 1695 } 1696 } 1697 1698 private int getTwoDigitYear(long instant, Chronology chrono) { 1699 try { 1700 int year = iType.getField(chrono).get(instant); 1701 if (year < 0) { 1702 year = -year; 1703 } 1704 return year % 100; 1705 } catch (RuntimeException e) { 1706 return -1; 1707 } 1708 } 1709 1710 public void printTo(StringBuffer buf, ReadablePartial partial, Locale locale) { 1711 int year = getTwoDigitYear(partial); 1712 if (year < 0) { 1713 buf.append('\ufffd'); 1714 buf.append('\ufffd'); 1715 } else { 1716 FormatUtils.appendPaddedInteger(buf, year, 2); 1717 } 1718 } 1719 1720 public void printTo(Writer out, ReadablePartial partial, Locale locale) throws IOException { 1721 int year = getTwoDigitYear(partial); 1722 if (year < 0) { 1723 out.write('\ufffd'); 1724 out.write('\ufffd'); 1725 } else { 1726 FormatUtils.writePaddedInteger(out, year, 2); 1727 } 1728 } 1729 1730 private int getTwoDigitYear(ReadablePartial partial) { 1731 if (partial.isSupported(iType)) { 1732 try { 1733 int year = partial.get(iType); 1734 if (year < 0) { 1735 year = -year; 1736 } 1737 return year % 100; 1738 } catch (RuntimeException e) {} 1739 } 1740 return -1; 1741 } 1742 } 1743 1744 //----------------------------------------------------------------------- 1745 static class TextField 1746 implements DateTimePrinter, DateTimeParser { 1747 1748 private static Map<Locale, Map<DateTimeFieldType, Object[]>> cParseCache = 1749 new HashMap<Locale, Map<DateTimeFieldType, Object[]>>(); 1750 private final DateTimeFieldType iFieldType; 1751 private final boolean iShort; 1752 1753 TextField(DateTimeFieldType fieldType, boolean isShort) { 1754 super(); 1755 iFieldType = fieldType; 1756 iShort = isShort; 1757 } 1758 1759 public int estimatePrintedLength() { 1760 return iShort ? 6 : 20; 1761 } 1762 1763 public void printTo( 1764 StringBuffer buf, long instant, Chronology chrono, 1765 int displayOffset, DateTimeZone displayZone, Locale locale) { 1766 try { 1767 buf.append(print(instant, chrono, locale)); 1768 } catch (RuntimeException e) { 1769 buf.append('\ufffd'); 1770 } 1771 } 1772 1773 public void printTo( 1774 Writer out, long instant, Chronology chrono, 1775 int displayOffset, DateTimeZone displayZone, Locale locale) throws IOException { 1776 try { 1777 out.write(print(instant, chrono, locale)); 1778 } catch (RuntimeException e) { 1779 out.write('\ufffd'); 1780 } 1781 } 1782 1783 public void printTo(StringBuffer buf, ReadablePartial partial, Locale locale) { 1784 try { 1785 buf.append(print(partial, locale)); 1786 } catch (RuntimeException e) { 1787 buf.append('\ufffd'); 1788 } 1789 } 1790 1791 public void printTo(Writer out, ReadablePartial partial, Locale locale) throws IOException { 1792 try { 1793 out.write(print(partial, locale)); 1794 } catch (RuntimeException e) { 1795 out.write('\ufffd'); 1796 } 1797 } 1798 1799 private String print(long instant, Chronology chrono, Locale locale) { 1800 DateTimeField field = iFieldType.getField(chrono); 1801 if (iShort) { 1802 return field.getAsShortText(instant, locale); 1803 } else { 1804 return field.getAsText(instant, locale); 1805 } 1806 } 1807 1808 private String print(ReadablePartial partial, Locale locale) { 1809 if (partial.isSupported(iFieldType)) { 1810 DateTimeField field = iFieldType.getField(partial.getChronology()); 1811 if (iShort) { 1812 return field.getAsShortText(partial, locale); 1813 } else { 1814 return field.getAsText(partial, locale); 1815 } 1816 } else { 1817 return "\ufffd"; 1818 } 1819 } 1820 1821 public int estimateParsedLength() { 1822 return estimatePrintedLength(); 1823 } 1824 1825 @SuppressWarnings("unchecked") 1826 public int parseInto(DateTimeParserBucket bucket, String text, int position) { 1827 Locale locale = bucket.getLocale(); 1828 // handle languages which might have non ASCII A-Z or punctuation 1829 // bug 1788282 1830 Set<String> validValues = null; 1831 int maxLength = 0; 1832 synchronized (cParseCache) { 1833 Map<DateTimeFieldType, Object[]> innerMap = cParseCache.get(locale); 1834 if (innerMap == null) { 1835 innerMap = new HashMap<DateTimeFieldType, Object[]>(); 1836 cParseCache.put(locale, innerMap); 1837 } 1838 Object[] array = innerMap.get(iFieldType); 1839 if (array == null) { 1840 validValues = new HashSet<String>(32); 1841 MutableDateTime dt = new MutableDateTime(0L, DateTimeZone.UTC); 1842 Property property = dt.property(iFieldType); 1843 int min = property.getMinimumValueOverall(); 1844 int max = property.getMaximumValueOverall(); 1845 if (max - min > 32) { // protect against invalid fields 1846 return ~position; 1847 } 1848 maxLength = property.getMaximumTextLength(locale); 1849 for (int i = min; i <= max; i++) { 1850 property.set(i); 1851 validValues.add(property.getAsShortText(locale)); 1852 validValues.add(property.getAsShortText(locale).toLowerCase(locale)); 1853 validValues.add(property.getAsShortText(locale).toUpperCase(locale)); 1854 validValues.add(property.getAsText(locale)); 1855 validValues.add(property.getAsText(locale).toLowerCase(locale)); 1856 validValues.add(property.getAsText(locale).toUpperCase(locale)); 1857 } 1858 if ("en".equals(locale.getLanguage()) && iFieldType == DateTimeFieldType.era()) { 1859 // hack to support for parsing "BCE" and "CE" if the language is English 1860 validValues.add("BCE"); 1861 validValues.add("bce"); 1862 validValues.add("CE"); 1863 validValues.add("ce"); 1864 maxLength = 3; 1865 } 1866 array = new Object[] {validValues, Integer.valueOf(maxLength)}; 1867 innerMap.put(iFieldType, array); 1868 } else { 1869 validValues = (Set<String>) array[0]; 1870 maxLength = ((Integer) array[1]).intValue(); 1871 } 1872 } 1873 // match the longest string first using our knowledge of the max length 1874 int limit = Math.min(text.length(), position + maxLength); 1875 for (int i = limit; i > position; i--) { 1876 String match = text.substring(position, i); 1877 if (validValues.contains(match)) { 1878 bucket.saveField(iFieldType, match, locale); 1879 return i; 1880 } 1881 } 1882 return ~position; 1883 } 1884 } 1885 1886 //----------------------------------------------------------------------- 1887 static class Fraction 1888 implements DateTimePrinter, DateTimeParser { 1889 1890 private final DateTimeFieldType iFieldType; 1891 protected int iMinDigits; 1892 protected int iMaxDigits; 1893 1894 protected Fraction(DateTimeFieldType fieldType, int minDigits, int maxDigits) { 1895 super(); 1896 iFieldType = fieldType; 1897 // Limit the precision requirements. 1898 if (maxDigits > 18) { 1899 maxDigits = 18; 1900 } 1901 iMinDigits = minDigits; 1902 iMaxDigits = maxDigits; 1903 } 1904 1905 public int estimatePrintedLength() { 1906 return iMaxDigits; 1907 } 1908 1909 public void printTo( 1910 StringBuffer buf, long instant, Chronology chrono, 1911 int displayOffset, DateTimeZone displayZone, Locale locale) { 1912 try { 1913 printTo(buf, null, instant, chrono); 1914 } catch (IOException e) { 1915 // Not gonna happen. 1916 } 1917 } 1918 1919 public void printTo( 1920 Writer out, long instant, Chronology chrono, 1921 int displayOffset, DateTimeZone displayZone, Locale locale) throws IOException { 1922 printTo(null, out, instant, chrono); 1923 } 1924 1925 public void printTo(StringBuffer buf, ReadablePartial partial, Locale locale) { 1926 // removed check whether field is supported, as input field is typically 1927 // secondOfDay which is unsupported by TimeOfDay 1928 long millis = partial.getChronology().set(partial, 0L); 1929 try { 1930 printTo(buf, null, millis, partial.getChronology()); 1931 } catch (IOException e) { 1932 // Not gonna happen. 1933 } 1934 } 1935 1936 public void printTo(Writer out, ReadablePartial partial, Locale locale) throws IOException { 1937 // removed check whether field is supported, as input field is typically 1938 // secondOfDay which is unsupported by TimeOfDay 1939 long millis = partial.getChronology().set(partial, 0L); 1940 printTo(null, out, millis, partial.getChronology()); 1941 } 1942 1943 protected void printTo(StringBuffer buf, Writer out, long instant, Chronology chrono) 1944 throws IOException 1945 { 1946 DateTimeField field = iFieldType.getField(chrono); 1947 int minDigits = iMinDigits; 1948 1949 long fraction; 1950 try { 1951 fraction = field.remainder(instant); 1952 } catch (RuntimeException e) { 1953 if (buf != null) { 1954 appendUnknownString(buf, minDigits); 1955 } else { 1956 printUnknownString(out, minDigits); 1957 } 1958 return; 1959 } 1960 1961 if (fraction == 0) { 1962 if (buf != null) { 1963 while (--minDigits >= 0) { 1964 buf.append('0'); 1965 } 1966 } else { 1967 while (--minDigits >= 0) { 1968 out.write('0'); 1969 } 1970 } 1971 return; 1972 } 1973 1974 String str; 1975 long[] fractionData = getFractionData(fraction, field); 1976 long scaled = fractionData[0]; 1977 int maxDigits = (int) fractionData[1]; 1978 1979 if ((scaled & 0x7fffffff) == scaled) { 1980 str = Integer.toString((int) scaled); 1981 } else { 1982 str = Long.toString(scaled); 1983 } 1984 1985 int length = str.length(); 1986 int digits = maxDigits; 1987 while (length < digits) { 1988 if (buf != null) { 1989 buf.append('0'); 1990 } else { 1991 out.write('0'); 1992 } 1993 minDigits--; 1994 digits--; 1995 } 1996 1997 if (minDigits < digits) { 1998 // Chop off as many trailing zero digits as necessary. 1999 while (minDigits < digits) { 2000 if (length <= 1 || str.charAt(length - 1) != '0') { 2001 break; 2002 } 2003 digits--; 2004 length--; 2005 } 2006 if (length < str.length()) { 2007 if (buf != null) { 2008 for (int i=0; i<length; i++) { 2009 buf.append(str.charAt(i)); 2010 } 2011 } else { 2012 for (int i=0; i<length; i++) { 2013 out.write(str.charAt(i)); 2014 } 2015 } 2016 return; 2017 } 2018 } 2019 2020 if (buf != null) { 2021 buf.append(str); 2022 } else { 2023 out.write(str); 2024 } 2025 } 2026 2027 private long[] getFractionData(long fraction, DateTimeField field) { 2028 long rangeMillis = field.getDurationField().getUnitMillis(); 2029 long scalar; 2030 int maxDigits = iMaxDigits; 2031 while (true) { 2032 switch (maxDigits) { 2033 default: scalar = 1L; break; 2034 case 1: scalar = 10L; break; 2035 case 2: scalar = 100L; break; 2036 case 3: scalar = 1000L; break; 2037 case 4: scalar = 10000L; break; 2038 case 5: scalar = 100000L; break; 2039 case 6: scalar = 1000000L; break; 2040 case 7: scalar = 10000000L; break; 2041 case 8: scalar = 100000000L; break; 2042 case 9: scalar = 1000000000L; break; 2043 case 10: scalar = 10000000000L; break; 2044 case 11: scalar = 100000000000L; break; 2045 case 12: scalar = 1000000000000L; break; 2046 case 13: scalar = 10000000000000L; break; 2047 case 14: scalar = 100000000000000L; break; 2048 case 15: scalar = 1000000000000000L; break; 2049 case 16: scalar = 10000000000000000L; break; 2050 case 17: scalar = 100000000000000000L; break; 2051 case 18: scalar = 1000000000000000000L; break; 2052 } 2053 if (((rangeMillis * scalar) / scalar) == rangeMillis) { 2054 break; 2055 } 2056 // Overflowed: scale down. 2057 maxDigits--; 2058 } 2059 2060 return new long[] {fraction * scalar / rangeMillis, maxDigits}; 2061 } 2062 2063 public int estimateParsedLength() { 2064 return iMaxDigits; 2065 } 2066 2067 public int parseInto(DateTimeParserBucket bucket, String text, int position) { 2068 DateTimeField field = iFieldType.getField(bucket.getChronology()); 2069 2070 int limit = Math.min(iMaxDigits, text.length() - position); 2071 2072 long value = 0; 2073 long n = field.getDurationField().getUnitMillis() * 10; 2074 int length = 0; 2075 while (length < limit) { 2076 char c = text.charAt(position + length); 2077 if (c < '0' || c > '9') { 2078 break; 2079 } 2080 length++; 2081 long nn = n / 10; 2082 value += (c - '0') * nn; 2083 n = nn; 2084 } 2085 2086 value /= 10; 2087 2088 if (length == 0) { 2089 return ~position; 2090 } 2091 2092 if (value > Integer.MAX_VALUE) { 2093 return ~position; 2094 } 2095 2096 DateTimeField parseField = new PreciseDateTimeField( 2097 DateTimeFieldType.millisOfSecond(), 2098 MillisDurationField.INSTANCE, 2099 field.getDurationField()); 2100 2101 bucket.saveField(parseField, (int) value); 2102 2103 return position + length; 2104 } 2105 } 2106 2107 //----------------------------------------------------------------------- 2108 static class TimeZoneOffset 2109 implements DateTimePrinter, DateTimeParser { 2110 2111 private final String iZeroOffsetPrintText; 2112 private final String iZeroOffsetParseText; 2113 private final boolean iShowSeparators; 2114 private final int iMinFields; 2115 private final int iMaxFields; 2116 2117 TimeZoneOffset(String zeroOffsetPrintText, String zeroOffsetParseText, 2118 boolean showSeparators, 2119 int minFields, int maxFields) 2120 { 2121 super(); 2122 iZeroOffsetPrintText = zeroOffsetPrintText; 2123 iZeroOffsetParseText = zeroOffsetParseText; 2124 iShowSeparators = showSeparators; 2125 if (minFields <= 0 || maxFields < minFields) { 2126 throw new IllegalArgumentException(); 2127 } 2128 if (minFields > 4) { 2129 minFields = 4; 2130 maxFields = 4; 2131 } 2132 iMinFields = minFields; 2133 iMaxFields = maxFields; 2134 } 2135 2136 public int estimatePrintedLength() { 2137 int est = 1 + iMinFields << 1; 2138 if (iShowSeparators) { 2139 est += iMinFields - 1; 2140 } 2141 if (iZeroOffsetPrintText != null && iZeroOffsetPrintText.length() > est) { 2142 est = iZeroOffsetPrintText.length(); 2143 } 2144 return est; 2145 } 2146 2147 public void printTo( 2148 StringBuffer buf, long instant, Chronology chrono, 2149 int displayOffset, DateTimeZone displayZone, Locale locale) { 2150 if (displayZone == null) { 2151 return; // no zone 2152 } 2153 if (displayOffset == 0 && iZeroOffsetPrintText != null) { 2154 buf.append(iZeroOffsetPrintText); 2155 return; 2156 } 2157 if (displayOffset >= 0) { 2158 buf.append('+'); 2159 } else { 2160 buf.append('-'); 2161 displayOffset = -displayOffset; 2162 } 2163 2164 int hours = displayOffset / DateTimeConstants.MILLIS_PER_HOUR; 2165 FormatUtils.appendPaddedInteger(buf, hours, 2); 2166 if (iMaxFields == 1) { 2167 return; 2168 } 2169 displayOffset -= hours * (int)DateTimeConstants.MILLIS_PER_HOUR; 2170 if (displayOffset == 0 && iMinFields <= 1) { 2171 return; 2172 } 2173 2174 int minutes = displayOffset / DateTimeConstants.MILLIS_PER_MINUTE; 2175 if (iShowSeparators) { 2176 buf.append(':'); 2177 } 2178 FormatUtils.appendPaddedInteger(buf, minutes, 2); 2179 if (iMaxFields == 2) { 2180 return; 2181 } 2182 displayOffset -= minutes * DateTimeConstants.MILLIS_PER_MINUTE; 2183 if (displayOffset == 0 && iMinFields <= 2) { 2184 return; 2185 } 2186 2187 int seconds = displayOffset / DateTimeConstants.MILLIS_PER_SECOND; 2188 if (iShowSeparators) { 2189 buf.append(':'); 2190 } 2191 FormatUtils.appendPaddedInteger(buf, seconds, 2); 2192 if (iMaxFields == 3) { 2193 return; 2194 } 2195 displayOffset -= seconds * DateTimeConstants.MILLIS_PER_SECOND; 2196 if (displayOffset == 0 && iMinFields <= 3) { 2197 return; 2198 } 2199 2200 if (iShowSeparators) { 2201 buf.append('.'); 2202 } 2203 FormatUtils.appendPaddedInteger(buf, displayOffset, 3); 2204 } 2205 2206 public void printTo( 2207 Writer out, long instant, Chronology chrono, 2208 int displayOffset, DateTimeZone displayZone, Locale locale) throws IOException { 2209 if (displayZone == null) { 2210 return; // no zone 2211 } 2212 if (displayOffset == 0 && iZeroOffsetPrintText != null) { 2213 out.write(iZeroOffsetPrintText); 2214 return; 2215 } 2216 if (displayOffset >= 0) { 2217 out.write('+'); 2218 } else { 2219 out.write('-'); 2220 displayOffset = -displayOffset; 2221 } 2222 2223 int hours = displayOffset / DateTimeConstants.MILLIS_PER_HOUR; 2224 FormatUtils.writePaddedInteger(out, hours, 2); 2225 if (iMaxFields == 1) { 2226 return; 2227 } 2228 displayOffset -= hours * (int)DateTimeConstants.MILLIS_PER_HOUR; 2229 if (displayOffset == 0 && iMinFields == 1) { 2230 return; 2231 } 2232 2233 int minutes = displayOffset / DateTimeConstants.MILLIS_PER_MINUTE; 2234 if (iShowSeparators) { 2235 out.write(':'); 2236 } 2237 FormatUtils.writePaddedInteger(out, minutes, 2); 2238 if (iMaxFields == 2) { 2239 return; 2240 } 2241 displayOffset -= minutes * DateTimeConstants.MILLIS_PER_MINUTE; 2242 if (displayOffset == 0 && iMinFields == 2) { 2243 return; 2244 } 2245 2246 int seconds = displayOffset / DateTimeConstants.MILLIS_PER_SECOND; 2247 if (iShowSeparators) { 2248 out.write(':'); 2249 } 2250 FormatUtils.writePaddedInteger(out, seconds, 2); 2251 if (iMaxFields == 3) { 2252 return; 2253 } 2254 displayOffset -= seconds * DateTimeConstants.MILLIS_PER_SECOND; 2255 if (displayOffset == 0 && iMinFields == 3) { 2256 return; 2257 } 2258 2259 if (iShowSeparators) { 2260 out.write('.'); 2261 } 2262 FormatUtils.writePaddedInteger(out, displayOffset, 3); 2263 } 2264 2265 public void printTo(StringBuffer buf, ReadablePartial partial, Locale locale) { 2266 // no zone info 2267 } 2268 2269 public void printTo(Writer out, ReadablePartial partial, Locale locale) throws IOException { 2270 // no zone info 2271 } 2272 2273 public int estimateParsedLength() { 2274 return estimatePrintedLength(); 2275 } 2276 2277 public int parseInto(DateTimeParserBucket bucket, String text, int position) { 2278 int limit = text.length() - position; 2279 2280 zeroOffset: 2281 if (iZeroOffsetParseText != null) { 2282 if (iZeroOffsetParseText.length() == 0) { 2283 // Peek ahead, looking for sign character. 2284 if (limit > 0) { 2285 char c = text.charAt(position); 2286 if (c == '-' || c == '+') { 2287 break zeroOffset; 2288 } 2289 } 2290 bucket.setOffset(Integer.valueOf(0)); 2291 return position; 2292 } 2293 if (text.regionMatches(true, position, iZeroOffsetParseText, 0, iZeroOffsetParseText.length())) { 2294 bucket.setOffset(Integer.valueOf(0)); 2295 return position + iZeroOffsetParseText.length(); 2296 } 2297 } 2298 2299 // Format to expect is sign character followed by at least one digit. 2300 2301 if (limit <= 1) { 2302 return ~position; 2303 } 2304 2305 boolean negative; 2306 char c = text.charAt(position); 2307 if (c == '-') { 2308 negative = true; 2309 } else if (c == '+') { 2310 negative = false; 2311 } else { 2312 return ~position; 2313 } 2314 2315 limit--; 2316 position++; 2317 2318 // Format following sign is one of: 2319 // 2320 // hh 2321 // hhmm 2322 // hhmmss 2323 // hhmmssSSS 2324 // hh:mm 2325 // hh:mm:ss 2326 // hh:mm:ss.SSS 2327 2328 // First parse hours. 2329 2330 if (digitCount(text, position, 2) < 2) { 2331 // Need two digits for hour. 2332 return ~position; 2333 } 2334 2335 int offset; 2336 2337 int hours = FormatUtils.parseTwoDigits(text, position); 2338 if (hours > 23) { 2339 return ~position; 2340 } 2341 offset = hours * DateTimeConstants.MILLIS_PER_HOUR; 2342 limit -= 2; 2343 position += 2; 2344 2345 parse: { 2346 // Need to decide now if separators are expected or parsing 2347 // stops at hour field. 2348 2349 if (limit <= 0) { 2350 break parse; 2351 } 2352 2353 boolean expectSeparators; 2354 c = text.charAt(position); 2355 if (c == ':') { 2356 expectSeparators = true; 2357 limit--; 2358 position++; 2359 } else if (c >= '0' && c <= '9') { 2360 expectSeparators = false; 2361 } else { 2362 break parse; 2363 } 2364 2365 // Proceed to parse minutes. 2366 2367 int count = digitCount(text, position, 2); 2368 if (count == 0 && !expectSeparators) { 2369 break parse; 2370 } else if (count < 2) { 2371 // Need two digits for minute. 2372 return ~position; 2373 } 2374 2375 int minutes = FormatUtils.parseTwoDigits(text, position); 2376 if (minutes > 59) { 2377 return ~position; 2378 } 2379 offset += minutes * DateTimeConstants.MILLIS_PER_MINUTE; 2380 limit -= 2; 2381 position += 2; 2382 2383 // Proceed to parse seconds. 2384 2385 if (limit <= 0) { 2386 break parse; 2387 } 2388 2389 if (expectSeparators) { 2390 if (text.charAt(position) != ':') { 2391 break parse; 2392 } 2393 limit--; 2394 position++; 2395 } 2396 2397 count = digitCount(text, position, 2); 2398 if (count == 0 && !expectSeparators) { 2399 break parse; 2400 } else if (count < 2) { 2401 // Need two digits for second. 2402 return ~position; 2403 } 2404 2405 int seconds = FormatUtils.parseTwoDigits(text, position); 2406 if (seconds > 59) { 2407 return ~position; 2408 } 2409 offset += seconds * DateTimeConstants.MILLIS_PER_SECOND; 2410 limit -= 2; 2411 position += 2; 2412 2413 // Proceed to parse fraction of second. 2414 2415 if (limit <= 0) { 2416 break parse; 2417 } 2418 2419 if (expectSeparators) { 2420 if (text.charAt(position) != '.' && text.charAt(position) != ',') { 2421 break parse; 2422 } 2423 limit--; 2424 position++; 2425 } 2426 2427 count = digitCount(text, position, 3); 2428 if (count == 0 && !expectSeparators) { 2429 break parse; 2430 } else if (count < 1) { 2431 // Need at least one digit for fraction of second. 2432 return ~position; 2433 } 2434 2435 offset += (text.charAt(position++) - '0') * 100; 2436 if (count > 1) { 2437 offset += (text.charAt(position++) - '0') * 10; 2438 if (count > 2) { 2439 offset += text.charAt(position++) - '0'; 2440 } 2441 } 2442 } 2443 2444 bucket.setOffset(Integer.valueOf(negative ? -offset : offset)); 2445 return position; 2446 } 2447 2448 /** 2449 * Returns actual amount of digits to parse, but no more than original 2450 * 'amount' parameter. 2451 */ 2452 private int digitCount(String text, int position, int amount) { 2453 int limit = Math.min(text.length() - position, amount); 2454 amount = 0; 2455 for (; limit > 0; limit--) { 2456 char c = text.charAt(position + amount); 2457 if (c < '0' || c > '9') { 2458 break; 2459 } 2460 amount++; 2461 } 2462 return amount; 2463 } 2464 } 2465 2466 //----------------------------------------------------------------------- 2467 static class TimeZoneName 2468 implements DateTimePrinter, DateTimeParser { 2469 2470 static final int LONG_NAME = 0; 2471 static final int SHORT_NAME = 1; 2472 2473 private final Map<String, DateTimeZone> iParseLookup; 2474 private final int iType; 2475 2476 TimeZoneName(int type, Map<String, DateTimeZone> parseLookup) { 2477 super(); 2478 iType = type; 2479 iParseLookup = parseLookup; 2480 } 2481 2482 public int estimatePrintedLength() { 2483 return (iType == SHORT_NAME ? 4 : 20); 2484 } 2485 2486 public void printTo( 2487 StringBuffer buf, long instant, Chronology chrono, 2488 int displayOffset, DateTimeZone displayZone, Locale locale) { 2489 buf.append(print(instant - displayOffset, displayZone, locale)); 2490 } 2491 2492 public void printTo( 2493 Writer out, long instant, Chronology chrono, 2494 int displayOffset, DateTimeZone displayZone, Locale locale) throws IOException { 2495 out.write(print(instant - displayOffset, displayZone, locale)); 2496 } 2497 2498 private String print(long instant, DateTimeZone displayZone, Locale locale) { 2499 if (displayZone == null) { 2500 return ""; // no zone 2501 } 2502 switch (iType) { 2503 case LONG_NAME: 2504 return displayZone.getName(instant, locale); 2505 case SHORT_NAME: 2506 return displayZone.getShortName(instant, locale); 2507 } 2508 return ""; 2509 } 2510 2511 public void printTo(StringBuffer buf, ReadablePartial partial, Locale locale) { 2512 // no zone info 2513 } 2514 2515 public void printTo(Writer out, ReadablePartial partial, Locale locale) throws IOException { 2516 // no zone info 2517 } 2518 2519 public int estimateParsedLength() { 2520 return (iType == SHORT_NAME ? 4 : 20); 2521 } 2522 2523 public int parseInto(DateTimeParserBucket bucket, String text, int position) { 2524 Map<String, DateTimeZone> parseLookup = iParseLookup; 2525 parseLookup = (parseLookup != null ? parseLookup : DateTimeUtils.getDefaultTimeZoneNames()); 2526 String str = text.substring(position); 2527 for (String name : parseLookup.keySet()) { 2528 if (str.startsWith(name)) { 2529 bucket.setZone(parseLookup.get(name)); 2530 return position + name.length(); 2531 } 2532 } 2533 return ~position; 2534 } 2535 } 2536 2537 //----------------------------------------------------------------------- 2538 static enum TimeZoneId 2539 implements DateTimePrinter, DateTimeParser { 2540 2541 INSTANCE; 2542 static final Set<String> ALL_IDS = DateTimeZone.getAvailableIDs(); 2543 static final int MAX_LENGTH; 2544 static { 2545 int max = 0; 2546 for (String id : ALL_IDS) { 2547 max = Math.max(max, id.length()); 2548 } 2549 MAX_LENGTH = max; 2550 } 2551 2552 public int estimatePrintedLength() { 2553 return MAX_LENGTH; 2554 } 2555 2556 public void printTo( 2557 StringBuffer buf, long instant, Chronology chrono, 2558 int displayOffset, DateTimeZone displayZone, Locale locale) { 2559 buf.append(displayZone != null ? displayZone.getID() : ""); 2560 } 2561 2562 public void printTo( 2563 Writer out, long instant, Chronology chrono, 2564 int displayOffset, DateTimeZone displayZone, Locale locale) throws IOException { 2565 out.write(displayZone != null ? displayZone.getID() : ""); 2566 } 2567 2568 public void printTo(StringBuffer buf, ReadablePartial partial, Locale locale) { 2569 // no zone info 2570 } 2571 2572 public void printTo(Writer out, ReadablePartial partial, Locale locale) throws IOException { 2573 // no zone info 2574 } 2575 2576 public int estimateParsedLength() { 2577 return MAX_LENGTH; 2578 } 2579 2580 public int parseInto(DateTimeParserBucket bucket, String text, int position) { 2581 String str = text.substring(position); 2582 String best = null; 2583 for (String id : ALL_IDS) { 2584 if (str.startsWith(id)) { 2585 if (best == null || id.length() > best.length()) { 2586 best = id; 2587 } 2588 } 2589 } 2590 if (best != null) { 2591 bucket.setZone(DateTimeZone.forID(best)); 2592 return position + best.length(); 2593 } 2594 return ~position; 2595 } 2596 } 2597 2598 //----------------------------------------------------------------------- 2599 static class Composite 2600 implements DateTimePrinter, DateTimeParser { 2601 2602 private final DateTimePrinter[] iPrinters; 2603 private final DateTimeParser[] iParsers; 2604 2605 private final int iPrintedLengthEstimate; 2606 private final int iParsedLengthEstimate; 2607 2608 Composite(List<Object> elementPairs) { 2609 super(); 2610 2611 List<Object> printerList = new ArrayList<Object>(); 2612 List<Object> parserList = new ArrayList<Object>(); 2613 2614 decompose(elementPairs, printerList, parserList); 2615 2616 if (printerList.contains(null) || printerList.isEmpty()) { 2617 iPrinters = null; 2618 iPrintedLengthEstimate = 0; 2619 } else { 2620 int size = printerList.size(); 2621 iPrinters = new DateTimePrinter[size]; 2622 int printEst = 0; 2623 for (int i=0; i<size; i++) { 2624 DateTimePrinter printer = (DateTimePrinter) printerList.get(i); 2625 printEst += printer.estimatePrintedLength(); 2626 iPrinters[i] = printer; 2627 } 2628 iPrintedLengthEstimate = printEst; 2629 } 2630 2631 if (parserList.contains(null) || parserList.isEmpty()) { 2632 iParsers = null; 2633 iParsedLengthEstimate = 0; 2634 } else { 2635 int size = parserList.size(); 2636 iParsers = new DateTimeParser[size]; 2637 int parseEst = 0; 2638 for (int i=0; i<size; i++) { 2639 DateTimeParser parser = (DateTimeParser) parserList.get(i); 2640 parseEst += parser.estimateParsedLength(); 2641 iParsers[i] = parser; 2642 } 2643 iParsedLengthEstimate = parseEst; 2644 } 2645 } 2646 2647 public int estimatePrintedLength() { 2648 return iPrintedLengthEstimate; 2649 } 2650 2651 public void printTo( 2652 StringBuffer buf, long instant, Chronology chrono, 2653 int displayOffset, DateTimeZone displayZone, Locale locale) { 2654 DateTimePrinter[] elements = iPrinters; 2655 if (elements == null) { 2656 throw new UnsupportedOperationException(); 2657 } 2658 2659 if (locale == null) { 2660 // Guard against default locale changing concurrently. 2661 locale = Locale.getDefault(); 2662 } 2663 2664 int len = elements.length; 2665 for (int i = 0; i < len; i++) { 2666 elements[i].printTo(buf, instant, chrono, displayOffset, displayZone, locale); 2667 } 2668 } 2669 2670 public void printTo( 2671 Writer out, long instant, Chronology chrono, 2672 int displayOffset, DateTimeZone displayZone, Locale locale) throws IOException { 2673 DateTimePrinter[] elements = iPrinters; 2674 if (elements == null) { 2675 throw new UnsupportedOperationException(); 2676 } 2677 2678 if (locale == null) { 2679 // Guard against default locale changing concurrently. 2680 locale = Locale.getDefault(); 2681 } 2682 2683 int len = elements.length; 2684 for (int i = 0; i < len; i++) { 2685 elements[i].printTo(out, instant, chrono, displayOffset, displayZone, locale); 2686 } 2687 } 2688 2689 public void printTo(StringBuffer buf, ReadablePartial partial, Locale locale) { 2690 DateTimePrinter[] elements = iPrinters; 2691 if (elements == null) { 2692 throw new UnsupportedOperationException(); 2693 } 2694 2695 if (locale == null) { 2696 // Guard against default locale changing concurrently. 2697 locale = Locale.getDefault(); 2698 } 2699 2700 int len = elements.length; 2701 for (int i=0; i<len; i++) { 2702 elements[i].printTo(buf, partial, locale); 2703 } 2704 } 2705 2706 public void printTo(Writer out, ReadablePartial partial, Locale locale) throws IOException { 2707 DateTimePrinter[] elements = iPrinters; 2708 if (elements == null) { 2709 throw new UnsupportedOperationException(); 2710 } 2711 2712 if (locale == null) { 2713 // Guard against default locale changing concurrently. 2714 locale = Locale.getDefault(); 2715 } 2716 2717 int len = elements.length; 2718 for (int i=0; i<len; i++) { 2719 elements[i].printTo(out, partial, locale); 2720 } 2721 } 2722 2723 public int estimateParsedLength() { 2724 return iParsedLengthEstimate; 2725 } 2726 2727 public int parseInto(DateTimeParserBucket bucket, String text, int position) { 2728 DateTimeParser[] elements = iParsers; 2729 if (elements == null) { 2730 throw new UnsupportedOperationException(); 2731 } 2732 2733 int len = elements.length; 2734 for (int i=0; i<len && position >= 0; i++) { 2735 position = elements[i].parseInto(bucket, text, position); 2736 } 2737 return position; 2738 } 2739 2740 boolean isPrinter() { 2741 return iPrinters != null; 2742 } 2743 2744 boolean isParser() { 2745 return iParsers != null; 2746 } 2747 2748 /** 2749 * Processes the element pairs, putting results into the given printer 2750 * and parser lists. 2751 */ 2752 private void decompose(List<Object> elementPairs, List<Object> printerList, List<Object> parserList) { 2753 int size = elementPairs.size(); 2754 for (int i=0; i<size; i+=2) { 2755 Object element = elementPairs.get(i); 2756 if (element instanceof Composite) { 2757 addArrayToList(printerList, ((Composite)element).iPrinters); 2758 } else { 2759 printerList.add(element); 2760 } 2761 2762 element = elementPairs.get(i + 1); 2763 if (element instanceof Composite) { 2764 addArrayToList(parserList, ((Composite)element).iParsers); 2765 } else { 2766 parserList.add(element); 2767 } 2768 } 2769 } 2770 2771 private void addArrayToList(List<Object> list, Object[] array) { 2772 if (array != null) { 2773 for (int i=0; i<array.length; i++) { 2774 list.add(array[i]); 2775 } 2776 } 2777 } 2778 } 2779 2780 //----------------------------------------------------------------------- 2781 static class MatchingParser 2782 implements DateTimeParser { 2783 2784 private final DateTimeParser[] iParsers; 2785 private final int iParsedLengthEstimate; 2786 2787 MatchingParser(DateTimeParser[] parsers) { 2788 super(); 2789 iParsers = parsers; 2790 int est = 0; 2791 for (int i=parsers.length; --i>=0 ;) { 2792 DateTimeParser parser = parsers[i]; 2793 if (parser != null) { 2794 int len = parser.estimateParsedLength(); 2795 if (len > est) { 2796 est = len; 2797 } 2798 } 2799 } 2800 iParsedLengthEstimate = est; 2801 } 2802 2803 public int estimateParsedLength() { 2804 return iParsedLengthEstimate; 2805 } 2806 2807 public int parseInto(DateTimeParserBucket bucket, String text, int position) { 2808 DateTimeParser[] parsers = iParsers; 2809 int length = parsers.length; 2810 2811 final Object originalState = bucket.saveState(); 2812 boolean isOptional = false; 2813 2814 int bestValidPos = position; 2815 Object bestValidState = null; 2816 2817 int bestInvalidPos = position; 2818 2819 for (int i=0; i<length; i++) { 2820 DateTimeParser parser = parsers[i]; 2821 if (parser == null) { 2822 // The empty parser wins only if nothing is better. 2823 if (bestValidPos <= position) { 2824 return position; 2825 } 2826 isOptional = true; 2827 break; 2828 } 2829 int parsePos = parser.parseInto(bucket, text, position); 2830 if (parsePos >= position) { 2831 if (parsePos > bestValidPos) { 2832 if (parsePos >= text.length() || 2833 (i + 1) >= length || parsers[i + 1] == null) { 2834 2835 // Completely parsed text or no more parsers to 2836 // check. Skip the rest. 2837 return parsePos; 2838 } 2839 bestValidPos = parsePos; 2840 bestValidState = bucket.saveState(); 2841 } 2842 } else { 2843 if (parsePos < 0) { 2844 parsePos = ~parsePos; 2845 if (parsePos > bestInvalidPos) { 2846 bestInvalidPos = parsePos; 2847 } 2848 } 2849 } 2850 bucket.restoreState(originalState); 2851 } 2852 2853 if (bestValidPos > position || (bestValidPos == position && isOptional)) { 2854 // Restore the state to the best valid parse. 2855 if (bestValidState != null) { 2856 bucket.restoreState(bestValidState); 2857 } 2858 return bestValidPos; 2859 } 2860 2861 return ~bestInvalidPos; 2862 } 2863 } 2864 2865 }