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.DateTimeZone; 033 import org.joda.time.MutableDateTime; 034 import org.joda.time.ReadablePartial; 035 import org.joda.time.MutableDateTime.Property; 036 import org.joda.time.field.MillisDurationField; 037 import 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 */ 071 public class DateTimeFormatterBuilder { 072 073 /** Array of printers and parsers (alternating). */ 074 private ArrayList<Object> 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<Object>(); 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, for chaining 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, for chaining 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, for chaining 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, for chaining 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 addition, 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, for chaining 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, for chaining 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, for chaining 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, for chaining 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 minimum 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, for chaining 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, for chaining 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 minimum 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, for chaining 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, for chaining 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, for chaining 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, for chaining 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 * without 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 minimum number of digits to print. 526 * @param maxDigits maximum number of digits to print or parse. 527 * @return this DateTimeFormatterBuilder, for chaining 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 * Appends the print/parse of a fractional second. 546 * <p> 547 * This reliably handles the case where fractional digits are being handled 548 * beyond a visible decimal point. The digits parsed will always be treated 549 * as the most significant (numerically largest) digits. 550 * Thus '23' will be parsed as 230 milliseconds. 551 * Contrast this behaviour to {@link #appendMillisOfSecond}. 552 * This method does not print or parse the decimal point itself. 553 * 554 * @param minDigits minimum number of digits to print 555 * @param maxDigits maximum number of digits to print or parse 556 * @return this DateTimeFormatterBuilder, for chaining 557 */ 558 public DateTimeFormatterBuilder appendFractionOfSecond(int minDigits, int maxDigits) { 559 return appendFraction(DateTimeFieldType.secondOfDay(), minDigits, maxDigits); 560 } 561 562 /** 563 * Appends the print/parse of a fractional minute. 564 * <p> 565 * This reliably handles the case where fractional digits are being handled 566 * beyond a visible decimal point. The digits parsed will always be treated 567 * as the most significant (numerically largest) digits. 568 * Thus '23' will be parsed as 0.23 minutes (converted to milliseconds). 569 * This method does not print or parse the decimal point itself. 570 * 571 * @param minDigits minimum number of digits to print 572 * @param maxDigits maximum number of digits to print or parse 573 * @return this DateTimeFormatterBuilder, for chaining 574 */ 575 public DateTimeFormatterBuilder appendFractionOfMinute(int minDigits, int maxDigits) { 576 return appendFraction(DateTimeFieldType.minuteOfDay(), minDigits, maxDigits); 577 } 578 579 /** 580 * Appends the print/parse of a fractional hour. 581 * <p> 582 * This reliably handles the case where fractional digits are being handled 583 * beyond a visible decimal point. The digits parsed will always be treated 584 * as the most significant (numerically largest) digits. 585 * Thus '23' will be parsed as 0.23 hours (converted to milliseconds). 586 * This method does not print or parse the decimal point itself. 587 * 588 * @param minDigits minimum number of digits to print 589 * @param maxDigits maximum number of digits to print or parse 590 * @return this DateTimeFormatterBuilder, for chaining 591 */ 592 public DateTimeFormatterBuilder appendFractionOfHour(int minDigits, int maxDigits) { 593 return appendFraction(DateTimeFieldType.hourOfDay(), minDigits, maxDigits); 594 } 595 596 /** 597 * Appends the print/parse of a fractional day. 598 * <p> 599 * This reliably handles the case where fractional digits are being handled 600 * beyond a visible decimal point. The digits parsed will always be treated 601 * as the most significant (numerically largest) digits. 602 * Thus '23' will be parsed as 0.23 days (converted to milliseconds). 603 * This method does not print or parse the decimal point itself. 604 * 605 * @param minDigits minimum number of digits to print 606 * @param maxDigits maximum number of digits to print or parse 607 * @return this DateTimeFormatterBuilder, for chaining 608 */ 609 public DateTimeFormatterBuilder appendFractionOfDay(int minDigits, int maxDigits) { 610 return appendFraction(DateTimeFieldType.dayOfYear(), minDigits, maxDigits); 611 } 612 613 /** 614 * Instructs the printer to emit a numeric millisOfSecond field. 615 * <p> 616 * This method will append a field that prints a three digit value. 617 * During parsing the value that is parsed is assumed to be three digits. 618 * If less than three digits are present then they will be counted as the 619 * smallest parts of the millisecond. This is probably not what you want 620 * if you are using the field as a fraction. Instead, a fractional 621 * millisecond should be produced using {@link #appendFractionOfSecond}. 622 * 623 * @param minDigits minimum number of digits to print 624 * @return this DateTimeFormatterBuilder, for chaining 625 */ 626 public DateTimeFormatterBuilder appendMillisOfSecond(int minDigits) { 627 return appendDecimal(DateTimeFieldType.millisOfSecond(), minDigits, 3); 628 } 629 630 /** 631 * Instructs the printer to emit a numeric millisOfDay field. 632 * 633 * @param minDigits minimum number of digits to print 634 * @return this DateTimeFormatterBuilder, for chaining 635 */ 636 public DateTimeFormatterBuilder appendMillisOfDay(int minDigits) { 637 return appendDecimal(DateTimeFieldType.millisOfDay(), minDigits, 8); 638 } 639 640 /** 641 * Instructs the printer to emit a numeric secondOfMinute field. 642 * 643 * @param minDigits minimum number of digits to print 644 * @return this DateTimeFormatterBuilder, for chaining 645 */ 646 public DateTimeFormatterBuilder appendSecondOfMinute(int minDigits) { 647 return appendDecimal(DateTimeFieldType.secondOfMinute(), minDigits, 2); 648 } 649 650 /** 651 * Instructs the printer to emit a numeric secondOfDay field. 652 * 653 * @param minDigits minimum number of digits to print 654 * @return this DateTimeFormatterBuilder, for chaining 655 */ 656 public DateTimeFormatterBuilder appendSecondOfDay(int minDigits) { 657 return appendDecimal(DateTimeFieldType.secondOfDay(), minDigits, 5); 658 } 659 660 /** 661 * Instructs the printer to emit a numeric minuteOfHour field. 662 * 663 * @param minDigits minimum number of digits to print 664 * @return this DateTimeFormatterBuilder, for chaining 665 */ 666 public DateTimeFormatterBuilder appendMinuteOfHour(int minDigits) { 667 return appendDecimal(DateTimeFieldType.minuteOfHour(), minDigits, 2); 668 } 669 670 /** 671 * Instructs the printer to emit a numeric minuteOfDay field. 672 * 673 * @param minDigits minimum number of digits to print 674 * @return this DateTimeFormatterBuilder, for chaining 675 */ 676 public DateTimeFormatterBuilder appendMinuteOfDay(int minDigits) { 677 return appendDecimal(DateTimeFieldType.minuteOfDay(), minDigits, 4); 678 } 679 680 /** 681 * Instructs the printer to emit a numeric hourOfDay field. 682 * 683 * @param minDigits minimum number of digits to print 684 * @return this DateTimeFormatterBuilder, for chaining 685 */ 686 public DateTimeFormatterBuilder appendHourOfDay(int minDigits) { 687 return appendDecimal(DateTimeFieldType.hourOfDay(), minDigits, 2); 688 } 689 690 /** 691 * Instructs the printer to emit a numeric clockhourOfDay field. 692 * 693 * @param minDigits minimum number of digits to print 694 * @return this DateTimeFormatterBuilder, for chaining 695 */ 696 public DateTimeFormatterBuilder appendClockhourOfDay(int minDigits) { 697 return appendDecimal(DateTimeFieldType.clockhourOfDay(), minDigits, 2); 698 } 699 700 /** 701 * Instructs the printer to emit a numeric hourOfHalfday field. 702 * 703 * @param minDigits minimum number of digits to print 704 * @return this DateTimeFormatterBuilder, for chaining 705 */ 706 public DateTimeFormatterBuilder appendHourOfHalfday(int minDigits) { 707 return appendDecimal(DateTimeFieldType.hourOfHalfday(), minDigits, 2); 708 } 709 710 /** 711 * Instructs the printer to emit a numeric clockhourOfHalfday field. 712 * 713 * @param minDigits minimum number of digits to print 714 * @return this DateTimeFormatterBuilder, for chaining 715 */ 716 public DateTimeFormatterBuilder appendClockhourOfHalfday(int minDigits) { 717 return appendDecimal(DateTimeFieldType.clockhourOfHalfday(), minDigits, 2); 718 } 719 720 /** 721 * Instructs the printer to emit a numeric dayOfWeek field. 722 * 723 * @param minDigits minimum number of digits to print 724 * @return this DateTimeFormatterBuilder, for chaining 725 */ 726 public DateTimeFormatterBuilder appendDayOfWeek(int minDigits) { 727 return appendDecimal(DateTimeFieldType.dayOfWeek(), minDigits, 1); 728 } 729 730 /** 731 * Instructs the printer to emit a numeric dayOfMonth field. 732 * 733 * @param minDigits minimum number of digits to print 734 * @return this DateTimeFormatterBuilder, for chaining 735 */ 736 public DateTimeFormatterBuilder appendDayOfMonth(int minDigits) { 737 return appendDecimal(DateTimeFieldType.dayOfMonth(), minDigits, 2); 738 } 739 740 /** 741 * Instructs the printer to emit a numeric dayOfYear field. 742 * 743 * @param minDigits minimum number of digits to print 744 * @return this DateTimeFormatterBuilder, for chaining 745 */ 746 public DateTimeFormatterBuilder appendDayOfYear(int minDigits) { 747 return appendDecimal(DateTimeFieldType.dayOfYear(), minDigits, 3); 748 } 749 750 /** 751 * Instructs the printer to emit a numeric weekOfWeekyear field. 752 * 753 * @param minDigits minimum number of digits to print 754 * @return this DateTimeFormatterBuilder, for chaining 755 */ 756 public DateTimeFormatterBuilder appendWeekOfWeekyear(int minDigits) { 757 return appendDecimal(DateTimeFieldType.weekOfWeekyear(), minDigits, 2); 758 } 759 760 /** 761 * Instructs the printer to emit a numeric weekyear field. 762 * 763 * @param minDigits minimum number of digits to <i>print</i> 764 * @param maxDigits maximum number of digits to <i>parse</i>, or the estimated 765 * maximum number of digits to print 766 * @return this DateTimeFormatterBuilder, for chaining 767 */ 768 public DateTimeFormatterBuilder appendWeekyear(int minDigits, int maxDigits) { 769 return appendSignedDecimal(DateTimeFieldType.weekyear(), minDigits, maxDigits); 770 } 771 772 /** 773 * Instructs the printer to emit a numeric monthOfYear field. 774 * 775 * @param minDigits minimum number of digits to print 776 * @return this DateTimeFormatterBuilder, for chaining 777 */ 778 public DateTimeFormatterBuilder appendMonthOfYear(int minDigits) { 779 return appendDecimal(DateTimeFieldType.monthOfYear(), minDigits, 2); 780 } 781 782 /** 783 * Instructs the printer to emit a numeric year field. 784 * 785 * @param minDigits minimum number of digits to <i>print</i> 786 * @param maxDigits maximum number of digits to <i>parse</i>, or the estimated 787 * maximum number of digits to print 788 * @return this DateTimeFormatterBuilder, for chaining 789 */ 790 public DateTimeFormatterBuilder appendYear(int minDigits, int maxDigits) { 791 return appendSignedDecimal(DateTimeFieldType.year(), minDigits, maxDigits); 792 } 793 794 /** 795 * Instructs the printer to emit a numeric year field which always prints 796 * and parses two digits. A pivot year is used during parsing to determine 797 * the range of supported years as <code>(pivot - 50) .. (pivot + 49)</code>. 798 * 799 * <pre> 800 * pivot supported range 00 is 20 is 40 is 60 is 80 is 801 * --------------------------------------------------------------- 802 * 1950 1900..1999 1900 1920 1940 1960 1980 803 * 1975 1925..2024 2000 2020 1940 1960 1980 804 * 2000 1950..2049 2000 2020 2040 1960 1980 805 * 2025 1975..2074 2000 2020 2040 2060 1980 806 * 2050 2000..2099 2000 2020 2040 2060 2080 807 * </pre> 808 * 809 * @param pivot pivot year to use when parsing 810 * @return this DateTimeFormatterBuilder, for chaining 811 */ 812 public DateTimeFormatterBuilder appendTwoDigitYear(int pivot) { 813 return appendTwoDigitYear(pivot, false); 814 } 815 816 /** 817 * Instructs the printer to emit a numeric year field which always prints 818 * two digits. A pivot year is used during parsing to determine the range 819 * of supported years as <code>(pivot - 50) .. (pivot + 49)</code>. If 820 * parse is instructed to be lenient and the digit count is not two, it is 821 * treated as an absolute year. With lenient parsing, specifying a positive 822 * or negative sign before the year also makes it absolute. 823 * 824 * @param pivot pivot year to use when parsing 825 * @param lenientParse when true, if digit count is not two, it is treated 826 * as an absolute year 827 * @return this DateTimeFormatterBuilder, for chaining 828 * @since 1.1 829 */ 830 public DateTimeFormatterBuilder appendTwoDigitYear(int pivot, boolean lenientParse) { 831 return append0(new TwoDigitYear(DateTimeFieldType.year(), pivot, lenientParse)); 832 } 833 834 /** 835 * Instructs the printer to emit a numeric weekyear field which always prints 836 * and parses two digits. A pivot year is used during parsing to determine 837 * the range of supported years as <code>(pivot - 50) .. (pivot + 49)</code>. 838 * 839 * <pre> 840 * pivot supported range 00 is 20 is 40 is 60 is 80 is 841 * --------------------------------------------------------------- 842 * 1950 1900..1999 1900 1920 1940 1960 1980 843 * 1975 1925..2024 2000 2020 1940 1960 1980 844 * 2000 1950..2049 2000 2020 2040 1960 1980 845 * 2025 1975..2074 2000 2020 2040 2060 1980 846 * 2050 2000..2099 2000 2020 2040 2060 2080 847 * </pre> 848 * 849 * @param pivot pivot weekyear to use when parsing 850 * @return this DateTimeFormatterBuilder, for chaining 851 */ 852 public DateTimeFormatterBuilder appendTwoDigitWeekyear(int pivot) { 853 return appendTwoDigitWeekyear(pivot, false); 854 } 855 856 /** 857 * Instructs the printer to emit a numeric weekyear field which always prints 858 * two digits. A pivot year is used during parsing to determine the range 859 * of supported years as <code>(pivot - 50) .. (pivot + 49)</code>. If 860 * parse is instructed to be lenient and the digit count is not two, it is 861 * treated as an absolute weekyear. With lenient parsing, specifying a positive 862 * or negative sign before the weekyear also makes it absolute. 863 * 864 * @param pivot pivot weekyear to use when parsing 865 * @param lenientParse when true, if digit count is not two, it is treated 866 * as an absolute weekyear 867 * @return this DateTimeFormatterBuilder, for chaining 868 * @since 1.1 869 */ 870 public DateTimeFormatterBuilder appendTwoDigitWeekyear(int pivot, boolean lenientParse) { 871 return append0(new TwoDigitYear(DateTimeFieldType.weekyear(), pivot, lenientParse)); 872 } 873 874 /** 875 * Instructs the printer to emit a numeric yearOfEra field. 876 * 877 * @param minDigits minimum number of digits to <i>print</i> 878 * @param maxDigits maximum number of digits to <i>parse</i>, or the estimated 879 * maximum number of digits to print 880 * @return this DateTimeFormatterBuilder, for chaining 881 */ 882 public DateTimeFormatterBuilder appendYearOfEra(int minDigits, int maxDigits) { 883 return appendDecimal(DateTimeFieldType.yearOfEra(), minDigits, maxDigits); 884 } 885 886 /** 887 * Instructs the printer to emit a numeric year of century field. 888 * 889 * @param minDigits minimum number of digits to print 890 * @param maxDigits maximum number of digits to <i>parse</i>, or the estimated 891 * maximum number of digits to print 892 * @return this DateTimeFormatterBuilder, for chaining 893 */ 894 public DateTimeFormatterBuilder appendYearOfCentury(int minDigits, int maxDigits) { 895 return appendDecimal(DateTimeFieldType.yearOfCentury(), minDigits, maxDigits); 896 } 897 898 /** 899 * Instructs the printer to emit a numeric century of era field. 900 * 901 * @param minDigits minimum number of digits to print 902 * @param maxDigits maximum number of digits to <i>parse</i>, or the estimated 903 * maximum number of digits to print 904 * @return this DateTimeFormatterBuilder, for chaining 905 */ 906 public DateTimeFormatterBuilder appendCenturyOfEra(int minDigits, int maxDigits) { 907 return appendSignedDecimal(DateTimeFieldType.centuryOfEra(), minDigits, maxDigits); 908 } 909 910 /** 911 * Instructs the printer to emit a locale-specific AM/PM text, and the 912 * parser to expect it. The parser is case-insensitive. 913 * 914 * @return this DateTimeFormatterBuilder, for chaining 915 */ 916 public DateTimeFormatterBuilder appendHalfdayOfDayText() { 917 return appendText(DateTimeFieldType.halfdayOfDay()); 918 } 919 920 /** 921 * Instructs the printer to emit a locale-specific dayOfWeek text. The 922 * parser will accept a long or short dayOfWeek text, case-insensitive. 923 * 924 * @return this DateTimeFormatterBuilder, for chaining 925 */ 926 public DateTimeFormatterBuilder appendDayOfWeekText() { 927 return appendText(DateTimeFieldType.dayOfWeek()); 928 } 929 930 /** 931 * Instructs the printer to emit a short locale-specific dayOfWeek 932 * text. The parser will accept a long or short dayOfWeek text, 933 * case-insensitive. 934 * 935 * @return this DateTimeFormatterBuilder, for chaining 936 */ 937 public DateTimeFormatterBuilder appendDayOfWeekShortText() { 938 return appendShortText(DateTimeFieldType.dayOfWeek()); 939 } 940 941 /** 942 * Instructs the printer to emit a short locale-specific monthOfYear 943 * text. The parser will accept a long or short monthOfYear text, 944 * case-insensitive. 945 * 946 * @return this DateTimeFormatterBuilder, for chaining 947 */ 948 public DateTimeFormatterBuilder appendMonthOfYearText() { 949 return appendText(DateTimeFieldType.monthOfYear()); 950 } 951 952 /** 953 * Instructs the printer to emit a locale-specific monthOfYear text. The 954 * parser will accept a long or short monthOfYear text, case-insensitive. 955 * 956 * @return this DateTimeFormatterBuilder, for chaining 957 */ 958 public DateTimeFormatterBuilder appendMonthOfYearShortText() { 959 return appendShortText(DateTimeFieldType.monthOfYear()); 960 } 961 962 /** 963 * Instructs the printer to emit a locale-specific era text (BC/AD), and 964 * the parser to expect it. The parser is case-insensitive. 965 * 966 * @return this DateTimeFormatterBuilder, for chaining 967 */ 968 public DateTimeFormatterBuilder appendEraText() { 969 return appendText(DateTimeFieldType.era()); 970 } 971 972 /** 973 * Instructs the printer to emit a locale-specific time zone name. 974 * Using this method prevents parsing, because time zone names are not unique. 975 * See {@link #appendTimeZoneName(Map)}. 976 * 977 * @return this DateTimeFormatterBuilder, for chaining 978 */ 979 public DateTimeFormatterBuilder appendTimeZoneName() { 980 return append0(new TimeZoneName(TimeZoneName.LONG_NAME, null), null); 981 } 982 983 /** 984 * Instructs the printer to emit a locale-specific time zone name, providing a lookup for parsing. 985 * Time zone names are not unique, thus the API forces you to supply the lookup. 986 * The names are searched in the order of the map, thus it is strongly recommended 987 * to use a {@code LinkedHashMap} or similar. 988 * 989 * @param parseLookup the table of names, not null 990 * @return this DateTimeFormatterBuilder, for chaining 991 */ 992 public DateTimeFormatterBuilder appendTimeZoneName(Map<String, DateTimeZone> parseLookup) { 993 TimeZoneName pp = new TimeZoneName(TimeZoneName.LONG_NAME, parseLookup); 994 return append0(pp, pp); 995 } 996 997 /** 998 * Instructs the printer to emit a short locale-specific time zone name. 999 * Using this method prevents parsing, because time zone names are not unique. 1000 * See {@link #appendTimeZoneShortName(Map)}. 1001 * 1002 * @return this DateTimeFormatterBuilder, for chaining 1003 */ 1004 public DateTimeFormatterBuilder appendTimeZoneShortName() { 1005 return append0(new TimeZoneName(TimeZoneName.SHORT_NAME, null), null); 1006 } 1007 1008 /** 1009 * Instructs the printer to emit a short locale-specific time zone 1010 * name, providing a lookup for parsing. 1011 * Time zone names are not unique, thus the API forces you to supply the lookup. 1012 * The names are searched in the order of the map, thus it is strongly recommended 1013 * to use a {@code LinkedHashMap} or similar. 1014 * 1015 * @param parseLookup the table of names, not null 1016 * @return this DateTimeFormatterBuilder, for chaining 1017 */ 1018 public DateTimeFormatterBuilder appendTimeZoneShortName(Map<String, DateTimeZone> parseLookup) { 1019 TimeZoneName pp = new TimeZoneName(TimeZoneName.SHORT_NAME, parseLookup); 1020 return append0(pp, pp); 1021 } 1022 1023 /** 1024 * Instructs the printer to emit the identifier of the time zone. 1025 * From version 2.0, this field can be parsed. 1026 * 1027 * @return this DateTimeFormatterBuilder, for chaining 1028 */ 1029 public DateTimeFormatterBuilder appendTimeZoneId() { 1030 return append0(TimeZoneId.INSTANCE, TimeZoneId.INSTANCE); 1031 } 1032 1033 /** 1034 * Instructs the printer to emit text and numbers to display time zone 1035 * offset from UTC. A parser will use the parsed time zone offset to adjust 1036 * the datetime. 1037 * <p> 1038 * If zero offset text is supplied, then it will be printed when the zone is zero. 1039 * During parsing, either the zero offset text, or the offset will be parsed. 1040 * 1041 * @param zeroOffsetText the text to use if time zone offset is zero. If 1042 * null, offset is always shown. 1043 * @param showSeparators if true, prints ':' separator before minute and 1044 * second field and prints '.' separator before fraction field. 1045 * @param minFields minimum number of fields to print, stopping when no 1046 * more precision is required. 1=hours, 2=minutes, 3=seconds, 4=fraction 1047 * @param maxFields maximum number of fields to print 1048 * @return this DateTimeFormatterBuilder, for chaining 1049 */ 1050 public DateTimeFormatterBuilder appendTimeZoneOffset( 1051 String zeroOffsetText, boolean showSeparators, 1052 int minFields, int maxFields) { 1053 return append0(new TimeZoneOffset 1054 (zeroOffsetText, zeroOffsetText, showSeparators, minFields, maxFields)); 1055 } 1056 1057 /** 1058 * Instructs the printer to emit text and numbers to display time zone 1059 * offset from UTC. A parser will use the parsed time zone offset to adjust 1060 * the datetime. 1061 * <p> 1062 * If zero offset print text is supplied, then it will be printed when the zone is zero. 1063 * If zero offset parse text is supplied, then either it or the offset will be parsed. 1064 * 1065 * @param zeroOffsetPrintText the text to print if time zone offset is zero. If 1066 * null, offset is always shown. 1067 * @param zeroOffsetParseText the text to optionally parse to indicate that the time 1068 * zone offset is zero. If null, then always use the offset. 1069 * @param showSeparators if true, prints ':' separator before minute and 1070 * second field and prints '.' separator before fraction field. 1071 * @param minFields minimum number of fields to print, stopping when no 1072 * more precision is required. 1=hours, 2=minutes, 3=seconds, 4=fraction 1073 * @param maxFields maximum number of fields to print 1074 * @return this DateTimeFormatterBuilder, for chaining 1075 * @since 2.0 1076 */ 1077 public DateTimeFormatterBuilder appendTimeZoneOffset( 1078 String zeroOffsetPrintText, String zeroOffsetParseText, boolean showSeparators, 1079 int minFields, int maxFields) { 1080 return append0(new TimeZoneOffset 1081 (zeroOffsetPrintText, zeroOffsetParseText, showSeparators, minFields, maxFields)); 1082 } 1083 1084 //----------------------------------------------------------------------- 1085 /** 1086 * Calls upon {@link DateTimeFormat} to parse the pattern and append the 1087 * results into this builder. 1088 * 1089 * @param pattern pattern specification 1090 * @throws IllegalArgumentException if the pattern is invalid 1091 * @see DateTimeFormat 1092 */ 1093 public DateTimeFormatterBuilder appendPattern(String pattern) { 1094 DateTimeFormat.appendPatternTo(this, pattern); 1095 return this; 1096 } 1097 1098 //----------------------------------------------------------------------- 1099 private Object getFormatter() { 1100 Object f = iFormatter; 1101 1102 if (f == null) { 1103 if (iElementPairs.size() == 2) { 1104 Object printer = iElementPairs.get(0); 1105 Object parser = iElementPairs.get(1); 1106 1107 if (printer != null) { 1108 if (printer == parser || parser == null) { 1109 f = printer; 1110 } 1111 } else { 1112 f = parser; 1113 } 1114 } 1115 1116 if (f == null) { 1117 f = new Composite(iElementPairs); 1118 } 1119 1120 iFormatter = f; 1121 } 1122 1123 return f; 1124 } 1125 1126 private boolean isPrinter(Object f) { 1127 if (f instanceof DateTimePrinter) { 1128 if (f instanceof Composite) { 1129 return ((Composite)f).isPrinter(); 1130 } 1131 return true; 1132 } 1133 return false; 1134 } 1135 1136 private boolean isParser(Object f) { 1137 if (f instanceof DateTimeParser) { 1138 if (f instanceof Composite) { 1139 return ((Composite)f).isParser(); 1140 } 1141 return true; 1142 } 1143 return false; 1144 } 1145 1146 private boolean isFormatter(Object f) { 1147 return (isPrinter(f) || isParser(f)); 1148 } 1149 1150 static void appendUnknownString(StringBuffer buf, int len) { 1151 for (int i = len; --i >= 0;) { 1152 buf.append('\ufffd'); 1153 } 1154 } 1155 1156 static void printUnknownString(Writer out, int len) throws IOException { 1157 for (int i = len; --i >= 0;) { 1158 out.write('\ufffd'); 1159 } 1160 } 1161 1162 //----------------------------------------------------------------------- 1163 static class CharacterLiteral 1164 implements DateTimePrinter, DateTimeParser { 1165 1166 private final char iValue; 1167 1168 CharacterLiteral(char value) { 1169 super(); 1170 iValue = value; 1171 } 1172 1173 public int estimatePrintedLength() { 1174 return 1; 1175 } 1176 1177 public void printTo( 1178 StringBuffer buf, long instant, Chronology chrono, 1179 int displayOffset, DateTimeZone displayZone, Locale locale) { 1180 buf.append(iValue); 1181 } 1182 1183 public void printTo( 1184 Writer out, long instant, Chronology chrono, 1185 int displayOffset, DateTimeZone displayZone, Locale locale) throws IOException { 1186 out.write(iValue); 1187 } 1188 1189 public void printTo(StringBuffer buf, ReadablePartial partial, Locale locale) { 1190 buf.append(iValue); 1191 } 1192 1193 public void printTo(Writer out, ReadablePartial partial, Locale locale) throws IOException { 1194 out.write(iValue); 1195 } 1196 1197 public int estimateParsedLength() { 1198 return 1; 1199 } 1200 1201 public int parseInto(DateTimeParserBucket bucket, String text, int position) { 1202 if (position >= text.length()) { 1203 return ~position; 1204 } 1205 1206 char a = text.charAt(position); 1207 char b = iValue; 1208 1209 if (a != b) { 1210 a = Character.toUpperCase(a); 1211 b = Character.toUpperCase(b); 1212 if (a != b) { 1213 a = Character.toLowerCase(a); 1214 b = Character.toLowerCase(b); 1215 if (a != b) { 1216 return ~position; 1217 } 1218 } 1219 } 1220 1221 return position + 1; 1222 } 1223 } 1224 1225 //----------------------------------------------------------------------- 1226 static class StringLiteral 1227 implements DateTimePrinter, DateTimeParser { 1228 1229 private final String iValue; 1230 1231 StringLiteral(String value) { 1232 super(); 1233 iValue = value; 1234 } 1235 1236 public int estimatePrintedLength() { 1237 return iValue.length(); 1238 } 1239 1240 public void printTo( 1241 StringBuffer buf, long instant, Chronology chrono, 1242 int displayOffset, DateTimeZone displayZone, Locale locale) { 1243 buf.append(iValue); 1244 } 1245 1246 public void printTo( 1247 Writer out, long instant, Chronology chrono, 1248 int displayOffset, DateTimeZone displayZone, Locale locale) throws IOException { 1249 out.write(iValue); 1250 } 1251 1252 public void printTo(StringBuffer buf, ReadablePartial partial, Locale locale) { 1253 buf.append(iValue); 1254 } 1255 1256 public void printTo(Writer out, ReadablePartial partial, Locale locale) throws IOException { 1257 out.write(iValue); 1258 } 1259 1260 public int estimateParsedLength() { 1261 return iValue.length(); 1262 } 1263 1264 public int parseInto(DateTimeParserBucket bucket, String text, int position) { 1265 if (text.regionMatches(true, position, iValue, 0, iValue.length())) { 1266 return position + iValue.length(); 1267 } 1268 return ~position; 1269 } 1270 } 1271 1272 //----------------------------------------------------------------------- 1273 static abstract class NumberFormatter 1274 implements DateTimePrinter, DateTimeParser { 1275 protected final DateTimeFieldType iFieldType; 1276 protected final int iMaxParsedDigits; 1277 protected final boolean iSigned; 1278 1279 NumberFormatter(DateTimeFieldType fieldType, 1280 int maxParsedDigits, boolean signed) { 1281 super(); 1282 iFieldType = fieldType; 1283 iMaxParsedDigits = maxParsedDigits; 1284 iSigned = signed; 1285 } 1286 1287 public int estimateParsedLength() { 1288 return iMaxParsedDigits; 1289 } 1290 1291 public int parseInto(DateTimeParserBucket bucket, String text, int position) { 1292 int limit = Math.min(iMaxParsedDigits, text.length() - position); 1293 1294 boolean negative = false; 1295 int length = 0; 1296 while (length < limit) { 1297 char c = text.charAt(position + length); 1298 if (length == 0 && (c == '-' || c == '+') && iSigned) { 1299 negative = c == '-'; 1300 1301 // Next character must be a digit. 1302 if (length + 1 >= limit || 1303 (c = text.charAt(position + length + 1)) < '0' || c > '9') 1304 { 1305 break; 1306 } 1307 1308 if (negative) { 1309 length++; 1310 } else { 1311 // Skip the '+' for parseInt to succeed. 1312 position++; 1313 } 1314 // Expand the limit to disregard the sign character. 1315 limit = Math.min(limit + 1, text.length() - position); 1316 continue; 1317 } 1318 if (c < '0' || c > '9') { 1319 break; 1320 } 1321 length++; 1322 } 1323 1324 if (length == 0) { 1325 return ~position; 1326 } 1327 1328 int value; 1329 if (length >= 9) { 1330 // Since value may exceed integer limits, use stock parser 1331 // which checks for this. 1332 value = Integer.parseInt(text.substring(position, position += length)); 1333 } else { 1334 int i = position; 1335 if (negative) { 1336 i++; 1337 } 1338 try { 1339 value = text.charAt(i++) - '0'; 1340 } catch (StringIndexOutOfBoundsException e) { 1341 return ~position; 1342 } 1343 position += length; 1344 while (i < position) { 1345 value = ((value << 3) + (value << 1)) + text.charAt(i++) - '0'; 1346 } 1347 if (negative) { 1348 value = -value; 1349 } 1350 } 1351 1352 bucket.saveField(iFieldType, value); 1353 return position; 1354 } 1355 } 1356 1357 //----------------------------------------------------------------------- 1358 static class UnpaddedNumber extends NumberFormatter { 1359 1360 protected UnpaddedNumber(DateTimeFieldType fieldType, 1361 int maxParsedDigits, boolean signed) 1362 { 1363 super(fieldType, maxParsedDigits, signed); 1364 } 1365 1366 public int estimatePrintedLength() { 1367 return iMaxParsedDigits; 1368 } 1369 1370 public void printTo( 1371 StringBuffer buf, long instant, Chronology chrono, 1372 int displayOffset, DateTimeZone displayZone, Locale locale) { 1373 try { 1374 DateTimeField field = iFieldType.getField(chrono); 1375 FormatUtils.appendUnpaddedInteger(buf, field.get(instant)); 1376 } catch (RuntimeException e) { 1377 buf.append('\ufffd'); 1378 } 1379 } 1380 1381 public void printTo( 1382 Writer out, long instant, Chronology chrono, 1383 int displayOffset, DateTimeZone displayZone, Locale locale) throws IOException { 1384 try { 1385 DateTimeField field = iFieldType.getField(chrono); 1386 FormatUtils.writeUnpaddedInteger(out, field.get(instant)); 1387 } catch (RuntimeException e) { 1388 out.write('\ufffd'); 1389 } 1390 } 1391 1392 public void printTo(StringBuffer buf, ReadablePartial partial, Locale locale) { 1393 if (partial.isSupported(iFieldType)) { 1394 try { 1395 FormatUtils.appendUnpaddedInteger(buf, partial.get(iFieldType)); 1396 } catch (RuntimeException e) { 1397 buf.append('\ufffd'); 1398 } 1399 } else { 1400 buf.append('\ufffd'); 1401 } 1402 } 1403 1404 public void printTo(Writer out, ReadablePartial partial, Locale locale) throws IOException { 1405 if (partial.isSupported(iFieldType)) { 1406 try { 1407 FormatUtils.writeUnpaddedInteger(out, partial.get(iFieldType)); 1408 } catch (RuntimeException e) { 1409 out.write('\ufffd'); 1410 } 1411 } else { 1412 out.write('\ufffd'); 1413 } 1414 } 1415 } 1416 1417 //----------------------------------------------------------------------- 1418 static class PaddedNumber extends NumberFormatter { 1419 1420 protected final int iMinPrintedDigits; 1421 1422 protected PaddedNumber(DateTimeFieldType fieldType, int maxParsedDigits, 1423 boolean signed, int minPrintedDigits) 1424 { 1425 super(fieldType, maxParsedDigits, signed); 1426 iMinPrintedDigits = minPrintedDigits; 1427 } 1428 1429 public int estimatePrintedLength() { 1430 return iMaxParsedDigits; 1431 } 1432 1433 public void printTo( 1434 StringBuffer buf, long instant, Chronology chrono, 1435 int displayOffset, DateTimeZone displayZone, Locale locale) { 1436 try { 1437 DateTimeField field = iFieldType.getField(chrono); 1438 FormatUtils.appendPaddedInteger(buf, field.get(instant), iMinPrintedDigits); 1439 } catch (RuntimeException e) { 1440 appendUnknownString(buf, iMinPrintedDigits); 1441 } 1442 } 1443 1444 public void printTo( 1445 Writer out, long instant, Chronology chrono, 1446 int displayOffset, DateTimeZone displayZone, Locale locale) throws IOException { 1447 try { 1448 DateTimeField field = iFieldType.getField(chrono); 1449 FormatUtils.writePaddedInteger(out, field.get(instant), iMinPrintedDigits); 1450 } catch (RuntimeException e) { 1451 printUnknownString(out, iMinPrintedDigits); 1452 } 1453 } 1454 1455 public void printTo(StringBuffer buf, ReadablePartial partial, Locale locale) { 1456 if (partial.isSupported(iFieldType)) { 1457 try { 1458 FormatUtils.appendPaddedInteger(buf, partial.get(iFieldType), iMinPrintedDigits); 1459 } catch (RuntimeException e) { 1460 appendUnknownString(buf, iMinPrintedDigits); 1461 } 1462 } else { 1463 appendUnknownString(buf, iMinPrintedDigits); 1464 } 1465 } 1466 1467 public void printTo(Writer out, ReadablePartial partial, Locale locale) throws IOException { 1468 if (partial.isSupported(iFieldType)) { 1469 try { 1470 FormatUtils.writePaddedInteger(out, partial.get(iFieldType), iMinPrintedDigits); 1471 } catch (RuntimeException e) { 1472 printUnknownString(out, iMinPrintedDigits); 1473 } 1474 } else { 1475 printUnknownString(out, iMinPrintedDigits); 1476 } 1477 } 1478 } 1479 1480 //----------------------------------------------------------------------- 1481 static class FixedNumber extends PaddedNumber { 1482 1483 protected FixedNumber(DateTimeFieldType fieldType, int numDigits, boolean signed) { 1484 super(fieldType, numDigits, signed, numDigits); 1485 } 1486 1487 public int parseInto(DateTimeParserBucket bucket, String text, int position) { 1488 int newPos = super.parseInto(bucket, text, position); 1489 if (newPos < 0) { 1490 return newPos; 1491 } 1492 int expectedPos = position + iMaxParsedDigits; 1493 if (newPos != expectedPos) { 1494 if (iSigned) { 1495 char c = text.charAt(position); 1496 if (c == '-' || c == '+') { 1497 expectedPos++; 1498 } 1499 } 1500 if (newPos > expectedPos) { 1501 // The failure is at the position of the first extra digit. 1502 return ~(expectedPos + 1); 1503 } else if (newPos < expectedPos) { 1504 // The failure is at the position where the next digit should be. 1505 return ~newPos; 1506 } 1507 } 1508 return newPos; 1509 } 1510 } 1511 1512 //----------------------------------------------------------------------- 1513 static class TwoDigitYear 1514 implements DateTimePrinter, DateTimeParser { 1515 1516 /** The field to print/parse. */ 1517 private final DateTimeFieldType iType; 1518 /** The pivot year. */ 1519 private final int iPivot; 1520 private final boolean iLenientParse; 1521 1522 TwoDigitYear(DateTimeFieldType type, int pivot, boolean lenientParse) { 1523 super(); 1524 iType = type; 1525 iPivot = pivot; 1526 iLenientParse = lenientParse; 1527 } 1528 1529 public int estimateParsedLength() { 1530 return iLenientParse ? 4 : 2; 1531 } 1532 1533 public int parseInto(DateTimeParserBucket bucket, String text, int position) { 1534 int limit = text.length() - position; 1535 1536 if (!iLenientParse) { 1537 limit = Math.min(2, limit); 1538 if (limit < 2) { 1539 return ~position; 1540 } 1541 } else { 1542 boolean hasSignChar = false; 1543 boolean negative = false; 1544 int length = 0; 1545 while (length < limit) { 1546 char c = text.charAt(position + length); 1547 if (length == 0 && (c == '-' || c == '+')) { 1548 hasSignChar = true; 1549 negative = c == '-'; 1550 if (negative) { 1551 length++; 1552 } else { 1553 // Skip the '+' for parseInt to succeed. 1554 position++; 1555 limit--; 1556 } 1557 continue; 1558 } 1559 if (c < '0' || c > '9') { 1560 break; 1561 } 1562 length++; 1563 } 1564 1565 if (length == 0) { 1566 return ~position; 1567 } 1568 1569 if (hasSignChar || length != 2) { 1570 int value; 1571 if (length >= 9) { 1572 // Since value may exceed integer limits, use stock 1573 // parser which checks for this. 1574 value = Integer.parseInt(text.substring(position, position += length)); 1575 } else { 1576 int i = position; 1577 if (negative) { 1578 i++; 1579 } 1580 try { 1581 value = text.charAt(i++) - '0'; 1582 } catch (StringIndexOutOfBoundsException e) { 1583 return ~position; 1584 } 1585 position += length; 1586 while (i < position) { 1587 value = ((value << 3) + (value << 1)) + text.charAt(i++) - '0'; 1588 } 1589 if (negative) { 1590 value = -value; 1591 } 1592 } 1593 1594 bucket.saveField(iType, value); 1595 return position; 1596 } 1597 } 1598 1599 int year; 1600 char c = text.charAt(position); 1601 if (c < '0' || c > '9') { 1602 return ~position; 1603 } 1604 year = c - '0'; 1605 c = text.charAt(position + 1); 1606 if (c < '0' || c > '9') { 1607 return ~position; 1608 } 1609 year = ((year << 3) + (year << 1)) + c - '0'; 1610 1611 int pivot = iPivot; 1612 // If the bucket pivot year is non-null, use that when parsing 1613 if (bucket.getPivotYear() != null) { 1614 pivot = bucket.getPivotYear().intValue(); 1615 } 1616 1617 int low = pivot - 50; 1618 1619 int t; 1620 if (low >= 0) { 1621 t = low % 100; 1622 } else { 1623 t = 99 + ((low + 1) % 100); 1624 } 1625 1626 year += low + ((year < t) ? 100 : 0) - t; 1627 1628 bucket.saveField(iType, year); 1629 return position + 2; 1630 } 1631 1632 public int estimatePrintedLength() { 1633 return 2; 1634 } 1635 1636 public void printTo( 1637 StringBuffer buf, long instant, Chronology chrono, 1638 int displayOffset, DateTimeZone displayZone, Locale locale) { 1639 int year = getTwoDigitYear(instant, chrono); 1640 if (year < 0) { 1641 buf.append('\ufffd'); 1642 buf.append('\ufffd'); 1643 } else { 1644 FormatUtils.appendPaddedInteger(buf, year, 2); 1645 } 1646 } 1647 1648 public void printTo( 1649 Writer out, long instant, Chronology chrono, 1650 int displayOffset, DateTimeZone displayZone, Locale locale) throws IOException { 1651 int year = getTwoDigitYear(instant, chrono); 1652 if (year < 0) { 1653 out.write('\ufffd'); 1654 out.write('\ufffd'); 1655 } else { 1656 FormatUtils.writePaddedInteger(out, year, 2); 1657 } 1658 } 1659 1660 private int getTwoDigitYear(long instant, Chronology chrono) { 1661 try { 1662 int year = iType.getField(chrono).get(instant); 1663 if (year < 0) { 1664 year = -year; 1665 } 1666 return year % 100; 1667 } catch (RuntimeException e) { 1668 return -1; 1669 } 1670 } 1671 1672 public void printTo(StringBuffer buf, ReadablePartial partial, Locale locale) { 1673 int year = getTwoDigitYear(partial); 1674 if (year < 0) { 1675 buf.append('\ufffd'); 1676 buf.append('\ufffd'); 1677 } else { 1678 FormatUtils.appendPaddedInteger(buf, year, 2); 1679 } 1680 } 1681 1682 public void printTo(Writer out, ReadablePartial partial, Locale locale) throws IOException { 1683 int year = getTwoDigitYear(partial); 1684 if (year < 0) { 1685 out.write('\ufffd'); 1686 out.write('\ufffd'); 1687 } else { 1688 FormatUtils.writePaddedInteger(out, year, 2); 1689 } 1690 } 1691 1692 private int getTwoDigitYear(ReadablePartial partial) { 1693 if (partial.isSupported(iType)) { 1694 try { 1695 int year = partial.get(iType); 1696 if (year < 0) { 1697 year = -year; 1698 } 1699 return year % 100; 1700 } catch (RuntimeException e) {} 1701 } 1702 return -1; 1703 } 1704 } 1705 1706 //----------------------------------------------------------------------- 1707 static class TextField 1708 implements DateTimePrinter, DateTimeParser { 1709 1710 private static Map<Locale, Map<DateTimeFieldType, Object[]>> cParseCache = 1711 new HashMap<Locale, Map<DateTimeFieldType, Object[]>>(); 1712 private final DateTimeFieldType iFieldType; 1713 private final boolean iShort; 1714 1715 TextField(DateTimeFieldType fieldType, boolean isShort) { 1716 super(); 1717 iFieldType = fieldType; 1718 iShort = isShort; 1719 } 1720 1721 public int estimatePrintedLength() { 1722 return iShort ? 6 : 20; 1723 } 1724 1725 public void printTo( 1726 StringBuffer buf, long instant, Chronology chrono, 1727 int displayOffset, DateTimeZone displayZone, Locale locale) { 1728 try { 1729 buf.append(print(instant, chrono, locale)); 1730 } catch (RuntimeException e) { 1731 buf.append('\ufffd'); 1732 } 1733 } 1734 1735 public void printTo( 1736 Writer out, long instant, Chronology chrono, 1737 int displayOffset, DateTimeZone displayZone, Locale locale) throws IOException { 1738 try { 1739 out.write(print(instant, chrono, locale)); 1740 } catch (RuntimeException e) { 1741 out.write('\ufffd'); 1742 } 1743 } 1744 1745 public void printTo(StringBuffer buf, ReadablePartial partial, Locale locale) { 1746 try { 1747 buf.append(print(partial, locale)); 1748 } catch (RuntimeException e) { 1749 buf.append('\ufffd'); 1750 } 1751 } 1752 1753 public void printTo(Writer out, ReadablePartial partial, Locale locale) throws IOException { 1754 try { 1755 out.write(print(partial, locale)); 1756 } catch (RuntimeException e) { 1757 out.write('\ufffd'); 1758 } 1759 } 1760 1761 private String print(long instant, Chronology chrono, Locale locale) { 1762 DateTimeField field = iFieldType.getField(chrono); 1763 if (iShort) { 1764 return field.getAsShortText(instant, locale); 1765 } else { 1766 return field.getAsText(instant, locale); 1767 } 1768 } 1769 1770 private String print(ReadablePartial partial, Locale locale) { 1771 if (partial.isSupported(iFieldType)) { 1772 DateTimeField field = iFieldType.getField(partial.getChronology()); 1773 if (iShort) { 1774 return field.getAsShortText(partial, locale); 1775 } else { 1776 return field.getAsText(partial, locale); 1777 } 1778 } else { 1779 return "\ufffd"; 1780 } 1781 } 1782 1783 public int estimateParsedLength() { 1784 return estimatePrintedLength(); 1785 } 1786 1787 @SuppressWarnings("unchecked") 1788 public int parseInto(DateTimeParserBucket bucket, String text, int position) { 1789 Locale locale = bucket.getLocale(); 1790 // handle languages which might have non ASCII A-Z or punctuation 1791 // bug 1788282 1792 Set<String> validValues = null; 1793 int maxLength = 0; 1794 synchronized (cParseCache) { 1795 Map<DateTimeFieldType, Object[]> innerMap = cParseCache.get(locale); 1796 if (innerMap == null) { 1797 innerMap = new HashMap<DateTimeFieldType, Object[]>(); 1798 cParseCache.put(locale, innerMap); 1799 } 1800 Object[] array = innerMap.get(iFieldType); 1801 if (array == null) { 1802 validValues = new HashSet<String>(32); 1803 MutableDateTime dt = new MutableDateTime(0L, DateTimeZone.UTC); 1804 Property property = dt.property(iFieldType); 1805 int min = property.getMinimumValueOverall(); 1806 int max = property.getMaximumValueOverall(); 1807 if (max - min > 32) { // protect against invalid fields 1808 return ~position; 1809 } 1810 maxLength = property.getMaximumTextLength(locale); 1811 for (int i = min; i <= max; i++) { 1812 property.set(i); 1813 validValues.add(property.getAsShortText(locale)); 1814 validValues.add(property.getAsShortText(locale).toLowerCase(locale)); 1815 validValues.add(property.getAsShortText(locale).toUpperCase(locale)); 1816 validValues.add(property.getAsText(locale)); 1817 validValues.add(property.getAsText(locale).toLowerCase(locale)); 1818 validValues.add(property.getAsText(locale).toUpperCase(locale)); 1819 } 1820 if ("en".equals(locale.getLanguage()) && iFieldType == DateTimeFieldType.era()) { 1821 // hack to support for parsing "BCE" and "CE" if the language is English 1822 validValues.add("BCE"); 1823 validValues.add("bce"); 1824 validValues.add("CE"); 1825 validValues.add("ce"); 1826 maxLength = 3; 1827 } 1828 array = new Object[] {validValues, Integer.valueOf(maxLength)}; 1829 innerMap.put(iFieldType, array); 1830 } else { 1831 validValues = (Set<String>) array[0]; 1832 maxLength = ((Integer) array[1]).intValue(); 1833 } 1834 } 1835 // match the longest string first using our knowledge of the max length 1836 int limit = Math.min(text.length(), position + maxLength); 1837 for (int i = limit; i > position; i--) { 1838 String match = text.substring(position, i); 1839 if (validValues.contains(match)) { 1840 bucket.saveField(iFieldType, match, locale); 1841 return i; 1842 } 1843 } 1844 return ~position; 1845 } 1846 } 1847 1848 //----------------------------------------------------------------------- 1849 static class Fraction 1850 implements DateTimePrinter, DateTimeParser { 1851 1852 private final DateTimeFieldType iFieldType; 1853 protected int iMinDigits; 1854 protected int iMaxDigits; 1855 1856 protected Fraction(DateTimeFieldType fieldType, int minDigits, int maxDigits) { 1857 super(); 1858 iFieldType = fieldType; 1859 // Limit the precision requirements. 1860 if (maxDigits > 18) { 1861 maxDigits = 18; 1862 } 1863 iMinDigits = minDigits; 1864 iMaxDigits = maxDigits; 1865 } 1866 1867 public int estimatePrintedLength() { 1868 return iMaxDigits; 1869 } 1870 1871 public void printTo( 1872 StringBuffer buf, long instant, Chronology chrono, 1873 int displayOffset, DateTimeZone displayZone, Locale locale) { 1874 try { 1875 printTo(buf, null, instant, chrono); 1876 } catch (IOException e) { 1877 // Not gonna happen. 1878 } 1879 } 1880 1881 public void printTo( 1882 Writer out, long instant, Chronology chrono, 1883 int displayOffset, DateTimeZone displayZone, Locale locale) throws IOException { 1884 printTo(null, out, instant, chrono); 1885 } 1886 1887 public void printTo(StringBuffer buf, ReadablePartial partial, Locale locale) { 1888 // removed check whether field is supported, as input field is typically 1889 // secondOfDay which is unsupported by TimeOfDay 1890 long millis = partial.getChronology().set(partial, 0L); 1891 try { 1892 printTo(buf, null, millis, partial.getChronology()); 1893 } catch (IOException e) { 1894 // Not gonna happen. 1895 } 1896 } 1897 1898 public void printTo(Writer out, ReadablePartial partial, Locale locale) throws IOException { 1899 // removed check whether field is supported, as input field is typically 1900 // secondOfDay which is unsupported by TimeOfDay 1901 long millis = partial.getChronology().set(partial, 0L); 1902 printTo(null, out, millis, partial.getChronology()); 1903 } 1904 1905 protected void printTo(StringBuffer buf, Writer out, long instant, Chronology chrono) 1906 throws IOException 1907 { 1908 DateTimeField field = iFieldType.getField(chrono); 1909 int minDigits = iMinDigits; 1910 1911 long fraction; 1912 try { 1913 fraction = field.remainder(instant); 1914 } catch (RuntimeException e) { 1915 if (buf != null) { 1916 appendUnknownString(buf, minDigits); 1917 } else { 1918 printUnknownString(out, minDigits); 1919 } 1920 return; 1921 } 1922 1923 if (fraction == 0) { 1924 if (buf != null) { 1925 while (--minDigits >= 0) { 1926 buf.append('0'); 1927 } 1928 } else { 1929 while (--minDigits >= 0) { 1930 out.write('0'); 1931 } 1932 } 1933 return; 1934 } 1935 1936 String str; 1937 long[] fractionData = getFractionData(fraction, field); 1938 long scaled = fractionData[0]; 1939 int maxDigits = (int) fractionData[1]; 1940 1941 if ((scaled & 0x7fffffff) == scaled) { 1942 str = Integer.toString((int) scaled); 1943 } else { 1944 str = Long.toString(scaled); 1945 } 1946 1947 int length = str.length(); 1948 int digits = maxDigits; 1949 while (length < digits) { 1950 if (buf != null) { 1951 buf.append('0'); 1952 } else { 1953 out.write('0'); 1954 } 1955 minDigits--; 1956 digits--; 1957 } 1958 1959 if (minDigits < digits) { 1960 // Chop off as many trailing zero digits as necessary. 1961 while (minDigits < digits) { 1962 if (length <= 1 || str.charAt(length - 1) != '0') { 1963 break; 1964 } 1965 digits--; 1966 length--; 1967 } 1968 if (length < str.length()) { 1969 if (buf != null) { 1970 for (int i=0; i<length; i++) { 1971 buf.append(str.charAt(i)); 1972 } 1973 } else { 1974 for (int i=0; i<length; i++) { 1975 out.write(str.charAt(i)); 1976 } 1977 } 1978 return; 1979 } 1980 } 1981 1982 if (buf != null) { 1983 buf.append(str); 1984 } else { 1985 out.write(str); 1986 } 1987 } 1988 1989 private long[] getFractionData(long fraction, DateTimeField field) { 1990 long rangeMillis = field.getDurationField().getUnitMillis(); 1991 long scalar; 1992 int maxDigits = iMaxDigits; 1993 while (true) { 1994 switch (maxDigits) { 1995 default: scalar = 1L; break; 1996 case 1: scalar = 10L; break; 1997 case 2: scalar = 100L; break; 1998 case 3: scalar = 1000L; break; 1999 case 4: scalar = 10000L; break; 2000 case 5: scalar = 100000L; break; 2001 case 6: scalar = 1000000L; break; 2002 case 7: scalar = 10000000L; break; 2003 case 8: scalar = 100000000L; break; 2004 case 9: scalar = 1000000000L; break; 2005 case 10: scalar = 10000000000L; break; 2006 case 11: scalar = 100000000000L; break; 2007 case 12: scalar = 1000000000000L; break; 2008 case 13: scalar = 10000000000000L; break; 2009 case 14: scalar = 100000000000000L; break; 2010 case 15: scalar = 1000000000000000L; break; 2011 case 16: scalar = 10000000000000000L; break; 2012 case 17: scalar = 100000000000000000L; break; 2013 case 18: scalar = 1000000000000000000L; break; 2014 } 2015 if (((rangeMillis * scalar) / scalar) == rangeMillis) { 2016 break; 2017 } 2018 // Overflowed: scale down. 2019 maxDigits--; 2020 } 2021 2022 return new long[] {fraction * scalar / rangeMillis, maxDigits}; 2023 } 2024 2025 public int estimateParsedLength() { 2026 return iMaxDigits; 2027 } 2028 2029 public int parseInto(DateTimeParserBucket bucket, String text, int position) { 2030 DateTimeField field = iFieldType.getField(bucket.getChronology()); 2031 2032 int limit = Math.min(iMaxDigits, text.length() - position); 2033 2034 long value = 0; 2035 long n = field.getDurationField().getUnitMillis() * 10; 2036 int length = 0; 2037 while (length < limit) { 2038 char c = text.charAt(position + length); 2039 if (c < '0' || c > '9') { 2040 break; 2041 } 2042 length++; 2043 long nn = n / 10; 2044 value += (c - '0') * nn; 2045 n = nn; 2046 } 2047 2048 value /= 10; 2049 2050 if (length == 0) { 2051 return ~position; 2052 } 2053 2054 if (value > Integer.MAX_VALUE) { 2055 return ~position; 2056 } 2057 2058 DateTimeField parseField = new PreciseDateTimeField( 2059 DateTimeFieldType.millisOfSecond(), 2060 MillisDurationField.INSTANCE, 2061 field.getDurationField()); 2062 2063 bucket.saveField(parseField, (int) value); 2064 2065 return position + length; 2066 } 2067 } 2068 2069 //----------------------------------------------------------------------- 2070 static class TimeZoneOffset 2071 implements DateTimePrinter, DateTimeParser { 2072 2073 private final String iZeroOffsetPrintText; 2074 private final String iZeroOffsetParseText; 2075 private final boolean iShowSeparators; 2076 private final int iMinFields; 2077 private final int iMaxFields; 2078 2079 TimeZoneOffset(String zeroOffsetPrintText, String zeroOffsetParseText, 2080 boolean showSeparators, 2081 int minFields, int maxFields) 2082 { 2083 super(); 2084 iZeroOffsetPrintText = zeroOffsetPrintText; 2085 iZeroOffsetParseText = zeroOffsetParseText; 2086 iShowSeparators = showSeparators; 2087 if (minFields <= 0 || maxFields < minFields) { 2088 throw new IllegalArgumentException(); 2089 } 2090 if (minFields > 4) { 2091 minFields = 4; 2092 maxFields = 4; 2093 } 2094 iMinFields = minFields; 2095 iMaxFields = maxFields; 2096 } 2097 2098 public int estimatePrintedLength() { 2099 int est = 1 + iMinFields << 1; 2100 if (iShowSeparators) { 2101 est += iMinFields - 1; 2102 } 2103 if (iZeroOffsetPrintText != null && iZeroOffsetPrintText.length() > est) { 2104 est = iZeroOffsetPrintText.length(); 2105 } 2106 return est; 2107 } 2108 2109 public void printTo( 2110 StringBuffer buf, long instant, Chronology chrono, 2111 int displayOffset, DateTimeZone displayZone, Locale locale) { 2112 if (displayZone == null) { 2113 return; // no zone 2114 } 2115 if (displayOffset == 0 && iZeroOffsetPrintText != null) { 2116 buf.append(iZeroOffsetPrintText); 2117 return; 2118 } 2119 if (displayOffset >= 0) { 2120 buf.append('+'); 2121 } else { 2122 buf.append('-'); 2123 displayOffset = -displayOffset; 2124 } 2125 2126 int hours = displayOffset / DateTimeConstants.MILLIS_PER_HOUR; 2127 FormatUtils.appendPaddedInteger(buf, hours, 2); 2128 if (iMaxFields == 1) { 2129 return; 2130 } 2131 displayOffset -= hours * (int)DateTimeConstants.MILLIS_PER_HOUR; 2132 if (displayOffset == 0 && iMinFields <= 1) { 2133 return; 2134 } 2135 2136 int minutes = displayOffset / DateTimeConstants.MILLIS_PER_MINUTE; 2137 if (iShowSeparators) { 2138 buf.append(':'); 2139 } 2140 FormatUtils.appendPaddedInteger(buf, minutes, 2); 2141 if (iMaxFields == 2) { 2142 return; 2143 } 2144 displayOffset -= minutes * DateTimeConstants.MILLIS_PER_MINUTE; 2145 if (displayOffset == 0 && iMinFields <= 2) { 2146 return; 2147 } 2148 2149 int seconds = displayOffset / DateTimeConstants.MILLIS_PER_SECOND; 2150 if (iShowSeparators) { 2151 buf.append(':'); 2152 } 2153 FormatUtils.appendPaddedInteger(buf, seconds, 2); 2154 if (iMaxFields == 3) { 2155 return; 2156 } 2157 displayOffset -= seconds * DateTimeConstants.MILLIS_PER_SECOND; 2158 if (displayOffset == 0 && iMinFields <= 3) { 2159 return; 2160 } 2161 2162 if (iShowSeparators) { 2163 buf.append('.'); 2164 } 2165 FormatUtils.appendPaddedInteger(buf, displayOffset, 3); 2166 } 2167 2168 public void printTo( 2169 Writer out, long instant, Chronology chrono, 2170 int displayOffset, DateTimeZone displayZone, Locale locale) throws IOException { 2171 if (displayZone == null) { 2172 return; // no zone 2173 } 2174 if (displayOffset == 0 && iZeroOffsetPrintText != null) { 2175 out.write(iZeroOffsetPrintText); 2176 return; 2177 } 2178 if (displayOffset >= 0) { 2179 out.write('+'); 2180 } else { 2181 out.write('-'); 2182 displayOffset = -displayOffset; 2183 } 2184 2185 int hours = displayOffset / DateTimeConstants.MILLIS_PER_HOUR; 2186 FormatUtils.writePaddedInteger(out, hours, 2); 2187 if (iMaxFields == 1) { 2188 return; 2189 } 2190 displayOffset -= hours * (int)DateTimeConstants.MILLIS_PER_HOUR; 2191 if (displayOffset == 0 && iMinFields == 1) { 2192 return; 2193 } 2194 2195 int minutes = displayOffset / DateTimeConstants.MILLIS_PER_MINUTE; 2196 if (iShowSeparators) { 2197 out.write(':'); 2198 } 2199 FormatUtils.writePaddedInteger(out, minutes, 2); 2200 if (iMaxFields == 2) { 2201 return; 2202 } 2203 displayOffset -= minutes * DateTimeConstants.MILLIS_PER_MINUTE; 2204 if (displayOffset == 0 && iMinFields == 2) { 2205 return; 2206 } 2207 2208 int seconds = displayOffset / DateTimeConstants.MILLIS_PER_SECOND; 2209 if (iShowSeparators) { 2210 out.write(':'); 2211 } 2212 FormatUtils.writePaddedInteger(out, seconds, 2); 2213 if (iMaxFields == 3) { 2214 return; 2215 } 2216 displayOffset -= seconds * DateTimeConstants.MILLIS_PER_SECOND; 2217 if (displayOffset == 0 && iMinFields == 3) { 2218 return; 2219 } 2220 2221 if (iShowSeparators) { 2222 out.write('.'); 2223 } 2224 FormatUtils.writePaddedInteger(out, displayOffset, 3); 2225 } 2226 2227 public void printTo(StringBuffer buf, ReadablePartial partial, Locale locale) { 2228 // no zone info 2229 } 2230 2231 public void printTo(Writer out, ReadablePartial partial, Locale locale) throws IOException { 2232 // no zone info 2233 } 2234 2235 public int estimateParsedLength() { 2236 return estimatePrintedLength(); 2237 } 2238 2239 public int parseInto(DateTimeParserBucket bucket, String text, int position) { 2240 int limit = text.length() - position; 2241 2242 zeroOffset: 2243 if (iZeroOffsetParseText != null) { 2244 if (iZeroOffsetParseText.length() == 0) { 2245 // Peek ahead, looking for sign character. 2246 if (limit > 0) { 2247 char c = text.charAt(position); 2248 if (c == '-' || c == '+') { 2249 break zeroOffset; 2250 } 2251 } 2252 bucket.setOffset(Integer.valueOf(0)); 2253 return position; 2254 } 2255 if (text.regionMatches(true, position, iZeroOffsetParseText, 0, iZeroOffsetParseText.length())) { 2256 bucket.setOffset(Integer.valueOf(0)); 2257 return position + iZeroOffsetParseText.length(); 2258 } 2259 } 2260 2261 // Format to expect is sign character followed by at least one digit. 2262 2263 if (limit <= 1) { 2264 return ~position; 2265 } 2266 2267 boolean negative; 2268 char c = text.charAt(position); 2269 if (c == '-') { 2270 negative = true; 2271 } else if (c == '+') { 2272 negative = false; 2273 } else { 2274 return ~position; 2275 } 2276 2277 limit--; 2278 position++; 2279 2280 // Format following sign is one of: 2281 // 2282 // hh 2283 // hhmm 2284 // hhmmss 2285 // hhmmssSSS 2286 // hh:mm 2287 // hh:mm:ss 2288 // hh:mm:ss.SSS 2289 2290 // First parse hours. 2291 2292 if (digitCount(text, position, 2) < 2) { 2293 // Need two digits for hour. 2294 return ~position; 2295 } 2296 2297 int offset; 2298 2299 int hours = FormatUtils.parseTwoDigits(text, position); 2300 if (hours > 23) { 2301 return ~position; 2302 } 2303 offset = hours * DateTimeConstants.MILLIS_PER_HOUR; 2304 limit -= 2; 2305 position += 2; 2306 2307 parse: { 2308 // Need to decide now if separators are expected or parsing 2309 // stops at hour field. 2310 2311 if (limit <= 0) { 2312 break parse; 2313 } 2314 2315 boolean expectSeparators; 2316 c = text.charAt(position); 2317 if (c == ':') { 2318 expectSeparators = true; 2319 limit--; 2320 position++; 2321 } else if (c >= '0' && c <= '9') { 2322 expectSeparators = false; 2323 } else { 2324 break parse; 2325 } 2326 2327 // Proceed to parse minutes. 2328 2329 int count = digitCount(text, position, 2); 2330 if (count == 0 && !expectSeparators) { 2331 break parse; 2332 } else if (count < 2) { 2333 // Need two digits for minute. 2334 return ~position; 2335 } 2336 2337 int minutes = FormatUtils.parseTwoDigits(text, position); 2338 if (minutes > 59) { 2339 return ~position; 2340 } 2341 offset += minutes * DateTimeConstants.MILLIS_PER_MINUTE; 2342 limit -= 2; 2343 position += 2; 2344 2345 // Proceed to parse seconds. 2346 2347 if (limit <= 0) { 2348 break parse; 2349 } 2350 2351 if (expectSeparators) { 2352 if (text.charAt(position) != ':') { 2353 break parse; 2354 } 2355 limit--; 2356 position++; 2357 } 2358 2359 count = digitCount(text, position, 2); 2360 if (count == 0 && !expectSeparators) { 2361 break parse; 2362 } else if (count < 2) { 2363 // Need two digits for second. 2364 return ~position; 2365 } 2366 2367 int seconds = FormatUtils.parseTwoDigits(text, position); 2368 if (seconds > 59) { 2369 return ~position; 2370 } 2371 offset += seconds * DateTimeConstants.MILLIS_PER_SECOND; 2372 limit -= 2; 2373 position += 2; 2374 2375 // Proceed to parse fraction of second. 2376 2377 if (limit <= 0) { 2378 break parse; 2379 } 2380 2381 if (expectSeparators) { 2382 if (text.charAt(position) != '.' && text.charAt(position) != ',') { 2383 break parse; 2384 } 2385 limit--; 2386 position++; 2387 } 2388 2389 count = digitCount(text, position, 3); 2390 if (count == 0 && !expectSeparators) { 2391 break parse; 2392 } else if (count < 1) { 2393 // Need at least one digit for fraction of second. 2394 return ~position; 2395 } 2396 2397 offset += (text.charAt(position++) - '0') * 100; 2398 if (count > 1) { 2399 offset += (text.charAt(position++) - '0') * 10; 2400 if (count > 2) { 2401 offset += text.charAt(position++) - '0'; 2402 } 2403 } 2404 } 2405 2406 bucket.setOffset(Integer.valueOf(negative ? -offset : offset)); 2407 return position; 2408 } 2409 2410 /** 2411 * Returns actual amount of digits to parse, but no more than original 2412 * 'amount' parameter. 2413 */ 2414 private int digitCount(String text, int position, int amount) { 2415 int limit = Math.min(text.length() - position, amount); 2416 amount = 0; 2417 for (; limit > 0; limit--) { 2418 char c = text.charAt(position + amount); 2419 if (c < '0' || c > '9') { 2420 break; 2421 } 2422 amount++; 2423 } 2424 return amount; 2425 } 2426 } 2427 2428 //----------------------------------------------------------------------- 2429 static class TimeZoneName 2430 implements DateTimePrinter, DateTimeParser { 2431 2432 static final int LONG_NAME = 0; 2433 static final int SHORT_NAME = 1; 2434 2435 private final Map<String, DateTimeZone> iParseLookup; 2436 private final int iType; 2437 2438 TimeZoneName(int type, Map<String, DateTimeZone> parseLookup) { 2439 super(); 2440 iType = type; 2441 iParseLookup = parseLookup; 2442 } 2443 2444 public int estimatePrintedLength() { 2445 return (iType == SHORT_NAME ? 4 : 20); 2446 } 2447 2448 public void printTo( 2449 StringBuffer buf, long instant, Chronology chrono, 2450 int displayOffset, DateTimeZone displayZone, Locale locale) { 2451 buf.append(print(instant - displayOffset, displayZone, locale)); 2452 } 2453 2454 public void printTo( 2455 Writer out, long instant, Chronology chrono, 2456 int displayOffset, DateTimeZone displayZone, Locale locale) throws IOException { 2457 out.write(print(instant - displayOffset, displayZone, locale)); 2458 } 2459 2460 private String print(long instant, DateTimeZone displayZone, Locale locale) { 2461 if (displayZone == null) { 2462 return ""; // no zone 2463 } 2464 switch (iType) { 2465 case LONG_NAME: 2466 return displayZone.getName(instant, locale); 2467 case SHORT_NAME: 2468 return displayZone.getShortName(instant, locale); 2469 } 2470 return ""; 2471 } 2472 2473 public void printTo(StringBuffer buf, ReadablePartial partial, Locale locale) { 2474 // no zone info 2475 } 2476 2477 public void printTo(Writer out, ReadablePartial partial, Locale locale) throws IOException { 2478 // no zone info 2479 } 2480 2481 public int estimateParsedLength() { 2482 return (iType == SHORT_NAME ? 4 : 20); 2483 } 2484 2485 public int parseInto(DateTimeParserBucket bucket, String text, int position) { 2486 String str = text.substring(position); 2487 for (String name : iParseLookup.keySet()) { 2488 if (str.startsWith(name)) { 2489 bucket.setZone(iParseLookup.get(name)); 2490 return position + name.length(); 2491 } 2492 } 2493 return ~position; 2494 } 2495 } 2496 2497 //----------------------------------------------------------------------- 2498 static enum TimeZoneId 2499 implements DateTimePrinter, DateTimeParser { 2500 2501 INSTANCE; 2502 static final Set<String> ALL_IDS = DateTimeZone.getAvailableIDs(); 2503 static final int MAX_LENGTH; 2504 static { 2505 int max = 0; 2506 for (String id : ALL_IDS) { 2507 max = Math.max(max, id.length()); 2508 } 2509 MAX_LENGTH = max; 2510 } 2511 2512 public int estimatePrintedLength() { 2513 return MAX_LENGTH; 2514 } 2515 2516 public void printTo( 2517 StringBuffer buf, long instant, Chronology chrono, 2518 int displayOffset, DateTimeZone displayZone, Locale locale) { 2519 buf.append(displayZone != null ? displayZone.getID() : ""); 2520 } 2521 2522 public void printTo( 2523 Writer out, long instant, Chronology chrono, 2524 int displayOffset, DateTimeZone displayZone, Locale locale) throws IOException { 2525 out.write(displayZone != null ? displayZone.getID() : ""); 2526 } 2527 2528 public void printTo(StringBuffer buf, ReadablePartial partial, Locale locale) { 2529 // no zone info 2530 } 2531 2532 public void printTo(Writer out, ReadablePartial partial, Locale locale) throws IOException { 2533 // no zone info 2534 } 2535 2536 public int estimateParsedLength() { 2537 return MAX_LENGTH; 2538 } 2539 2540 public int parseInto(DateTimeParserBucket bucket, String text, int position) { 2541 String str = text.substring(position); 2542 String best = null; 2543 for (String id : ALL_IDS) { 2544 if (str.startsWith(id)) { 2545 if (best == null || id.length() > best.length()) { 2546 best = id; 2547 } 2548 } 2549 } 2550 if (best != null) { 2551 bucket.setZone(DateTimeZone.forID(best)); 2552 return position + best.length(); 2553 } 2554 return ~position; 2555 } 2556 } 2557 2558 //----------------------------------------------------------------------- 2559 static class Composite 2560 implements DateTimePrinter, DateTimeParser { 2561 2562 private final DateTimePrinter[] iPrinters; 2563 private final DateTimeParser[] iParsers; 2564 2565 private final int iPrintedLengthEstimate; 2566 private final int iParsedLengthEstimate; 2567 2568 Composite(List<Object> elementPairs) { 2569 super(); 2570 2571 List<Object> printerList = new ArrayList<Object>(); 2572 List<Object> parserList = new ArrayList<Object>(); 2573 2574 decompose(elementPairs, printerList, parserList); 2575 2576 if (printerList.size() <= 0) { 2577 iPrinters = null; 2578 iPrintedLengthEstimate = 0; 2579 } else { 2580 int size = printerList.size(); 2581 iPrinters = new DateTimePrinter[size]; 2582 int printEst = 0; 2583 for (int i=0; i<size; i++) { 2584 DateTimePrinter printer = (DateTimePrinter) printerList.get(i); 2585 printEst += printer.estimatePrintedLength(); 2586 iPrinters[i] = printer; 2587 } 2588 iPrintedLengthEstimate = printEst; 2589 } 2590 2591 if (parserList.size() <= 0) { 2592 iParsers = null; 2593 iParsedLengthEstimate = 0; 2594 } else { 2595 int size = parserList.size(); 2596 iParsers = new DateTimeParser[size]; 2597 int parseEst = 0; 2598 for (int i=0; i<size; i++) { 2599 DateTimeParser parser = (DateTimeParser) parserList.get(i); 2600 parseEst += parser.estimateParsedLength(); 2601 iParsers[i] = parser; 2602 } 2603 iParsedLengthEstimate = parseEst; 2604 } 2605 } 2606 2607 public int estimatePrintedLength() { 2608 return iPrintedLengthEstimate; 2609 } 2610 2611 public void printTo( 2612 StringBuffer buf, long instant, Chronology chrono, 2613 int displayOffset, DateTimeZone displayZone, Locale locale) { 2614 DateTimePrinter[] elements = iPrinters; 2615 if (elements == null) { 2616 throw new UnsupportedOperationException(); 2617 } 2618 2619 if (locale == null) { 2620 // Guard against default locale changing concurrently. 2621 locale = Locale.getDefault(); 2622 } 2623 2624 int len = elements.length; 2625 for (int i = 0; i < len; i++) { 2626 elements[i].printTo(buf, instant, chrono, displayOffset, displayZone, locale); 2627 } 2628 } 2629 2630 public void printTo( 2631 Writer out, long instant, Chronology chrono, 2632 int displayOffset, DateTimeZone displayZone, Locale locale) throws IOException { 2633 DateTimePrinter[] elements = iPrinters; 2634 if (elements == null) { 2635 throw new UnsupportedOperationException(); 2636 } 2637 2638 if (locale == null) { 2639 // Guard against default locale changing concurrently. 2640 locale = Locale.getDefault(); 2641 } 2642 2643 int len = elements.length; 2644 for (int i = 0; i < len; i++) { 2645 elements[i].printTo(out, instant, chrono, displayOffset, displayZone, locale); 2646 } 2647 } 2648 2649 public void printTo(StringBuffer buf, ReadablePartial partial, Locale locale) { 2650 DateTimePrinter[] elements = iPrinters; 2651 if (elements == null) { 2652 throw new UnsupportedOperationException(); 2653 } 2654 2655 if (locale == null) { 2656 // Guard against default locale changing concurrently. 2657 locale = Locale.getDefault(); 2658 } 2659 2660 int len = elements.length; 2661 for (int i=0; i<len; i++) { 2662 elements[i].printTo(buf, partial, locale); 2663 } 2664 } 2665 2666 public void printTo(Writer out, ReadablePartial partial, Locale locale) throws IOException { 2667 DateTimePrinter[] elements = iPrinters; 2668 if (elements == null) { 2669 throw new UnsupportedOperationException(); 2670 } 2671 2672 if (locale == null) { 2673 // Guard against default locale changing concurrently. 2674 locale = Locale.getDefault(); 2675 } 2676 2677 int len = elements.length; 2678 for (int i=0; i<len; i++) { 2679 elements[i].printTo(out, partial, locale); 2680 } 2681 } 2682 2683 public int estimateParsedLength() { 2684 return iParsedLengthEstimate; 2685 } 2686 2687 public int parseInto(DateTimeParserBucket bucket, String text, int position) { 2688 DateTimeParser[] elements = iParsers; 2689 if (elements == null) { 2690 throw new UnsupportedOperationException(); 2691 } 2692 2693 int len = elements.length; 2694 for (int i=0; i<len && position >= 0; i++) { 2695 position = elements[i].parseInto(bucket, text, position); 2696 } 2697 return position; 2698 } 2699 2700 boolean isPrinter() { 2701 return iPrinters != null; 2702 } 2703 2704 boolean isParser() { 2705 return iParsers != null; 2706 } 2707 2708 /** 2709 * Processes the element pairs, putting results into the given printer 2710 * and parser lists. 2711 */ 2712 private void decompose(List<Object> elementPairs, List<Object> printerList, List<Object> parserList) { 2713 int size = elementPairs.size(); 2714 for (int i=0; i<size; i+=2) { 2715 Object element = elementPairs.get(i); 2716 if (element instanceof DateTimePrinter) { 2717 if (element instanceof Composite) { 2718 addArrayToList(printerList, ((Composite)element).iPrinters); 2719 } else { 2720 printerList.add(element); 2721 } 2722 } 2723 2724 element = elementPairs.get(i + 1); 2725 if (element instanceof DateTimeParser) { 2726 if (element instanceof Composite) { 2727 addArrayToList(parserList, ((Composite)element).iParsers); 2728 } else { 2729 parserList.add(element); 2730 } 2731 } 2732 } 2733 } 2734 2735 private void addArrayToList(List<Object> list, Object[] array) { 2736 if (array != null) { 2737 for (int i=0; i<array.length; i++) { 2738 list.add(array[i]); 2739 } 2740 } 2741 } 2742 } 2743 2744 //----------------------------------------------------------------------- 2745 static class MatchingParser 2746 implements DateTimeParser { 2747 2748 private final DateTimeParser[] iParsers; 2749 private final int iParsedLengthEstimate; 2750 2751 MatchingParser(DateTimeParser[] parsers) { 2752 super(); 2753 iParsers = parsers; 2754 int est = 0; 2755 for (int i=parsers.length; --i>=0 ;) { 2756 DateTimeParser parser = parsers[i]; 2757 if (parser != null) { 2758 int len = parser.estimateParsedLength(); 2759 if (len > est) { 2760 est = len; 2761 } 2762 } 2763 } 2764 iParsedLengthEstimate = est; 2765 } 2766 2767 public int estimateParsedLength() { 2768 return iParsedLengthEstimate; 2769 } 2770 2771 public int parseInto(DateTimeParserBucket bucket, String text, int position) { 2772 DateTimeParser[] parsers = iParsers; 2773 int length = parsers.length; 2774 2775 final Object originalState = bucket.saveState(); 2776 boolean isOptional = false; 2777 2778 int bestValidPos = position; 2779 Object bestValidState = null; 2780 2781 int bestInvalidPos = position; 2782 2783 for (int i=0; i<length; i++) { 2784 DateTimeParser parser = parsers[i]; 2785 if (parser == null) { 2786 // The empty parser wins only if nothing is better. 2787 if (bestValidPos <= position) { 2788 return position; 2789 } 2790 isOptional = true; 2791 break; 2792 } 2793 int parsePos = parser.parseInto(bucket, text, position); 2794 if (parsePos >= position) { 2795 if (parsePos > bestValidPos) { 2796 if (parsePos >= text.length() || 2797 (i + 1) >= length || parsers[i + 1] == null) { 2798 2799 // Completely parsed text or no more parsers to 2800 // check. Skip the rest. 2801 return parsePos; 2802 } 2803 bestValidPos = parsePos; 2804 bestValidState = bucket.saveState(); 2805 } 2806 } else { 2807 if (parsePos < 0) { 2808 parsePos = ~parsePos; 2809 if (parsePos > bestInvalidPos) { 2810 bestInvalidPos = parsePos; 2811 } 2812 } 2813 } 2814 bucket.restoreState(originalState); 2815 } 2816 2817 if (bestValidPos > position || (bestValidPos == position && isOptional)) { 2818 // Restore the state to the best valid parse. 2819 if (bestValidState != null) { 2820 bucket.restoreState(bestValidState); 2821 } 2822 return bestValidPos; 2823 } 2824 2825 return ~bestInvalidPos; 2826 } 2827 } 2828 2829 }