001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * https://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.apache.commons.lang3.time; 018 019import java.io.IOException; 020import java.io.ObjectInputStream; 021import java.io.Serializable; 022import java.text.DateFormat; 023import java.text.DateFormatSymbols; 024import java.text.FieldPosition; 025import java.text.SimpleDateFormat; 026import java.util.ArrayList; 027import java.util.Calendar; 028import java.util.Date; 029import java.util.List; 030import java.util.Locale; 031import java.util.TimeZone; 032import java.util.concurrent.ConcurrentHashMap; 033import java.util.concurrent.ConcurrentMap; 034 035import org.apache.commons.lang3.CharUtils; 036import org.apache.commons.lang3.ClassUtils; 037import org.apache.commons.lang3.LocaleUtils; 038import org.apache.commons.lang3.exception.ExceptionUtils; 039 040/** 041 * FastDatePrinter is a fast and thread-safe version of 042 * {@link java.text.SimpleDateFormat}. 043 * 044 * <p> 045 * To obtain a FastDatePrinter, use {@link FastDateFormat#getInstance(String, TimeZone, Locale)} 046 * or another variation of the factory methods of {@link FastDateFormat}. 047 * </p> 048 * 049 * <p> 050 * Since FastDatePrinter is thread safe, you can use a static member instance: 051 * </p> 052 * {@code 053 * private static final DatePrinter DATE_PRINTER = FastDateFormat.getInstance("yyyy-MM-dd"); 054 * } 055 * 056 * <p> 057 * This class can be used as a direct replacement to 058 * {@link SimpleDateFormat} in most formatting situations. 059 * This class is especially useful in multi-threaded server environments. 060 * {@link SimpleDateFormat} is not thread-safe in any JDK version, 061 * nor will it be as Sun have closed the bug/RFE. 062 * </p> 063 * 064 * <p> 065 * Only formatting is supported by this class, but all patterns are compatible with 066 * SimpleDateFormat (except time zones and some year patterns - see below). 067 * </p> 068 * 069 * <p> 070 * Java 1.4 introduced a new pattern letter, {@code 'Z'}, to represent 071 * time zones in RFC822 format (for example, {@code +0800} or {@code -1100}). 072 * This pattern letter can be used here (on all JDK versions). 073 * </p> 074 * 075 * <p> 076 * In addition, the pattern {@code 'ZZ'} has been made to represent 077 * ISO 8601 extended format time zones (for example, {@code +08:00} or {@code -11:00}). 078 * This introduces a minor incompatibility with Java 1.4, but at a gain of 079 * useful functionality. 080 * </p> 081 * 082 * <p> 083 * Starting with JDK7, ISO 8601 support was added using the pattern {@code 'X'}. 084 * To maintain compatibility, {@code 'ZZ'} will continue to be supported, but using 085 * one of the {@code 'X'} formats is recommended. 086 * 087 * <p> 088 * Javadoc cites for the year pattern: <i>For formatting, if the number of 089 * pattern letters is 2, the year is truncated to 2 digits; otherwise it is 090 * interpreted as a number.</i> Starting with Java 1.7 a pattern of 'Y' or 091 * 'YYY' will be formatted as '2003', while it was '03' in former Java 092 * versions. FastDatePrinter implements the behavior of Java 7. 093 * </p> 094 * 095 * @since 3.2 096 * @see FastDateParser 097 */ 098public class FastDatePrinter implements DatePrinter, Serializable { 099 // A lot of the speed in this class comes from caching, but some comes 100 // from the special int to StringBuffer conversion. 101 // 102 // The following produces a padded 2-digit number: 103 // buffer.append((char)(value / 10 + '0')); 104 // buffer.append((char)(value % 10 + '0')); 105 // 106 // Note that the fastest append to StringBuffer is a single char (used here). 107 // Note that Integer.toString() is not called, the conversion is simply 108 // taking the value and adding (mathematically) the ASCII value for '0'. 109 // So, don't change this code! It works and is very fast. 110 111 /** 112 * Inner class to output a constant single character. 113 */ 114 private static final class CharacterLiteral implements Rule { 115 private final char value; 116 117 /** 118 * Constructs a new instance of {@link CharacterLiteral} 119 * to hold the specified value. 120 * 121 * @param value The character literal 122 */ 123 CharacterLiteral(final char value) { 124 this.value = value; 125 } 126 127 /** 128 * {@inheritDoc} 129 */ 130 @Override 131 public void appendTo(final Appendable buffer, final Calendar calendar) throws IOException { 132 buffer.append(value); 133 } 134 135 /** 136 * {@inheritDoc} 137 */ 138 @Override 139 public int estimateLength() { 140 return 1; 141 } 142 } 143 144 /** 145 * Inner class to output the numeric day in week. 146 */ 147 private static final class DayInWeekField implements NumberRule { 148 private final NumberRule rule; 149 150 DayInWeekField(final NumberRule rule) { 151 this.rule = rule; 152 } 153 154 @Override 155 public void appendTo(final Appendable buffer, final Calendar calendar) throws IOException { 156 final int value = calendar.get(Calendar.DAY_OF_WEEK); 157 rule.appendTo(buffer, value == Calendar.SUNDAY ? 7 : value - 1); 158 } 159 160 @Override 161 public void appendTo(final Appendable buffer, final int value) throws IOException { 162 rule.appendTo(buffer, value); 163 } 164 165 @Override 166 public int estimateLength() { 167 return rule.estimateLength(); 168 } 169 } 170 171 /** 172 * Inner class to output a time zone as a number {@code +/-HHMM} 173 * or {@code +/-HH:MM}. 174 */ 175 private static final class Iso8601_Rule implements Rule { 176 177 // Sign TwoDigitHours or Z 178 static final Iso8601_Rule ISO8601_HOURS = new Iso8601_Rule(3); 179 // Sign TwoDigitHours Minutes or Z 180 static final Iso8601_Rule ISO8601_HOURS_MINUTES = new Iso8601_Rule(5); 181 // Sign TwoDigitHours : Minutes or Z 182 static final Iso8601_Rule ISO8601_HOURS_COLON_MINUTES = new Iso8601_Rule(6); 183 184 /** 185 * Gets the ISO 8601 formatting rule. 186 * 187 * @param tokenLen A token indicating the length of the TimeZone String to be formatted. 188 * @return An Iso8601_Rule that can format TimeZone String of length {@code tokenLen}. If no such 189 * rule exists, an IllegalArgumentException will be thrown. 190 */ 191 static Iso8601_Rule getRule(final int tokenLen) { 192 switch (tokenLen) { 193 case 1: 194 return ISO8601_HOURS; 195 case 2: 196 return ISO8601_HOURS_MINUTES; 197 case 3: 198 return ISO8601_HOURS_COLON_MINUTES; 199 default: 200 throw new IllegalArgumentException("invalid number of X"); 201 } 202 } 203 204 private final int length; 205 206 /** 207 * Constructs an instance of {@code Iso8601_Rule} with the specified properties. 208 * 209 * @param length The number of characters in output (unless Z is output). 210 */ 211 Iso8601_Rule(final int length) { 212 this.length = length; 213 } 214 215 /** 216 * {@inheritDoc} 217 */ 218 @Override 219 public void appendTo(final Appendable buffer, final Calendar calendar) throws IOException { 220 int offset = calendar.get(Calendar.ZONE_OFFSET) + calendar.get(Calendar.DST_OFFSET); 221 if (offset == 0) { 222 buffer.append("Z"); 223 return; 224 } 225 226 if (offset < 0) { 227 buffer.append('-'); 228 offset = -offset; 229 } else { 230 buffer.append('+'); 231 } 232 233 final int hours = offset / (60 * 60 * 1000); 234 appendDigits(buffer, hours); 235 236 if (length < 5) { 237 return; 238 } 239 240 if (length == 6) { 241 buffer.append(':'); 242 } 243 244 final int minutes = offset / (60 * 1000) - 60 * hours; 245 appendDigits(buffer, minutes); 246 } 247 248 /** 249 * {@inheritDoc} 250 */ 251 @Override 252 public int estimateLength() { 253 return length; 254 } 255 } 256 257 /** 258 * Inner class defining a numeric rule. 259 */ 260 private interface NumberRule extends Rule { 261 262 /** 263 * Appends the specified value to the output buffer based on the rule implementation. 264 * 265 * @param buffer The output buffer. 266 * @param value The value to be appended. 267 * @throws IOException Thrown if an I/O error occurs. 268 */ 269 void appendTo(Appendable buffer, int value) throws IOException; 270 } 271 272 /** 273 * Inner class to output a padded number. 274 */ 275 private static final class PaddedNumberField implements NumberRule { 276 // Note: This is final to avoid Spotbugs CT_CONSTRUCTOR_THROW 277 private final int field; 278 private final int size; 279 280 /** 281 * Constructs an instance of {@link PaddedNumberField}. 282 * 283 * @param field The field. 284 * @param size size of the output field. 285 */ 286 PaddedNumberField(final int field, final int size) { 287 if (size < 3) { 288 // Should use UnpaddedNumberField or TwoDigitNumberField. 289 throw new IllegalArgumentException(); 290 } 291 this.field = field; 292 this.size = size; 293 } 294 295 /** 296 * {@inheritDoc} 297 */ 298 @Override 299 public void appendTo(final Appendable buffer, final Calendar calendar) throws IOException { 300 appendTo(buffer, calendar.get(field)); 301 } 302 303 /** 304 * {@inheritDoc} 305 */ 306 @Override 307 public /* final */ void appendTo(final Appendable buffer, final int value) throws IOException { 308 // Checkstyle complains about redundant qualifier 309 appendFullDigits(buffer, value, size); 310 } 311 312 /** 313 * {@inheritDoc} 314 */ 315 @Override 316 public int estimateLength() { 317 return size; 318 } 319 } 320 321 /** 322 * Inner class defining a rule. 323 */ 324 private interface Rule { 325 326 /** 327 * Appends the value of the specified calendar to the output buffer based on the rule implementation. 328 * 329 * @param buf The output buffer. 330 * @param calendar calendar to be appended. 331 * @throws IOException Thrown if an I/O error occurs. 332 */ 333 void appendTo(Appendable buf, Calendar calendar) throws IOException; 334 335 /** 336 * Returns the estimated length of the result. 337 * 338 * @return The estimated length of the result. 339 */ 340 int estimateLength(); 341 } 342 343 /** 344 * Inner class to output a constant string. 345 */ 346 private static final class StringLiteral implements Rule { 347 private final String value; 348 349 /** 350 * Constructs a new instance of {@link StringLiteral} 351 * to hold the specified value. 352 * 353 * @param value The string literal. 354 */ 355 StringLiteral(final String value) { 356 this.value = value; 357 } 358 359 /** 360 * {@inheritDoc} 361 */ 362 @Override 363 public void appendTo(final Appendable buffer, final Calendar calendar) throws IOException { 364 buffer.append(value); 365 } 366 367 /** 368 * {@inheritDoc} 369 */ 370 @Override 371 public int estimateLength() { 372 return value.length(); 373 } 374 } 375 376 /** 377 * Inner class to output one of a set of values. 378 */ 379 private static final class TextField implements Rule { 380 private final int field; 381 private final String[] values; 382 383 /** 384 * Constructs an instance of {@link TextField} 385 * with the specified field and values. 386 * 387 * @param field The field. 388 * @param values The field values. 389 */ 390 TextField(final int field, final String[] values) { 391 this.field = field; 392 this.values = values; 393 } 394 395 /** 396 * {@inheritDoc} 397 */ 398 @Override 399 public void appendTo(final Appendable buffer, final Calendar calendar) throws IOException { 400 buffer.append(values[calendar.get(field)]); 401 } 402 403 /** 404 * {@inheritDoc} 405 */ 406 @Override 407 public int estimateLength() { 408 int max = 0; 409 for (int i = values.length; --i >= 0;) { 410 final int len = values[i].length(); 411 if (len > max) { 412 max = len; 413 } 414 } 415 return max; 416 } 417 } 418 419 /** 420 * Inner class that acts as a compound key for time zone names. 421 */ 422 private static final class TimeZoneDisplayKey { 423 private final TimeZone timeZone; 424 private final int style; 425 private final Locale locale; 426 427 /** 428 * Constructs an instance of {@link TimeZoneDisplayKey} with the specified properties. 429 * 430 * @param timeZone The time zone. 431 * @param daylight adjust the style for daylight saving time if {@code true}. 432 * @param style The time zone style. 433 * @param locale The time zone locale. 434 */ 435 TimeZoneDisplayKey(final TimeZone timeZone, 436 final boolean daylight, final int style, final Locale locale) { 437 this.timeZone = timeZone; 438 if (daylight) { 439 this.style = style | 0x80000000; 440 } else { 441 this.style = style; 442 } 443 this.locale = LocaleUtils.toLocale(locale); 444 } 445 446 /** 447 * {@inheritDoc} 448 */ 449 @Override 450 public boolean equals(final Object obj) { 451 if (this == obj) { 452 return true; 453 } 454 if (obj instanceof TimeZoneDisplayKey) { 455 final TimeZoneDisplayKey other = (TimeZoneDisplayKey) obj; 456 return 457 timeZone.equals(other.timeZone) && 458 style == other.style && 459 locale.equals(other.locale); 460 } 461 return false; 462 } 463 464 /** 465 * {@inheritDoc} 466 */ 467 @Override 468 public int hashCode() { 469 return (style * 31 + locale.hashCode()) * 31 + timeZone.hashCode(); 470 } 471 } 472 473 /** 474 * Inner class to output a time zone name. 475 */ 476 private static final class TimeZoneNameRule implements Rule { 477 private final Locale locale; 478 private final int style; 479 private final String standard; 480 private final String daylight; 481 482 /** 483 * Constructs an instance of {@link TimeZoneNameRule} with the specified properties. 484 * 485 * @param timeZone The time zone. 486 * @param locale The locale. 487 * @param style The style. 488 */ 489 TimeZoneNameRule(final TimeZone timeZone, final Locale locale, final int style) { 490 this.locale = LocaleUtils.toLocale(locale); 491 this.style = style; 492 this.standard = getTimeZoneDisplay(timeZone, false, style, locale); 493 this.daylight = getTimeZoneDisplay(timeZone, true, style, locale); 494 } 495 496 /** 497 * {@inheritDoc} 498 */ 499 @Override 500 public void appendTo(final Appendable buffer, final Calendar calendar) throws IOException { 501 final TimeZone zone = calendar.getTimeZone(); 502 final boolean daylight = calendar.get(Calendar.DST_OFFSET) != 0; 503 buffer.append(getTimeZoneDisplay(zone, daylight, style, locale)); 504 } 505 506 /** 507 * {@inheritDoc} 508 */ 509 @Override 510 public int estimateLength() { 511 // We have no access to the Calendar object that will be passed to 512 // appendTo so base estimate on the TimeZone passed to the 513 // constructor 514 return Math.max(standard.length(), daylight.length()); 515 } 516 } 517 518 /** 519 * Inner class to output a time zone as a number {@code +/-HHMM} 520 * or {@code +/-HH:MM}. 521 */ 522 private static final class TimeZoneNumberRule implements Rule { 523 static final TimeZoneNumberRule INSTANCE_COLON = new TimeZoneNumberRule(true); 524 static final TimeZoneNumberRule INSTANCE_NO_COLON = new TimeZoneNumberRule(false); 525 526 private final boolean colon; 527 528 /** 529 * Constructs an instance of {@link TimeZoneNumberRule} with the specified properties. 530 * 531 * @param colon add colon between HH and MM in the output if {@code true}. 532 */ 533 TimeZoneNumberRule(final boolean colon) { 534 this.colon = colon; 535 } 536 537 /** 538 * {@inheritDoc} 539 */ 540 @Override 541 public void appendTo(final Appendable buffer, final Calendar calendar) throws IOException { 542 543 int offset = calendar.get(Calendar.ZONE_OFFSET) + calendar.get(Calendar.DST_OFFSET); 544 545 if (offset < 0) { 546 buffer.append('-'); 547 offset = -offset; 548 } else { 549 buffer.append('+'); 550 } 551 552 final int hours = offset / (60 * 60 * 1000); 553 appendDigits(buffer, hours); 554 555 if (colon) { 556 buffer.append(':'); 557 } 558 559 final int minutes = offset / (60 * 1000) - 60 * hours; 560 appendDigits(buffer, minutes); 561 } 562 563 /** 564 * {@inheritDoc} 565 */ 566 @Override 567 public int estimateLength() { 568 return 5; 569 } 570 } 571 572 /** 573 * Inner class to output the twelve hour field. 574 */ 575 private static final class TwelveHourField implements NumberRule { 576 private final NumberRule rule; 577 578 /** 579 * Constructs an instance of {@link TwelveHourField} with the specified 580 * {@link NumberRule}. 581 * 582 * @param rule The rule. 583 */ 584 TwelveHourField(final NumberRule rule) { 585 this.rule = rule; 586 } 587 588 /** 589 * {@inheritDoc} 590 */ 591 @Override 592 public void appendTo(final Appendable buffer, final Calendar calendar) throws IOException { 593 int value = calendar.get(Calendar.HOUR); 594 if (value == 0) { 595 value = calendar.getLeastMaximum(Calendar.HOUR) + 1; 596 } 597 rule.appendTo(buffer, value); 598 } 599 600 /** 601 * {@inheritDoc} 602 */ 603 @Override 604 public void appendTo(final Appendable buffer, final int value) throws IOException { 605 rule.appendTo(buffer, value); 606 } 607 608 /** 609 * {@inheritDoc} 610 */ 611 @Override 612 public int estimateLength() { 613 return rule.estimateLength(); 614 } 615 } 616 617 /** 618 * Inner class to output the twenty four hour field. 619 */ 620 private static final class TwentyFourHourField implements NumberRule { 621 private final NumberRule rule; 622 623 /** 624 * Constructs an instance of {@link TwentyFourHourField} with the specified 625 * {@link NumberRule}. 626 * 627 * @param rule The rule. 628 */ 629 TwentyFourHourField(final NumberRule rule) { 630 this.rule = rule; 631 } 632 633 /** 634 * {@inheritDoc} 635 */ 636 @Override 637 public void appendTo(final Appendable buffer, final Calendar calendar) throws IOException { 638 int value = calendar.get(Calendar.HOUR_OF_DAY); 639 if (value == 0) { 640 value = calendar.getMaximum(Calendar.HOUR_OF_DAY) + 1; 641 } 642 rule.appendTo(buffer, value); 643 } 644 645 /** 646 * {@inheritDoc} 647 */ 648 @Override 649 public void appendTo(final Appendable buffer, final int value) throws IOException { 650 rule.appendTo(buffer, value); 651 } 652 653 /** 654 * {@inheritDoc} 655 */ 656 @Override 657 public int estimateLength() { 658 return rule.estimateLength(); 659 } 660 } 661 662 /** 663 * Inner class to output a two digit month. 664 */ 665 private static final class TwoDigitMonthField implements NumberRule { 666 static final TwoDigitMonthField INSTANCE = new TwoDigitMonthField(); 667 668 /** 669 * Constructs an instance of {@link TwoDigitMonthField}. 670 */ 671 TwoDigitMonthField() { 672 } 673 674 /** 675 * {@inheritDoc} 676 */ 677 @Override 678 public void appendTo(final Appendable buffer, final Calendar calendar) throws IOException { 679 appendTo(buffer, calendar.get(Calendar.MONTH) + 1); 680 } 681 682 /** 683 * {@inheritDoc} 684 */ 685 @Override 686 public void appendTo(final Appendable buffer, final int value) throws IOException { 687 appendDigits(buffer, value); 688 } 689 690 /** 691 * {@inheritDoc} 692 */ 693 @Override 694 public int estimateLength() { 695 return 2; 696 } 697 } 698 699 /** 700 * Inner class to output a two digit number. 701 */ 702 private static final class TwoDigitNumberField implements NumberRule { 703 private final int field; 704 705 /** 706 * Constructs an instance of {@link TwoDigitNumberField} with the specified field. 707 * 708 * @param field The field 709 */ 710 TwoDigitNumberField(final int field) { 711 this.field = field; 712 } 713 714 /** 715 * {@inheritDoc} 716 */ 717 @Override 718 public void appendTo(final Appendable buffer, final Calendar calendar) throws IOException { 719 appendTo(buffer, calendar.get(field)); 720 } 721 722 /** 723 * {@inheritDoc} 724 */ 725 @Override 726 public void appendTo(final Appendable buffer, final int value) throws IOException { 727 if (value < 100) { 728 appendDigits(buffer, value); 729 } else { 730 appendFullDigits(buffer, value, 2); 731 } 732 } 733 734 /** 735 * {@inheritDoc} 736 */ 737 @Override 738 public int estimateLength() { 739 return 2; 740 } 741 } 742 743 /** 744 * Inner class to output a two digit year. 745 */ 746 private static final class TwoDigitYearField implements NumberRule { 747 static final TwoDigitYearField INSTANCE = new TwoDigitYearField(); 748 749 /** 750 * Constructs an instance of {@link TwoDigitYearField}. 751 */ 752 TwoDigitYearField() { 753 } 754 755 /** 756 * {@inheritDoc} 757 */ 758 @Override 759 public void appendTo(final Appendable buffer, final Calendar calendar) throws IOException { 760 appendTo(buffer, calendar.get(Calendar.YEAR) % 100); 761 } 762 763 /** 764 * {@inheritDoc} 765 */ 766 @Override 767 public void appendTo(final Appendable buffer, final int value) throws IOException { 768 appendDigits(buffer, value % 100); 769 } 770 771 /** 772 * {@inheritDoc} 773 */ 774 @Override 775 public int estimateLength() { 776 return 2; 777 } 778 } 779 780 /** 781 * Inner class to output an unpadded month. 782 */ 783 private static final class UnpaddedMonthField implements NumberRule { 784 static final UnpaddedMonthField INSTANCE = new UnpaddedMonthField(); 785 786 /** 787 * Constructs an instance of {@link UnpaddedMonthField}. 788 */ 789 UnpaddedMonthField() { 790 } 791 792 /** 793 * {@inheritDoc} 794 */ 795 @Override 796 public void appendTo(final Appendable buffer, final Calendar calendar) throws IOException { 797 appendTo(buffer, calendar.get(Calendar.MONTH) + 1); 798 } 799 800 /** 801 * {@inheritDoc} 802 */ 803 @Override 804 public void appendTo(final Appendable buffer, final int value) throws IOException { 805 if (value < 10) { 806 buffer.append((char) (value + '0')); 807 } else { 808 appendDigits(buffer, value); 809 } 810 } 811 812 /** 813 * {@inheritDoc} 814 */ 815 @Override 816 public int estimateLength() { 817 return 2; 818 } 819 } 820 821 /** 822 * Inner class to output an unpadded number. 823 */ 824 private static final class UnpaddedNumberField implements NumberRule { 825 private final int field; 826 827 /** 828 * Constructs an instance of {@link UnpaddedNumberField} with the specified field. 829 * 830 * @param field The field. 831 */ 832 UnpaddedNumberField(final int field) { 833 this.field = field; 834 } 835 836 /** 837 * {@inheritDoc} 838 */ 839 @Override 840 public void appendTo(final Appendable buffer, final Calendar calendar) throws IOException { 841 appendTo(buffer, calendar.get(field)); 842 } 843 844 /** 845 * {@inheritDoc} 846 */ 847 @Override 848 public void appendTo(final Appendable buffer, final int value) throws IOException { 849 if (value < 10) { 850 buffer.append((char) (value + '0')); 851 } else if (value < 100) { 852 appendDigits(buffer, value); 853 } else { 854 appendFullDigits(buffer, value, 1); 855 } 856 } 857 858 /** 859 * {@inheritDoc} 860 */ 861 @Override 862 public int estimateLength() { 863 return 4; 864 } 865 } 866 867 /** 868 * Inner class to output the numeric day in week. 869 */ 870 private static final class WeekYear implements NumberRule { 871 private final NumberRule rule; 872 873 WeekYear(final NumberRule rule) { 874 this.rule = rule; 875 } 876 877 @Override 878 public void appendTo(final Appendable buffer, final Calendar calendar) throws IOException { 879 // Some Calendar implementations (JapaneseImperialCalendar) do not support week-dates. 880 // Fall back to Calendar.YEAR in that case. 881 appendTo(buffer, calendar.isWeekDateSupported() ? calendar.getWeekYear() : calendar.get(Calendar.YEAR)); 882 } 883 884 @Override 885 public void appendTo(final Appendable buffer, int value) throws IOException { 886 // A week year is proleptic, so a BC date gives a negative value the digit rules cannot render. 887 if (value < 0) { 888 buffer.append('-'); 889 value = -value; 890 } 891 rule.appendTo(buffer, value); 892 } 893 894 @Override 895 public int estimateLength() { 896 return rule.estimateLength(); 897 } 898 } 899 900 /** Empty array. */ 901 private static final Rule[] EMPTY_RULE_ARRAY = {}; 902 903 /** 904 * Required for serialization support. 905 * 906 * @see java.io.Serializable 907 */ 908 private static final long serialVersionUID = 1L; 909 910 /** 911 * FULL locale dependent date or time style. 912 */ 913 public static final int FULL = DateFormat.FULL; 914 915 /** 916 * LONG locale dependent date or time style. 917 */ 918 public static final int LONG = DateFormat.LONG; 919 920 /** 921 * MEDIUM locale dependent date or time style. 922 */ 923 public static final int MEDIUM = DateFormat.MEDIUM; 924 925 /** 926 * SHORT locale dependent date or time style. 927 */ 928 public static final int SHORT = DateFormat.SHORT; 929 930 private static final int MAX_DIGITS = 10; // log10(Integer.MAX_VALUE) ~= 9.3 931 932 private static final ConcurrentMap<TimeZoneDisplayKey, String> timeZoneDisplayCache = new ConcurrentHashMap<>(7); 933 934 /** 935 * Appends two digits to the given buffer. 936 * 937 * @param buffer The buffer to append to. 938 * @param value The value to append digits from. 939 * @throws IOException Thrown if an I/O error occurs. 940 */ 941 private static void appendDigits(final Appendable buffer, final int value) throws IOException { 942 buffer.append((char) (value / 10 + '0')); 943 buffer.append((char) (value % 10 + '0')); 944 } 945 946 /** 947 * Appends all digits to the given buffer. 948 * 949 * @param buffer The buffer to append to. 950 * @param value The value to append digits from. 951 * @param minFieldWidth Minimum field width. 952 * @throws IOException Thrown if an I/O error occurs. 953 */ 954 private static void appendFullDigits(final Appendable buffer, int value, int minFieldWidth) throws IOException { 955 // specialized paths for 1 to 4 digits -> avoid the memory allocation from the temporary work array 956 // see LANG-1248 957 if (value < 10000) { 958 // less memory allocation path works for four digits or less 959 960 int nDigits = 4; 961 if (value < 1000) { 962 --nDigits; 963 if (value < 100) { 964 --nDigits; 965 if (value < 10) { 966 --nDigits; 967 } 968 } 969 } 970 // left zero pad 971 for (int i = minFieldWidth - nDigits; i > 0; --i) { 972 buffer.append('0'); 973 } 974 975 switch (nDigits) { 976 case 4: 977 buffer.append((char) (value / 1000 + '0')); 978 value %= 1000; 979 // falls-through 980 case 3: 981 if (value >= 100) { 982 buffer.append((char) (value / 100 + '0')); 983 value %= 100; 984 } else { 985 buffer.append('0'); 986 } 987 // falls-through 988 case 2: 989 if (value >= 10) { 990 buffer.append((char) (value / 10 + '0')); 991 value %= 10; 992 } else { 993 buffer.append('0'); 994 } 995 // falls-through 996 case 1: 997 buffer.append((char) (value + '0')); 998 } 999 } else { 1000 // more memory allocation path works for any digits 1001 1002 // build up decimal representation in reverse 1003 final char[] work = new char[MAX_DIGITS]; 1004 int digit = 0; 1005 while (value != 0) { 1006 work[digit++] = (char) (value % 10 + '0'); 1007 value /= 10; 1008 } 1009 1010 // pad with zeros 1011 while (digit < minFieldWidth) { 1012 buffer.append('0'); 1013 --minFieldWidth; 1014 } 1015 1016 // reverse 1017 while (--digit >= 0) { 1018 buffer.append(work[digit]); 1019 } 1020 } 1021 } 1022 1023 static void clear() { 1024 timeZoneDisplayCache.clear(); 1025 } 1026 1027 /** 1028 * Gets the time zone display name, using a cache for performance. 1029 * 1030 * @param tz The zone to query. 1031 * @param daylight true if daylight savings. 1032 * @param style The style to use {@link TimeZone#LONG} or {@link TimeZone#SHORT}. 1033 * @param locale The locale to use. 1034 * @return The textual name of the time zone. 1035 */ 1036 static String getTimeZoneDisplay(final TimeZone tz, final boolean daylight, final int style, final Locale locale) { 1037 final TimeZoneDisplayKey key = new TimeZoneDisplayKey(tz, daylight, style, locale); 1038 // Bound the cache: it is static and process-lifetime, and custom time zone IDs give the 1039 // key unbounded cardinality, which would otherwise pin memory forever. 1040 if (timeZoneDisplayCache.size() >= AbstractFormatCache.MAX_CACHE_SIZE && !timeZoneDisplayCache.containsKey(key)) { 1041 timeZoneDisplayCache.clear(); 1042 } 1043 // This is a very slow call, so cache the results. 1044 return timeZoneDisplayCache.computeIfAbsent(key, k -> tz.getDisplayName(daylight, style, locale)); 1045 } 1046 1047 /** 1048 * The pattern. 1049 */ 1050 private final String pattern; 1051 1052 /** 1053 * The time zone. 1054 */ 1055 private final TimeZone timeZone; 1056 1057 /** 1058 * The locale. 1059 */ 1060 private final Locale locale; 1061 1062 /** 1063 * The parsed rules. 1064 */ 1065 private transient Rule[] rules; 1066 1067 /** 1068 * The estimated maximum length. 1069 */ 1070 private transient int maxLengthEstimate; 1071 1072 /** 1073 * Constructs a new FastDatePrinter. 1074 * Use {@link FastDateFormat#getInstance(String, TimeZone, Locale)} or another variation of the 1075 * factory methods of {@link FastDateFormat} to get a cached FastDatePrinter instance. 1076 * 1077 * @param pattern {@link java.text.SimpleDateFormat} compatible pattern. 1078 * @param timeZone non-null time zone to use. 1079 * @param locale non-null locale to use. 1080 * @throws NullPointerException Thrown if pattern, timeZone, or locale is null. 1081 */ 1082 protected FastDatePrinter(final String pattern, final TimeZone timeZone, final Locale locale) { 1083 this.pattern = pattern; 1084 // TimeZone is mutable and instances are shared through the FastDateFormat cache. 1085 this.timeZone = (TimeZone) timeZone.clone(); 1086 this.locale = LocaleUtils.toLocale(locale); 1087 init(); 1088 } 1089 1090 /** 1091 * Performs the formatting by applying the rules to the 1092 * specified calendar. 1093 * 1094 * @param calendar The calendar to format. 1095 * @param buf The buffer to format into. 1096 * @param <B> The Appendable class type, usually StringBuilder or StringBuffer. 1097 * @return The specified string buffer. 1098 */ 1099 private <B extends Appendable> B applyRules(final Calendar calendar, final B buf) { 1100 try { 1101 for (final Rule rule : rules) { 1102 rule.appendTo(buf, calendar); 1103 } 1104 } catch (final IOException ioe) { 1105 ExceptionUtils.asRuntimeException(ioe); 1106 } 1107 return buf; 1108 } 1109 1110 /** 1111 * Performs the formatting by applying the rules to the 1112 * specified calendar. 1113 * 1114 * @param calendar The calendar to format. 1115 * @param buf The buffer to format into. 1116 * @return The specified string buffer. 1117 * @deprecated Use {@link #format(Calendar)} or {@link #format(Calendar, Appendable)} 1118 */ 1119 @Deprecated 1120 protected StringBuffer applyRules(final Calendar calendar, final StringBuffer buf) { 1121 return (StringBuffer) applyRules(calendar, (Appendable) buf); 1122 } 1123 1124 /** 1125 * Creates a String representation of the given Calendar by applying the rules of this printer to it. 1126 * 1127 * @param c The Calendar to apply the rules to. 1128 * @return A String representation of the given Calendar. 1129 */ 1130 private String applyRulesToString(final Calendar c) { 1131 return applyRules(c, new StringBuilder(maxLengthEstimate)).toString(); 1132 } 1133 1134 /** 1135 * Compares two objects for equality. 1136 * 1137 * @param obj The object to compare to. 1138 * @return {@code true} if equal. 1139 */ 1140 @Override 1141 public boolean equals(final Object obj) { 1142 if (!(obj instanceof FastDatePrinter)) { 1143 return false; 1144 } 1145 final FastDatePrinter other = (FastDatePrinter) obj; 1146 return pattern.equals(other.pattern) 1147 && timeZone.equals(other.timeZone) 1148 && locale.equals(other.locale); 1149 } 1150 1151 /* (non-Javadoc) 1152 * @see org.apache.commons.lang3.time.DatePrinter#format(java.util.Calendar) 1153 */ 1154 @Override 1155 public String format(final Calendar calendar) { 1156 return format(calendar, new StringBuilder(maxLengthEstimate)).toString(); 1157 } 1158 1159 /* (non-Javadoc) 1160 * @see org.apache.commons.lang3.time.DatePrinter#format(java.util.Calendar, Appendable) 1161 */ 1162 @Override 1163 public <B extends Appendable> B format(final Calendar calendar, final B buf) { 1164 // Don't edit the given Calendar, clone it only if needed. 1165 Calendar actual = calendar; 1166 if (!calendar.getTimeZone().equals(timeZone)) { 1167 actual = (Calendar) calendar.clone(); 1168 actual.setTimeZone(timeZone); 1169 } 1170 return applyRules(actual, buf); 1171 } 1172 1173 /* (non-Javadoc) 1174 * @see org.apache.commons.lang3.time.DatePrinter#format(java.util.Calendar, StringBuffer) 1175 */ 1176 @Override 1177 public StringBuffer format(final Calendar calendar, final StringBuffer buf) { 1178 // do not pass in calendar directly, this will cause TimeZone of FastDatePrinter to be ignored 1179 return format(calendar.getTime(), buf); 1180 } 1181 1182 /* (non-Javadoc) 1183 * @see org.apache.commons.lang3.time.DatePrinter#format(java.util.Date) 1184 */ 1185 @Override 1186 public String format(final Date date) { 1187 final Calendar c = newCalendar(); 1188 c.setTime(date); 1189 return applyRulesToString(c); 1190 } 1191 1192 /* (non-Javadoc) 1193 * @see org.apache.commons.lang3.time.DatePrinter#format(java.util.Date, Appendable) 1194 */ 1195 @Override 1196 public <B extends Appendable> B format(final Date date, final B buf) { 1197 final Calendar c = newCalendar(); 1198 c.setTime(date); 1199 return applyRules(c, buf); 1200 } 1201 1202 /* (non-Javadoc) 1203 * @see org.apache.commons.lang3.time.DatePrinter#format(java.util.Date, StringBuffer) 1204 */ 1205 @Override 1206 public StringBuffer format(final Date date, final StringBuffer buf) { 1207 final Calendar c = newCalendar(); 1208 c.setTime(date); 1209 return (StringBuffer) applyRules(c, (Appendable) buf); 1210 } 1211 1212 /* (non-Javadoc) 1213 * @see org.apache.commons.lang3.time.DatePrinter#format(long) 1214 */ 1215 @Override 1216 public String format(final long millis) { 1217 final Calendar c = newCalendar(); 1218 c.setTimeInMillis(millis); 1219 return applyRulesToString(c); 1220 } 1221 1222 /* (non-Javadoc) 1223 * @see org.apache.commons.lang3.time.DatePrinter#format(long, Appendable) 1224 */ 1225 @Override 1226 public <B extends Appendable> B format(final long millis, final B buf) { 1227 final Calendar c = newCalendar(); 1228 c.setTimeInMillis(millis); 1229 return applyRules(c, buf); 1230 } 1231 1232 /* (non-Javadoc) 1233 * @see org.apache.commons.lang3.time.DatePrinter#format(long, StringBuffer) 1234 */ 1235 @Override 1236 public StringBuffer format(final long millis, final StringBuffer buf) { 1237 final Calendar c = newCalendar(); 1238 c.setTimeInMillis(millis); 1239 return (StringBuffer) applyRules(c, (Appendable) buf); 1240 } 1241 1242 /** 1243 * Formats a {@link Date}, {@link Calendar} or 1244 * {@link Long} (milliseconds) object. 1245 * 1246 * @param obj The object to format. 1247 * @return The formatted value. 1248 * @since 3.5 1249 */ 1250 String format(final Object obj) { 1251 if (obj instanceof Date) { 1252 return format((Date) obj); 1253 } 1254 if (obj instanceof Calendar) { 1255 return format((Calendar) obj); 1256 } 1257 if (obj instanceof Long) { 1258 return format(((Long) obj).longValue()); 1259 } 1260 throw new IllegalArgumentException("Unknown class: " + ClassUtils.getName(obj, "<null>")); 1261 } 1262 1263 /** 1264 * Formats a {@link Date}, {@link Calendar} or 1265 * {@link Long} (milliseconds) object. 1266 * 1267 * @param obj The object to format. 1268 * @param toAppendTo The buffer to append to. 1269 * @param pos The position; ignored. 1270 * @return The buffer passed in. 1271 * @deprecated Use {{@link #format(Date)}, {{@link #format(Calendar)}, {{@link #format(long)}. 1272 */ 1273 @Deprecated 1274 @Override 1275 public StringBuffer format(final Object obj, final StringBuffer toAppendTo, final FieldPosition pos) { 1276 if (obj instanceof Date) { 1277 return format((Date) obj, toAppendTo); 1278 } 1279 if (obj instanceof Calendar) { 1280 return format((Calendar) obj, toAppendTo); 1281 } 1282 if (obj instanceof Long) { 1283 return format(((Long) obj).longValue(), toAppendTo); 1284 } 1285 throw new IllegalArgumentException("Unknown class: " + ClassUtils.getName(obj, "<null>")); 1286 } 1287 1288 /* (non-Javadoc) 1289 * @see org.apache.commons.lang3.time.DatePrinter#getLocale() 1290 */ 1291 @Override 1292 public Locale getLocale() { 1293 return locale; 1294 } 1295 1296 /** 1297 * Gets an estimate for the maximum string length that the 1298 * formatter will produce. 1299 * 1300 * <p> 1301 * The actual formatted length will almost always be less than or 1302 * equal to this amount. 1303 * </p> 1304 * 1305 * @return The maximum formatted length. 1306 */ 1307 public int getMaxLengthEstimate() { 1308 return maxLengthEstimate; 1309 } 1310 1311 /* (non-Javadoc) 1312 * @see org.apache.commons.lang3.time.DatePrinter#getPattern() 1313 */ 1314 @Override 1315 public String getPattern() { 1316 return pattern; 1317 } 1318 1319 /* (non-Javadoc) 1320 * @see org.apache.commons.lang3.time.DatePrinter#getTimeZone() 1321 */ 1322 @Override 1323 public TimeZone getTimeZone() { 1324 return (TimeZone) timeZone.clone(); 1325 } 1326 1327 /** 1328 * Returns a hash code compatible with equals. 1329 * 1330 * @return A hash code compatible with equals. 1331 */ 1332 @Override 1333 public int hashCode() { 1334 return pattern.hashCode() + 13 * (timeZone.hashCode() + 13 * locale.hashCode()); 1335 } 1336 1337 /** 1338 * Initializes the instance for first use. 1339 */ 1340 private void init() { 1341 final List<Rule> rulesList = parsePattern(); 1342 rules = rulesList.toArray(EMPTY_RULE_ARRAY); 1343 1344 int len = 0; 1345 for (int i = rules.length; --i >= 0;) { 1346 len += rules[i].estimateLength(); 1347 } 1348 1349 maxLengthEstimate = len; 1350 } 1351 1352 /** 1353 * Creates a new Calendar instance. 1354 * 1355 * @return A new Calendar instance. 1356 */ 1357 private Calendar newCalendar() { 1358 return Calendar.getInstance(timeZone, locale); 1359 } 1360 1361 /** 1362 * Returns a list of Rules given a pattern. 1363 * 1364 * @return A {@link List} of Rule objects. 1365 * @throws IllegalArgumentException Thrown if pattern is invalid. 1366 */ 1367 protected List<Rule> parsePattern() { 1368 final DateFormatSymbols symbols = new DateFormatSymbols(locale); 1369 final List<Rule> rules = new ArrayList<>(); 1370 1371 final String[] ERAs = symbols.getEras(); 1372 final String[] months = symbols.getMonths(); 1373 final String[] shortMonths = symbols.getShortMonths(); 1374 final String[] weekdays = symbols.getWeekdays(); 1375 final String[] shortWeekdays = symbols.getShortWeekdays(); 1376 final String[] AmPmStrings = symbols.getAmPmStrings(); 1377 1378 final int length = pattern.length(); 1379 final int[] indexRef = new int[1]; 1380 1381 for (int i = 0; i < length; i++) { 1382 indexRef[0] = i; 1383 final String token = parseToken(pattern, indexRef); 1384 i = indexRef[0]; 1385 1386 final int tokenLen = token.length(); 1387 if (tokenLen == 0) { 1388 break; 1389 } 1390 1391 Rule rule; 1392 final char c = token.charAt(0); 1393 1394 switch (c) { 1395 case 'G': // era designator (text) 1396 rule = new TextField(Calendar.ERA, ERAs); 1397 break; 1398 case 'y': // year (number) 1399 case 'Y': // week year 1400 if (tokenLen == 2) { 1401 rule = TwoDigitYearField.INSTANCE; 1402 } else { 1403 rule = selectNumberRule(Calendar.YEAR, Math.max(tokenLen, 4)); 1404 } 1405 if (c == 'Y') { 1406 rule = new WeekYear((NumberRule) rule); 1407 } 1408 break; 1409 case 'M': // month in year (text and number) 1410 if (tokenLen >= 4) { 1411 rule = new TextField(Calendar.MONTH, months); 1412 } else if (tokenLen == 3) { 1413 rule = new TextField(Calendar.MONTH, shortMonths); 1414 } else if (tokenLen == 2) { 1415 rule = TwoDigitMonthField.INSTANCE; 1416 } else { 1417 rule = UnpaddedMonthField.INSTANCE; 1418 } 1419 break; 1420 case 'L': // month in year (text and number) 1421 if (tokenLen >= 4) { 1422 rule = new TextField(Calendar.MONTH, CalendarUtils.getInstance(locale).getStandaloneLongMonthNames()); 1423 } else if (tokenLen == 3) { 1424 rule = new TextField(Calendar.MONTH, CalendarUtils.getInstance(locale).getStandaloneShortMonthNames()); 1425 } else if (tokenLen == 2) { 1426 rule = TwoDigitMonthField.INSTANCE; 1427 } else { 1428 rule = UnpaddedMonthField.INSTANCE; 1429 } 1430 break; 1431 case 'd': // day in month (number) 1432 rule = selectNumberRule(Calendar.DAY_OF_MONTH, tokenLen); 1433 break; 1434 case 'h': // hour in am/pm (number, 1..12) 1435 rule = new TwelveHourField(selectNumberRule(Calendar.HOUR, tokenLen)); 1436 break; 1437 case 'H': // hour in day (number, 0..23) 1438 rule = selectNumberRule(Calendar.HOUR_OF_DAY, tokenLen); 1439 break; 1440 case 'm': // minute in hour (number) 1441 rule = selectNumberRule(Calendar.MINUTE, tokenLen); 1442 break; 1443 case 's': // second in minute (number) 1444 rule = selectNumberRule(Calendar.SECOND, tokenLen); 1445 break; 1446 case 'S': // millisecond (number) 1447 rule = selectNumberRule(Calendar.MILLISECOND, tokenLen); 1448 break; 1449 case 'E': // day in week (text) 1450 rule = new TextField(Calendar.DAY_OF_WEEK, tokenLen < 4 ? shortWeekdays : weekdays); 1451 break; 1452 case 'u': // day in week (number) 1453 rule = new DayInWeekField(selectNumberRule(Calendar.DAY_OF_WEEK, tokenLen)); 1454 break; 1455 case 'D': // day in year (number) 1456 rule = selectNumberRule(Calendar.DAY_OF_YEAR, tokenLen); 1457 break; 1458 case 'F': // day of week in month (number) 1459 rule = selectNumberRule(Calendar.DAY_OF_WEEK_IN_MONTH, tokenLen); 1460 break; 1461 case 'w': // week in year (number) 1462 rule = selectNumberRule(Calendar.WEEK_OF_YEAR, tokenLen); 1463 break; 1464 case 'W': // week in month (number) 1465 rule = selectNumberRule(Calendar.WEEK_OF_MONTH, tokenLen); 1466 break; 1467 case 'a': // am/pm marker (text) 1468 rule = new TextField(Calendar.AM_PM, AmPmStrings); 1469 break; 1470 case 'k': // hour in day (1..24) 1471 rule = new TwentyFourHourField(selectNumberRule(Calendar.HOUR_OF_DAY, tokenLen)); 1472 break; 1473 case 'K': // hour in am/pm (0..11) 1474 rule = selectNumberRule(Calendar.HOUR, tokenLen); 1475 break; 1476 case 'X': // ISO 8601 1477 rule = Iso8601_Rule.getRule(tokenLen); 1478 break; 1479 case 'z': // time zone (text) 1480 rule = new TimeZoneNameRule(timeZone, locale, tokenLen >= 4 ? TimeZone.LONG : TimeZone.SHORT); 1481 break; 1482 case 'Z': // time zone (value) 1483 if (tokenLen == 1) { 1484 rule = TimeZoneNumberRule.INSTANCE_NO_COLON; 1485 } else if (tokenLen == 2) { 1486 rule = Iso8601_Rule.ISO8601_HOURS_COLON_MINUTES; 1487 } else { 1488 rule = TimeZoneNumberRule.INSTANCE_COLON; 1489 } 1490 break; 1491 case '\'': // literal text 1492 final String sub = token.substring(1); 1493 if (sub.length() == 1) { 1494 rule = new CharacterLiteral(sub.charAt(0)); 1495 } else { 1496 rule = new StringLiteral(sub); 1497 } 1498 break; 1499 default: 1500 throw new IllegalArgumentException("Illegal pattern component: " + token); 1501 } 1502 1503 rules.add(rule); 1504 } 1505 1506 return rules; 1507 } 1508 1509 /** 1510 * Performs the parsing of tokens. 1511 * 1512 * @param pattern The pattern. 1513 * @param indexRef index references. 1514 * @return parsed token. 1515 */ 1516 protected String parseToken(final String pattern, final int[] indexRef) { 1517 final StringBuilder buf = new StringBuilder(); 1518 int i = indexRef[0]; 1519 final int length = pattern.length(); 1520 char c = pattern.charAt(i); 1521 final char c1 = c; 1522 if (CharUtils.isAsciiAlpha(c1)) { 1523 // Scan a run of the same character, which indicates a time 1524 // pattern. 1525 buf.append(c); 1526 while (i + 1 < length) { 1527 final char peek = pattern.charAt(i + 1); 1528 if (peek != c) { 1529 break; 1530 } 1531 buf.append(c); 1532 i++; 1533 } 1534 } else { 1535 // This will identify token as text. 1536 buf.append('\''); 1537 boolean inLiteral = false; 1538 for (; i < length; i++) { 1539 c = pattern.charAt(i); 1540 if (c == '\'') { 1541 if (i + 1 < length && pattern.charAt(i + 1) == '\'') { 1542 // '' is treated as escaped ' 1543 i++; 1544 buf.append(c); 1545 } else { 1546 inLiteral = !inLiteral; 1547 } 1548 } else { 1549 final char c2 = c; 1550 if (!inLiteral && CharUtils.isAsciiAlpha(c2)) { 1551 i--; 1552 break; 1553 } 1554 buf.append(c); 1555 } 1556 } 1557 } 1558 indexRef[0] = i; 1559 return buf.toString(); 1560 } 1561 1562 /** 1563 * Create the object after serialization. This implementation reinitializes the 1564 * transient properties. 1565 * 1566 * @param in ObjectInputStream from which the object is being deserialized. 1567 * @throws IOException Thrown if there is an IO issue. 1568 * @throws ClassNotFoundException Thrown if a class cannot be found. 1569 */ 1570 private void readObject(final ObjectInputStream in) throws IOException, ClassNotFoundException { 1571 in.defaultReadObject(); 1572 init(); 1573 } 1574 1575 /** 1576 * Gets an appropriate rule for the padding required. 1577 * 1578 * @param field The field to get a rule for. 1579 * @param padding The padding required. 1580 * @return A new rule with the correct padding. 1581 */ 1582 protected NumberRule selectNumberRule(final int field, final int padding) { 1583 switch (padding) { 1584 case 1: 1585 return new UnpaddedNumberField(field); 1586 case 2: 1587 return new TwoDigitNumberField(field); 1588 default: 1589 return new PaddedNumberField(field, padding); 1590 } 1591 } 1592 1593 /** 1594 * Gets a debugging string version of this formatter. 1595 * 1596 * @return A debugging string. 1597 */ 1598 @Override 1599 public String toString() { 1600 return "FastDatePrinter[" + pattern + "," + locale + "," + timeZone.getID() + "]"; 1601 } 1602}