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