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