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