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