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 * http://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.logging.log4j.core.util.datetime; 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.util.ArrayList; 026import java.util.Calendar; 027import java.util.Date; 028import java.util.GregorianCalendar; 029import java.util.List; 030import java.util.Locale; 031import java.util.TimeZone; 032import java.util.concurrent.ConcurrentHashMap; 033import java.util.concurrent.ConcurrentMap; 034 035/** 036 * Copied from Commons Lang 3. 037 */ 038public class FastDatePrinter implements DatePrinter, Serializable { 039 // A lot of the speed in this class comes from caching, but some comes 040 // from the special int to StringBuilder conversion. 041 // 042 // The following produces a padded 2 digit number: 043 // buffer.append((char)(value / 10 + '0')); 044 // buffer.append((char)(value % 10 + '0')); 045 // 046 // Note that the fastest append to StringBuilder is a single char (used here). 047 // Note that Integer.toString() is not called, the conversion is simply 048 // taking the value and adding (mathematically) the ASCII value for '0'. 049 // So, don't change this code! It works and is very fast. 050 051 /** 052 * FULL locale dependent date or time style. 053 */ 054 public static final int FULL = DateFormat.FULL; 055 /** 056 * LONG locale dependent date or time style. 057 */ 058 public static final int LONG = DateFormat.LONG; 059 /** 060 * MEDIUM locale dependent date or time style. 061 */ 062 public static final int MEDIUM = DateFormat.MEDIUM; 063 /** 064 * SHORT locale dependent date or time style. 065 */ 066 public static final int SHORT = DateFormat.SHORT; 067 068 /** 069 * Required for serialization support. 070 * 071 * @see java.io.Serializable 072 */ 073 private static final long serialVersionUID = 1L; 074 075 /** 076 * The pattern. 077 */ 078 private final String mPattern; 079 /** 080 * The time zone. 081 */ 082 private final TimeZone mTimeZone; 083 /** 084 * The locale. 085 */ 086 private final Locale mLocale; 087 /** 088 * The parsed rules. 089 */ 090 private transient Rule[] mRules; 091 /** 092 * The estimated maximum length. 093 */ 094 private transient int mMaxLengthEstimate; 095 096 // Constructor 097 // ----------------------------------------------------------------------- 098 /** 099 * <p> 100 * Constructs a new FastDatePrinter. 101 * </p> 102 * Use {@link FastDateFormat#getInstance(String, TimeZone, Locale)} or another variation of the factory methods of 103 * {@link FastDateFormat} to get a cached FastDatePrinter instance. 104 * 105 * @param pattern {@link java.text.SimpleDateFormat} compatible pattern 106 * @param timeZone non-null time zone to use 107 * @param locale non-null locale to use 108 * @throws NullPointerException if pattern, timeZone, or locale is null. 109 */ 110 protected FastDatePrinter(final String pattern, final TimeZone timeZone, final Locale locale) { 111 mPattern = pattern; 112 mTimeZone = timeZone; 113 mLocale = locale; 114 115 init(); 116 } 117 118 /** 119 * <p> 120 * Initializes the instance for first use. 121 * </p> 122 */ 123 private void init() { 124 final List<Rule> rulesList = parsePattern(); 125 mRules = rulesList.toArray(new Rule[rulesList.size()]); 126 127 int len = 0; 128 for (int i = mRules.length; --i >= 0;) { 129 len += mRules[i].estimateLength(); 130 } 131 132 mMaxLengthEstimate = len; 133 } 134 135 // Parse the pattern 136 // ----------------------------------------------------------------------- 137 /** 138 * <p> 139 * Returns a list of Rules given a pattern. 140 * </p> 141 * 142 * @return a {@code List} of Rule objects 143 * @throws IllegalArgumentException if pattern is invalid 144 */ 145 protected List<Rule> parsePattern() { 146 final DateFormatSymbols symbols = new DateFormatSymbols(mLocale); 147 final List<Rule> rules = new ArrayList<Rule>(); 148 149 final String[] ERAs = symbols.getEras(); 150 final String[] months = symbols.getMonths(); 151 final String[] shortMonths = symbols.getShortMonths(); 152 final String[] weekdays = symbols.getWeekdays(); 153 final String[] shortWeekdays = symbols.getShortWeekdays(); 154 final String[] AmPmStrings = symbols.getAmPmStrings(); 155 156 final int length = mPattern.length(); 157 final int[] indexRef = new int[1]; 158 159 for (int i = 0; i < length; i++) { 160 indexRef[0] = i; 161 final String token = parseToken(mPattern, indexRef); 162 i = indexRef[0]; 163 164 final int tokenLen = token.length(); 165 if (tokenLen == 0) { 166 break; 167 } 168 169 Rule rule; 170 final char c = token.charAt(0); 171 172 switch (c) { 173 case 'G': // era designator (text) 174 rule = new TextField(Calendar.ERA, ERAs); 175 break; 176 case 'y': // year (number) 177 if (tokenLen == 2) { 178 rule = TwoDigitYearField.INSTANCE; 179 } else { 180 rule = selectNumberRule(Calendar.YEAR, tokenLen < 4 ? 4 : tokenLen); 181 } 182 break; 183 case 'M': // month in year (text and number) 184 if (tokenLen >= 4) { 185 rule = new TextField(Calendar.MONTH, months); 186 } else if (tokenLen == 3) { 187 rule = new TextField(Calendar.MONTH, shortMonths); 188 } else if (tokenLen == 2) { 189 rule = TwoDigitMonthField.INSTANCE; 190 } else { 191 rule = UnpaddedMonthField.INSTANCE; 192 } 193 break; 194 case 'd': // day in month (number) 195 rule = selectNumberRule(Calendar.DAY_OF_MONTH, tokenLen); 196 break; 197 case 'h': // hour in am/pm (number, 1..12) 198 rule = new TwelveHourField(selectNumberRule(Calendar.HOUR, tokenLen)); 199 break; 200 case 'H': // hour in day (number, 0..23) 201 rule = selectNumberRule(Calendar.HOUR_OF_DAY, tokenLen); 202 break; 203 case 'm': // minute in hour (number) 204 rule = selectNumberRule(Calendar.MINUTE, tokenLen); 205 break; 206 case 's': // second in minute (number) 207 rule = selectNumberRule(Calendar.SECOND, tokenLen); 208 break; 209 case 'S': // millisecond (number) 210 rule = selectNumberRule(Calendar.MILLISECOND, tokenLen); 211 break; 212 case 'E': // day in week (text) 213 rule = new TextField(Calendar.DAY_OF_WEEK, tokenLen < 4 ? shortWeekdays : weekdays); 214 break; 215 case 'D': // day in year (number) 216 rule = selectNumberRule(Calendar.DAY_OF_YEAR, tokenLen); 217 break; 218 case 'F': // day of week in month (number) 219 rule = selectNumberRule(Calendar.DAY_OF_WEEK_IN_MONTH, tokenLen); 220 break; 221 case 'w': // week in year (number) 222 rule = selectNumberRule(Calendar.WEEK_OF_YEAR, tokenLen); 223 break; 224 case 'W': // week in month (number) 225 rule = selectNumberRule(Calendar.WEEK_OF_MONTH, tokenLen); 226 break; 227 case 'a': // am/pm marker (text) 228 rule = new TextField(Calendar.AM_PM, AmPmStrings); 229 break; 230 case 'k': // hour in day (1..24) 231 rule = new TwentyFourHourField(selectNumberRule(Calendar.HOUR_OF_DAY, tokenLen)); 232 break; 233 case 'K': // hour in am/pm (0..11) 234 rule = selectNumberRule(Calendar.HOUR, tokenLen); 235 break; 236 case 'X': // ISO 8601 237 rule = Iso8601_Rule.getRule(tokenLen); 238 break; 239 case 'z': // time zone (text) 240 if (tokenLen >= 4) { 241 rule = new TimeZoneNameRule(mTimeZone, mLocale, TimeZone.LONG); 242 } else { 243 rule = new TimeZoneNameRule(mTimeZone, mLocale, TimeZone.SHORT); 244 } 245 break; 246 case 'Z': // time zone (value) 247 if (tokenLen == 1) { 248 rule = TimeZoneNumberRule.INSTANCE_NO_COLON; 249 } else if (tokenLen == 2) { 250 rule = Iso8601_Rule.ISO8601_HOURS_COLON_MINUTES; 251 } else { 252 rule = TimeZoneNumberRule.INSTANCE_COLON; 253 } 254 break; 255 case '\'': // literal text 256 final String sub = token.substring(1); 257 if (sub.length() == 1) { 258 rule = new CharacterLiteral(sub.charAt(0)); 259 } else { 260 rule = new StringLiteral(sub); 261 } 262 break; 263 default: 264 throw new IllegalArgumentException("Illegal pattern component: " + token); 265 } 266 267 rules.add(rule); 268 } 269 270 return rules; 271 } 272 273 /** 274 * <p> 275 * Performs the parsing of tokens. 276 * </p> 277 * 278 * @param pattern the pattern 279 * @param indexRef index references 280 * @return parsed token 281 */ 282 protected String parseToken(final String pattern, final int[] indexRef) { 283 final StringBuilder buf = new StringBuilder(); 284 285 int i = indexRef[0]; 286 final int length = pattern.length(); 287 288 char c = pattern.charAt(i); 289 if (c >= 'A' && c <= 'Z' || c >= 'a' && c <= 'z') { 290 // Scan a run of the same character, which indicates a time 291 // pattern. 292 buf.append(c); 293 294 while (i + 1 < length) { 295 final char peek = pattern.charAt(i + 1); 296 if (peek == c) { 297 buf.append(c); 298 i++; 299 } else { 300 break; 301 } 302 } 303 } else { 304 // This will identify token as text. 305 buf.append('\''); 306 307 boolean inLiteral = false; 308 309 for (; i < length; i++) { 310 c = pattern.charAt(i); 311 312 if (c == '\'') { 313 if (i + 1 < length && pattern.charAt(i + 1) == '\'') { 314 // '' is treated as escaped ' 315 i++; 316 buf.append(c); 317 } else { 318 inLiteral = !inLiteral; 319 } 320 } else if (!inLiteral && (c >= 'A' && c <= 'Z' || c >= 'a' && c <= 'z')) { 321 i--; 322 break; 323 } else { 324 buf.append(c); 325 } 326 } 327 } 328 329 indexRef[0] = i; 330 return buf.toString(); 331 } 332 333 /** 334 * <p> 335 * Gets an appropriate rule for the padding required. 336 * </p> 337 * 338 * @param field the field to get a rule for 339 * @param padding the padding required 340 * @return a new rule with the correct padding 341 */ 342 protected NumberRule selectNumberRule(final int field, final int padding) { 343 switch (padding) { 344 case 1: 345 return new UnpaddedNumberField(field); 346 case 2: 347 return new TwoDigitNumberField(field); 348 default: 349 return new PaddedNumberField(field, padding); 350 } 351 } 352 353 // Format methods 354 // ----------------------------------------------------------------------- 355 /** 356 * <p> 357 * Formats a {@code Date}, {@code Calendar} or {@code Long} (milliseconds) object. 358 * </p> 359 * 360 * @param obj the object to format 361 * @param toAppendTo the buffer to append to 362 * @param pos the position - ignored 363 * @return the buffer passed in 364 */ 365 @Override 366 public StringBuilder format(final Object obj, final StringBuilder toAppendTo, final FieldPosition pos) { 367 if (obj instanceof Date) { 368 return format((Date) obj, toAppendTo); 369 } else if (obj instanceof Calendar) { 370 return format((Calendar) obj, toAppendTo); 371 } else if (obj instanceof Long) { 372 return format(((Long) obj).longValue(), toAppendTo); 373 } else { 374 throw new IllegalArgumentException("Unknown class: " + (obj == null ? "<null>" : obj.getClass().getName())); 375 } 376 } 377 378 /* 379 * (non-Javadoc) 380 * 381 * @see org.apache.commons.lang3.time.DatePrinter#format(long) 382 */ 383 @Override 384 public String format(final long millis) { 385 final Calendar c = newCalendar(); // hard code GregorianCalendar 386 c.setTimeInMillis(millis); 387 return applyRulesToString(c); 388 } 389 390 /** 391 * Creates a String representation of the given Calendar by applying the rules of this printer to it. 392 * 393 * @param c the Calender to apply the rules to. 394 * @return a String representation of the given Calendar. 395 */ 396 private String applyRulesToString(final Calendar c) { 397 return applyRules(c, new StringBuilder(mMaxLengthEstimate)).toString(); 398 } 399 400 /** 401 * Creation method for ne calender instances. 402 * 403 * @return a new Calendar instance. 404 */ 405 private GregorianCalendar newCalendar() { 406 // hard code GregorianCalendar 407 return new GregorianCalendar(mTimeZone, mLocale); 408 } 409 410 /* 411 * (non-Javadoc) 412 * 413 * @see org.apache.commons.lang3.time.DatePrinter#format(java.util.Date) 414 */ 415 @Override 416 public String format(final Date date) { 417 final Calendar c = newCalendar(); // hard code GregorianCalendar 418 c.setTime(date); 419 return applyRulesToString(c); 420 } 421 422 /* 423 * (non-Javadoc) 424 * 425 * @see org.apache.commons.lang3.time.DatePrinter#format(java.util.Calendar) 426 */ 427 @Override 428 public String format(final Calendar calendar) { 429 return format(calendar, new StringBuilder(mMaxLengthEstimate)).toString(); 430 } 431 432 /* 433 * (non-Javadoc) 434 * 435 * @see org.apache.commons.lang3.time.DatePrinter#format(long, java.lang.StringBuilder) 436 */ 437 @Override 438 public StringBuilder format(final long millis, final StringBuilder buf) { 439 return format(new Date(millis), buf); 440 } 441 442 /* 443 * (non-Javadoc) 444 * 445 * @see org.apache.commons.lang3.time.DatePrinter#format(java.util.Date, java.lang.StringBuilder) 446 */ 447 @Override 448 public StringBuilder format(final Date date, final StringBuilder buf) { 449 final Calendar c = newCalendar(); // hard code GregorianCalendar 450 c.setTime(date); 451 return applyRules(c, buf); 452 } 453 454 /* 455 * (non-Javadoc) 456 * 457 * @see org.apache.commons.lang3.time.DatePrinter#format(java.util.Calendar, java.lang.StringBuilder) 458 */ 459 @Override 460 public StringBuilder format(final Calendar calendar, final StringBuilder buf) { 461 // do not pass in calendar directly, this will cause TimeZone of FastDatePrinter to be ignored 462 return format(calendar.getTime(), buf); 463 } 464 465 /** 466 * <p> 467 * Performs the formatting by applying the rules to the specified calendar. 468 * </p> 469 * 470 * @param calendar the calendar to format 471 * @param buf the buffer to format into 472 * @return the specified string buffer 473 */ 474 protected StringBuilder applyRules(final Calendar calendar, final StringBuilder buf) { 475 for (final Rule rule : mRules) { 476 rule.appendTo(buf, calendar); 477 } 478 return buf; 479 } 480 481 // Accessors 482 // ----------------------------------------------------------------------- 483 /* 484 * (non-Javadoc) 485 * 486 * @see org.apache.commons.lang3.time.DatePrinter#getPattern() 487 */ 488 @Override 489 public String getPattern() { 490 return mPattern; 491 } 492 493 /* 494 * (non-Javadoc) 495 * 496 * @see org.apache.commons.lang3.time.DatePrinter#getTimeZone() 497 */ 498 @Override 499 public TimeZone getTimeZone() { 500 return mTimeZone; 501 } 502 503 /* 504 * (non-Javadoc) 505 * 506 * @see org.apache.commons.lang3.time.DatePrinter#getLocale() 507 */ 508 @Override 509 public Locale getLocale() { 510 return mLocale; 511 } 512 513 /** 514 * <p> 515 * Gets an estimate for the maximum string length that the formatter will produce. 516 * </p> 517 * 518 * <p> 519 * The actual formatted length will almost always be less than or equal to this amount. 520 * </p> 521 * 522 * @return the maximum formatted length 523 */ 524 public int getMaxLengthEstimate() { 525 return mMaxLengthEstimate; 526 } 527 528 // Basics 529 // ----------------------------------------------------------------------- 530 /** 531 * <p> 532 * Compares two objects for equality. 533 * </p> 534 * 535 * @param obj the object to compare to 536 * @return {@code true} if equal 537 */ 538 @Override 539 public boolean equals(final Object obj) { 540 if (obj instanceof FastDatePrinter == false) { 541 return false; 542 } 543 final FastDatePrinter other = (FastDatePrinter) obj; 544 return mPattern.equals(other.mPattern) && mTimeZone.equals(other.mTimeZone) && mLocale.equals(other.mLocale); 545 } 546 547 /** 548 * <p> 549 * Returns a hashcode compatible with equals. 550 * </p> 551 * 552 * @return a hashcode compatible with equals 553 */ 554 @Override 555 public int hashCode() { 556 return mPattern.hashCode() + 13 * (mTimeZone.hashCode() + 13 * mLocale.hashCode()); 557 } 558 559 /** 560 * <p> 561 * Gets a debugging string version of this formatter. 562 * </p> 563 * 564 * @return a debugging string 565 */ 566 @Override 567 public String toString() { 568 return "FastDatePrinter[" + mPattern + "," + mLocale + "," + mTimeZone.getID() + "]"; 569 } 570 571 // Serializing 572 // ----------------------------------------------------------------------- 573 /** 574 * Create the object after serialization. This implementation reinitializes the transient properties. 575 * 576 * @param in ObjectInputStream from which the object is being deserialized. 577 * @throws IOException if there is an IO issue. 578 * @throws ClassNotFoundException if a class cannot be found. 579 */ 580 private void readObject(final ObjectInputStream in) throws IOException, ClassNotFoundException { 581 in.defaultReadObject(); 582 init(); 583 } 584 585 /** 586 * Appends digits to the given buffer. 587 * 588 * @param buffer the buffer to append to. 589 * @param value the value to append digits from. 590 */ 591 private static void appendDigits(final StringBuilder buffer, final int value) { 592 buffer.append((char) (value / 10 + '0')); 593 buffer.append((char) (value % 10 + '0')); 594 } 595 596 // Rules 597 // ----------------------------------------------------------------------- 598 /** 599 * <p> 600 * Inner class defining a rule. 601 * </p> 602 */ 603 private interface Rule { 604 /** 605 * Returns the estimated length of the result. 606 * 607 * @return the estimated length 608 */ 609 int estimateLength(); 610 611 /** 612 * Appends the value of the specified calendar to the output buffer based on the rule implementation. 613 * 614 * @param buffer the output buffer 615 * @param calendar calendar to be appended 616 */ 617 void appendTo(StringBuilder buffer, Calendar calendar); 618 } 619 620 /** 621 * <p> 622 * Inner class defining a numeric rule. 623 * </p> 624 */ 625 private interface NumberRule extends Rule { 626 /** 627 * Appends the specified value to the output buffer based on the rule implementation. 628 * 629 * @param buffer the output buffer 630 * @param value the value to be appended 631 */ 632 void appendTo(StringBuilder buffer, int value); 633 } 634 635 /** 636 * <p> 637 * Inner class to output a constant single character. 638 * </p> 639 */ 640 private static class CharacterLiteral implements Rule { 641 private final char mValue; 642 643 /** 644 * Constructs a new instance of {@code CharacterLiteral} to hold the specified value. 645 * 646 * @param value the character literal 647 */ 648 CharacterLiteral(final char value) { 649 mValue = value; 650 } 651 652 /** 653 * {@inheritDoc} 654 */ 655 @Override 656 public int estimateLength() { 657 return 1; 658 } 659 660 /** 661 * {@inheritDoc} 662 */ 663 @Override 664 public void appendTo(final StringBuilder buffer, final Calendar calendar) { 665 buffer.append(mValue); 666 } 667 } 668 669 /** 670 * <p> 671 * Inner class to output a constant string. 672 * </p> 673 */ 674 private static class StringLiteral implements Rule { 675 private final String mValue; 676 677 /** 678 * Constructs a new instance of {@code StringLiteral} to hold the specified value. 679 * 680 * @param value the string literal 681 */ 682 StringLiteral(final String value) { 683 mValue = value; 684 } 685 686 /** 687 * {@inheritDoc} 688 */ 689 @Override 690 public int estimateLength() { 691 return mValue.length(); 692 } 693 694 /** 695 * {@inheritDoc} 696 */ 697 @Override 698 public void appendTo(final StringBuilder buffer, final Calendar calendar) { 699 buffer.append(mValue); 700 } 701 } 702 703 /** 704 * <p> 705 * Inner class to output one of a set of values. 706 * </p> 707 */ 708 private static class TextField implements Rule { 709 private final int mField; 710 private final String[] mValues; 711 712 /** 713 * Constructs an instance of {@code TextField} with the specified field and values. 714 * 715 * @param field the field 716 * @param values the field values 717 */ 718 TextField(final int field, final String[] values) { 719 mField = field; 720 mValues = values; 721 } 722 723 /** 724 * {@inheritDoc} 725 */ 726 @Override 727 public int estimateLength() { 728 int max = 0; 729 for (int i = mValues.length; --i >= 0;) { 730 final int len = mValues[i].length(); 731 if (len > max) { 732 max = len; 733 } 734 } 735 return max; 736 } 737 738 /** 739 * {@inheritDoc} 740 */ 741 @Override 742 public void appendTo(final StringBuilder buffer, final Calendar calendar) { 743 buffer.append(mValues[calendar.get(mField)]); 744 } 745 } 746 747 /** 748 * <p> 749 * Inner class to output an unpadded number. 750 * </p> 751 */ 752 private static class UnpaddedNumberField implements NumberRule { 753 private final int mField; 754 755 /** 756 * Constructs an instance of {@code UnpadedNumberField} with the specified field. 757 * 758 * @param field the field 759 */ 760 UnpaddedNumberField(final int field) { 761 mField = field; 762 } 763 764 /** 765 * {@inheritDoc} 766 */ 767 @Override 768 public int estimateLength() { 769 return 4; 770 } 771 772 /** 773 * {@inheritDoc} 774 */ 775 @Override 776 public void appendTo(final StringBuilder buffer, final Calendar calendar) { 777 appendTo(buffer, calendar.get(mField)); 778 } 779 780 /** 781 * {@inheritDoc} 782 */ 783 @Override 784 public final void appendTo(final StringBuilder buffer, final int value) { 785 if (value < 10) { 786 buffer.append((char) (value + '0')); 787 } else if (value < 100) { 788 appendDigits(buffer, value); 789 } else { 790 buffer.append(value); 791 } 792 } 793 } 794 795 /** 796 * <p> 797 * Inner class to output an unpadded month. 798 * </p> 799 */ 800 private static class UnpaddedMonthField implements NumberRule { 801 static final UnpaddedMonthField INSTANCE = new UnpaddedMonthField(); 802 803 /** 804 * Constructs an instance of {@code UnpaddedMonthField}. 805 * 806 */ 807 UnpaddedMonthField() { 808 super(); 809 } 810 811 /** 812 * {@inheritDoc} 813 */ 814 @Override 815 public int estimateLength() { 816 return 2; 817 } 818 819 /** 820 * {@inheritDoc} 821 */ 822 @Override 823 public void appendTo(final StringBuilder buffer, final Calendar calendar) { 824 appendTo(buffer, calendar.get(Calendar.MONTH) + 1); 825 } 826 827 /** 828 * {@inheritDoc} 829 */ 830 @Override 831 public final void appendTo(final StringBuilder buffer, final int value) { 832 if (value < 10) { 833 buffer.append((char) (value + '0')); 834 } else { 835 appendDigits(buffer, value); 836 } 837 } 838 } 839 840 /** 841 * <p> 842 * Inner class to output a padded number. 843 * </p> 844 */ 845 private static class PaddedNumberField implements NumberRule { 846 private final int mField; 847 private final int mSize; 848 849 /** 850 * Constructs an instance of {@code PaddedNumberField}. 851 * 852 * @param field the field 853 * @param size size of the output field 854 */ 855 PaddedNumberField(final int field, final int size) { 856 if (size < 3) { 857 // Should use UnpaddedNumberField or TwoDigitNumberField. 858 throw new IllegalArgumentException(); 859 } 860 mField = field; 861 mSize = size; 862 } 863 864 /** 865 * {@inheritDoc} 866 */ 867 @Override 868 public int estimateLength() { 869 return mSize; 870 } 871 872 /** 873 * {@inheritDoc} 874 */ 875 @Override 876 public void appendTo(final StringBuilder buffer, final Calendar calendar) { 877 appendTo(buffer, calendar.get(mField)); 878 } 879 880 /** 881 * {@inheritDoc} 882 */ 883 @Override 884 public final void appendTo(final StringBuilder buffer, int value) { 885 // pad the buffer with adequate zeros 886 for (int digit = 0; digit < mSize; ++digit) { 887 buffer.append('0'); 888 } 889 // backfill the buffer with non-zero digits 890 int index = buffer.length(); 891 for (; value > 0; value /= 10) { 892 buffer.setCharAt(--index, (char) ('0' + value % 10)); 893 } 894 } 895 } 896 897 /** 898 * <p> 899 * Inner class to output a two digit number. 900 * </p> 901 */ 902 private static class TwoDigitNumberField implements NumberRule { 903 private final int mField; 904 905 /** 906 * Constructs an instance of {@code TwoDigitNumberField} with the specified field. 907 * 908 * @param field the field 909 */ 910 TwoDigitNumberField(final int field) { 911 mField = field; 912 } 913 914 /** 915 * {@inheritDoc} 916 */ 917 @Override 918 public int estimateLength() { 919 return 2; 920 } 921 922 /** 923 * {@inheritDoc} 924 */ 925 @Override 926 public void appendTo(final StringBuilder buffer, final Calendar calendar) { 927 appendTo(buffer, calendar.get(mField)); 928 } 929 930 /** 931 * {@inheritDoc} 932 */ 933 @Override 934 public final void appendTo(final StringBuilder buffer, final int value) { 935 if (value < 100) { 936 appendDigits(buffer, value); 937 } else { 938 buffer.append(value); 939 } 940 } 941 } 942 943 /** 944 * <p> 945 * Inner class to output a two digit year. 946 * </p> 947 */ 948 private static class TwoDigitYearField implements NumberRule { 949 static final TwoDigitYearField INSTANCE = new TwoDigitYearField(); 950 951 /** 952 * Constructs an instance of {@code TwoDigitYearField}. 953 */ 954 TwoDigitYearField() { 955 super(); 956 } 957 958 /** 959 * {@inheritDoc} 960 */ 961 @Override 962 public int estimateLength() { 963 return 2; 964 } 965 966 /** 967 * {@inheritDoc} 968 */ 969 @Override 970 public void appendTo(final StringBuilder buffer, final Calendar calendar) { 971 appendTo(buffer, calendar.get(Calendar.YEAR) % 100); 972 } 973 974 /** 975 * {@inheritDoc} 976 */ 977 @Override 978 public final void appendTo(final StringBuilder buffer, final int value) { 979 appendDigits(buffer, value); 980 } 981 } 982 983 /** 984 * <p> 985 * Inner class to output a two digit month. 986 * </p> 987 */ 988 private static class TwoDigitMonthField implements NumberRule { 989 static final TwoDigitMonthField INSTANCE = new TwoDigitMonthField(); 990 991 /** 992 * Constructs an instance of {@code TwoDigitMonthField}. 993 */ 994 TwoDigitMonthField() { 995 super(); 996 } 997 998 /** 999 * {@inheritDoc} 1000 */ 1001 @Override 1002 public int estimateLength() { 1003 return 2; 1004 } 1005 1006 /** 1007 * {@inheritDoc} 1008 */ 1009 @Override 1010 public void appendTo(final StringBuilder buffer, final Calendar calendar) { 1011 appendTo(buffer, calendar.get(Calendar.MONTH) + 1); 1012 } 1013 1014 /** 1015 * {@inheritDoc} 1016 */ 1017 @Override 1018 public final void appendTo(final StringBuilder buffer, final int value) { 1019 appendDigits(buffer, value); 1020 } 1021 } 1022 1023 /** 1024 * <p> 1025 * Inner class to output the twelve hour field. 1026 * </p> 1027 */ 1028 private static class TwelveHourField implements NumberRule { 1029 private final NumberRule mRule; 1030 1031 /** 1032 * Constructs an instance of {@code TwelveHourField} with the specified {@code NumberRule}. 1033 * 1034 * @param rule the rule 1035 */ 1036 TwelveHourField(final NumberRule rule) { 1037 mRule = rule; 1038 } 1039 1040 /** 1041 * {@inheritDoc} 1042 */ 1043 @Override 1044 public int estimateLength() { 1045 return mRule.estimateLength(); 1046 } 1047 1048 /** 1049 * {@inheritDoc} 1050 */ 1051 @Override 1052 public void appendTo(final StringBuilder buffer, final Calendar calendar) { 1053 int value = calendar.get(Calendar.HOUR); 1054 if (value == 0) { 1055 value = calendar.getLeastMaximum(Calendar.HOUR) + 1; 1056 } 1057 mRule.appendTo(buffer, value); 1058 } 1059 1060 /** 1061 * {@inheritDoc} 1062 */ 1063 @Override 1064 public void appendTo(final StringBuilder buffer, final int value) { 1065 mRule.appendTo(buffer, value); 1066 } 1067 } 1068 1069 /** 1070 * <p> 1071 * Inner class to output the twenty four hour field. 1072 * </p> 1073 */ 1074 private static class TwentyFourHourField implements NumberRule { 1075 private final NumberRule mRule; 1076 1077 /** 1078 * Constructs an instance of {@code TwentyFourHourField} with the specified {@code NumberRule}. 1079 * 1080 * @param rule the rule 1081 */ 1082 TwentyFourHourField(final NumberRule rule) { 1083 mRule = rule; 1084 } 1085 1086 /** 1087 * {@inheritDoc} 1088 */ 1089 @Override 1090 public int estimateLength() { 1091 return mRule.estimateLength(); 1092 } 1093 1094 /** 1095 * {@inheritDoc} 1096 */ 1097 @Override 1098 public void appendTo(final StringBuilder buffer, final Calendar calendar) { 1099 int value = calendar.get(Calendar.HOUR_OF_DAY); 1100 if (value == 0) { 1101 value = calendar.getMaximum(Calendar.HOUR_OF_DAY) + 1; 1102 } 1103 mRule.appendTo(buffer, value); 1104 } 1105 1106 /** 1107 * {@inheritDoc} 1108 */ 1109 @Override 1110 public void appendTo(final StringBuilder buffer, final int value) { 1111 mRule.appendTo(buffer, value); 1112 } 1113 } 1114 1115 // ----------------------------------------------------------------------- 1116 1117 private static final ConcurrentMap<TimeZoneDisplayKey, String> cTimeZoneDisplayCache = new ConcurrentHashMap<TimeZoneDisplayKey, String>( 1118 7); 1119 1120 /** 1121 * <p> 1122 * Gets the time zone display name, using a cache for performance. 1123 * </p> 1124 * 1125 * @param tz the zone to query 1126 * @param daylight true if daylight savings 1127 * @param style the style to use {@code TimeZone.LONG} or {@code TimeZone.SHORT} 1128 * @param locale the locale to use 1129 * @return the textual name of the time zone 1130 */ 1131 static String getTimeZoneDisplay(final TimeZone tz, final boolean daylight, final int style, final Locale locale) { 1132 final TimeZoneDisplayKey key = new TimeZoneDisplayKey(tz, daylight, style, locale); 1133 String value = cTimeZoneDisplayCache.get(key); 1134 if (value == null) { 1135 // This is a very slow call, so cache the results. 1136 value = tz.getDisplayName(daylight, style, locale); 1137 final String prior = cTimeZoneDisplayCache.putIfAbsent(key, value); 1138 if (prior != null) { 1139 value = prior; 1140 } 1141 } 1142 return value; 1143 } 1144 1145 /** 1146 * <p> 1147 * Inner class to output a time zone name. 1148 * </p> 1149 */ 1150 private static class TimeZoneNameRule implements Rule { 1151 private final Locale mLocale; 1152 private final int mStyle; 1153 private final String mStandard; 1154 private final String mDaylight; 1155 1156 /** 1157 * Constructs an instance of {@code TimeZoneNameRule} with the specified properties. 1158 * 1159 * @param timeZone the time zone 1160 * @param locale the locale 1161 * @param style the style 1162 */ 1163 TimeZoneNameRule(final TimeZone timeZone, final Locale locale, final int style) { 1164 mLocale = locale; 1165 mStyle = style; 1166 1167 mStandard = getTimeZoneDisplay(timeZone, false, style, locale); 1168 mDaylight = getTimeZoneDisplay(timeZone, true, style, locale); 1169 } 1170 1171 /** 1172 * {@inheritDoc} 1173 */ 1174 @Override 1175 public int estimateLength() { 1176 // We have no access to the Calendar object that will be passed to 1177 // appendTo so base estimate on the TimeZone passed to the 1178 // constructor 1179 return Math.max(mStandard.length(), mDaylight.length()); 1180 } 1181 1182 /** 1183 * {@inheritDoc} 1184 */ 1185 @Override 1186 public void appendTo(final StringBuilder buffer, final Calendar calendar) { 1187 final TimeZone zone = calendar.getTimeZone(); 1188 if (calendar.get(Calendar.DST_OFFSET) != 0) { 1189 buffer.append(getTimeZoneDisplay(zone, true, mStyle, mLocale)); 1190 } else { 1191 buffer.append(getTimeZoneDisplay(zone, false, mStyle, mLocale)); 1192 } 1193 } 1194 } 1195 1196 /** 1197 * <p> 1198 * Inner class to output a time zone as a number {@code +/-HHMM} or {@code +/-HH:MM}. 1199 * </p> 1200 */ 1201 private static class TimeZoneNumberRule implements Rule { 1202 static final TimeZoneNumberRule INSTANCE_COLON = new TimeZoneNumberRule(true); 1203 static final TimeZoneNumberRule INSTANCE_NO_COLON = new TimeZoneNumberRule(false); 1204 1205 final boolean mColon; 1206 1207 /** 1208 * Constructs an instance of {@code TimeZoneNumberRule} with the specified properties. 1209 * 1210 * @param colon add colon between HH and MM in the output if {@code true} 1211 */ 1212 TimeZoneNumberRule(final boolean colon) { 1213 mColon = colon; 1214 } 1215 1216 /** 1217 * {@inheritDoc} 1218 */ 1219 @Override 1220 public int estimateLength() { 1221 return 5; 1222 } 1223 1224 /** 1225 * {@inheritDoc} 1226 */ 1227 @Override 1228 public void appendTo(final StringBuilder buffer, final Calendar calendar) { 1229 1230 int offset = calendar.get(Calendar.ZONE_OFFSET) + calendar.get(Calendar.DST_OFFSET); 1231 1232 if (offset < 0) { 1233 buffer.append('-'); 1234 offset = -offset; 1235 } else { 1236 buffer.append('+'); 1237 } 1238 1239 final int hours = offset / (60 * 60 * 1000); 1240 appendDigits(buffer, hours); 1241 1242 if (mColon) { 1243 buffer.append(':'); 1244 } 1245 1246 final int minutes = offset / (60 * 1000) - 60 * hours; 1247 appendDigits(buffer, minutes); 1248 } 1249 } 1250 1251 /** 1252 * <p> 1253 * Inner class to output a time zone as a number {@code +/-HHMM} or {@code +/-HH:MM}. 1254 * </p> 1255 */ 1256 private static class Iso8601_Rule implements Rule { 1257 1258 // Sign TwoDigitHours or Z 1259 static final Iso8601_Rule ISO8601_HOURS = new Iso8601_Rule(3); 1260 // Sign TwoDigitHours Minutes or Z 1261 static final Iso8601_Rule ISO8601_HOURS_MINUTES = new Iso8601_Rule(5); 1262 // Sign TwoDigitHours : Minutes or Z 1263 static final Iso8601_Rule ISO8601_HOURS_COLON_MINUTES = new Iso8601_Rule(6); 1264 1265 /** 1266 * Factory method for Iso8601_Rules. 1267 * 1268 * @param tokenLen a token indicating the length of the TimeZone String to be formatted. 1269 * @return a Iso8601_Rule that can format TimeZone String of length {@code tokenLen}. If no such rule exists, an 1270 * IllegalArgumentException will be thrown. 1271 */ 1272 static Iso8601_Rule getRule(final int tokenLen) { 1273 switch (tokenLen) { 1274 case 1: 1275 return Iso8601_Rule.ISO8601_HOURS; 1276 case 2: 1277 return Iso8601_Rule.ISO8601_HOURS_MINUTES; 1278 case 3: 1279 return Iso8601_Rule.ISO8601_HOURS_COLON_MINUTES; 1280 default: 1281 throw new IllegalArgumentException("invalid number of X"); 1282 } 1283 } 1284 1285 final int length; 1286 1287 /** 1288 * Constructs an instance of {@code Iso8601_Rule} with the specified properties. 1289 * 1290 * @param length The number of characters in output (unless Z is output) 1291 */ 1292 Iso8601_Rule(final int length) { 1293 this.length = length; 1294 } 1295 1296 /** 1297 * {@inheritDoc} 1298 */ 1299 @Override 1300 public int estimateLength() { 1301 return length; 1302 } 1303 1304 /** 1305 * {@inheritDoc} 1306 */ 1307 @Override 1308 public void appendTo(final StringBuilder buffer, final Calendar calendar) { 1309 int offset = calendar.get(Calendar.ZONE_OFFSET) + calendar.get(Calendar.DST_OFFSET); 1310 if (offset == 0) { 1311 buffer.append("Z"); 1312 return; 1313 } 1314 1315 if (offset < 0) { 1316 buffer.append('-'); 1317 offset = -offset; 1318 } else { 1319 buffer.append('+'); 1320 } 1321 1322 final int hours = offset / (60 * 60 * 1000); 1323 appendDigits(buffer, hours); 1324 1325 if (length < 5) { 1326 return; 1327 } 1328 1329 if (length == 6) { 1330 buffer.append(':'); 1331 } 1332 1333 final int minutes = offset / (60 * 1000) - 60 * hours; 1334 appendDigits(buffer, minutes); 1335 } 1336 } 1337 1338 // ---------------------------------------------------------------------- 1339 /** 1340 * <p> 1341 * Inner class that acts as a compound key for time zone names. 1342 * </p> 1343 */ 1344 private static class TimeZoneDisplayKey { 1345 private final TimeZone mTimeZone; 1346 private final int mStyle; 1347 private final Locale mLocale; 1348 1349 /** 1350 * Constructs an instance of {@code TimeZoneDisplayKey} with the specified properties. 1351 * 1352 * @param timeZone the time zone 1353 * @param daylight adjust the style for daylight saving time if {@code true} 1354 * @param style the timezone style 1355 * @param locale the timezone locale 1356 */ 1357 TimeZoneDisplayKey(final TimeZone timeZone, final boolean daylight, final int style, final Locale locale) { 1358 mTimeZone = timeZone; 1359 if (daylight) { 1360 mStyle = style | 0x80000000; 1361 } else { 1362 mStyle = style; 1363 } 1364 mLocale = locale; 1365 } 1366 1367 /** 1368 * {@inheritDoc} 1369 */ 1370 @Override 1371 public int hashCode() { 1372 return (mStyle * 31 + mLocale.hashCode()) * 31 + mTimeZone.hashCode(); 1373 } 1374 1375 /** 1376 * {@inheritDoc} 1377 */ 1378 @Override 1379 public boolean equals(final Object obj) { 1380 if (this == obj) { 1381 return true; 1382 } 1383 if (obj instanceof TimeZoneDisplayKey) { 1384 final TimeZoneDisplayKey other = (TimeZoneDisplayKey) obj; 1385 return mTimeZone.equals(other.mTimeZone) && mStyle == other.mStyle && mLocale.equals(other.mLocale); 1386 } 1387 return false; 1388 } 1389 } 1390}