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