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 TeaTrove project 063 * @author Brian S O'Neill 064 * @author Sean Schofield 065 * @author Gary Gregory 066 * @author Stephen Colebourne 067 * @author Nikolay Metchev 068 * @since 2.0 069 * @version $Id: FastDateFormat.java 590552 2007-10-31 04:04:32Z bayard $ 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; 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 = (Calendar) calendar.clone(); 871 calendar.setTimeZone(mTimeZone); 872 } 873 return applyRules(calendar, buf); 874 } 875 876 /** 877 * <p>Performs the formatting by applying the rules to the 878 * specified calendar.</p> 879 * 880 * @param calendar the calendar to format 881 * @param buf the buffer to format into 882 * @return the specified string buffer 883 */ 884 protected StringBuffer applyRules(Calendar calendar, StringBuffer buf) { 885 Rule[] rules = mRules; 886 int len = mRules.length; 887 for (int i = 0; i < len; i++) { 888 rules[i].appendTo(buf, calendar); 889 } 890 return buf; 891 } 892 893 // Parsing 894 //----------------------------------------------------------------------- 895 /** 896 * <p>Parsing is not supported.</p> 897 * 898 * @param source the string to parse 899 * @param pos the parsing position 900 * @return <code>null</code> as not supported 901 */ 902 public Object parseObject(String source, ParsePosition pos) { 903 pos.setIndex(0); 904 pos.setErrorIndex(0); 905 return null; 906 } 907 908 // Accessors 909 //----------------------------------------------------------------------- 910 /** 911 * <p>Gets the pattern used by this formatter.</p> 912 * 913 * @return the pattern, {@link java.text.SimpleDateFormat} compatible 914 */ 915 public String getPattern() { 916 return mPattern; 917 } 918 919 /** 920 * <p>Gets the time zone used by this formatter.</p> 921 * 922 * <p>This zone is always used for <code>Date</code> formatting. 923 * If a <code>Calendar</code> is passed in to be formatted, the 924 * time zone on that may be used depending on 925 * {@link #getTimeZoneOverridesCalendar()}.</p> 926 * 927 * @return the time zone 928 */ 929 public TimeZone getTimeZone() { 930 return mTimeZone; 931 } 932 933 /** 934 * <p>Returns <code>true</code> if the time zone of the 935 * calendar overrides the time zone of the formatter.</p> 936 * 937 * @return <code>true</code> if time zone of formatter 938 * overridden for calendars 939 */ 940 public boolean getTimeZoneOverridesCalendar() { 941 return mTimeZoneForced; 942 } 943 944 /** 945 * <p>Gets the locale used by this formatter.</p> 946 * 947 * @return the locale 948 */ 949 public Locale getLocale() { 950 return mLocale; 951 } 952 953 /** 954 * <p>Gets an estimate for the maximum string length that the 955 * formatter will produce.</p> 956 * 957 * <p>The actual formatted length will almost always be less than or 958 * equal to this amount.</p> 959 * 960 * @return the maximum formatted length 961 */ 962 public int getMaxLengthEstimate() { 963 return mMaxLengthEstimate; 964 } 965 966 // Basics 967 //----------------------------------------------------------------------- 968 /** 969 * <p>Compares two objects for equality.</p> 970 * 971 * @param obj the object to compare to 972 * @return <code>true</code> if equal 973 */ 974 public boolean equals(Object obj) { 975 if (obj instanceof FastDateFormat == false) { 976 return false; 977 } 978 FastDateFormat other = (FastDateFormat) obj; 979 if ( 980 (mPattern == other.mPattern || mPattern.equals(other.mPattern)) && 981 (mTimeZone == other.mTimeZone || mTimeZone.equals(other.mTimeZone)) && 982 (mLocale == other.mLocale || mLocale.equals(other.mLocale)) && 983 (mTimeZoneForced == other.mTimeZoneForced) && 984 (mLocaleForced == other.mLocaleForced) 985 ) { 986 return true; 987 } 988 return false; 989 } 990 991 /** 992 * <p>Returns a hashcode compatible with equals.</p> 993 * 994 * @return a hashcode compatible with equals 995 */ 996 public int hashCode() { 997 int total = 0; 998 total += mPattern.hashCode(); 999 total += mTimeZone.hashCode(); 1000 total += (mTimeZoneForced ? 1 : 0); 1001 total += mLocale.hashCode(); 1002 total += (mLocaleForced ? 1 : 0); 1003 return total; 1004 } 1005 1006 /** 1007 * <p>Gets a debugging string version of this formatter.</p> 1008 * 1009 * @return a debugging string 1010 */ 1011 public String toString() { 1012 return "FastDateFormat[" + mPattern + "]"; 1013 } 1014 1015 // Serializing 1016 //----------------------------------------------------------------------- 1017 /** 1018 * Create the object after serialization. This implementation reinitializes the 1019 * transient properties. 1020 * 1021 * @param in ObjectInputStream from which the object is being deserialized. 1022 * @throws IOException if there is an IO issue. 1023 * @throws ClassNotFoundException if a class cannot be found. 1024 */ 1025 private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException { 1026 in.defaultReadObject(); 1027 init(); 1028 } 1029 1030 // Rules 1031 //----------------------------------------------------------------------- 1032 /** 1033 * <p>Inner class defining a rule.</p> 1034 */ 1035 private interface Rule { 1036 /** 1037 * Returns the estimated lentgh of the result. 1038 * 1039 * @return the estimated length 1040 */ 1041 int estimateLength(); 1042 1043 /** 1044 * Appends the value of the specified calendar to the output buffer based on the rule implementation. 1045 * 1046 * @param buffer the output buffer 1047 * @param calendar calendar to be appended 1048 */ 1049 void appendTo(StringBuffer buffer, Calendar calendar); 1050 } 1051 1052 /** 1053 * <p>Inner class defining a numeric rule.</p> 1054 */ 1055 private interface NumberRule extends Rule { 1056 /** 1057 * Appends the specified value to the output buffer based on the rule implementation. 1058 * 1059 * @param buffer the output buffer 1060 * @param value the value to be appended 1061 */ 1062 void appendTo(StringBuffer buffer, int value); 1063 } 1064 1065 /** 1066 * <p>Inner class to output a constant single character.</p> 1067 */ 1068 private static class CharacterLiteral implements Rule { 1069 private final char mValue; 1070 1071 /** 1072 * Constructs a new instance of <code>CharacterLiteral</code> 1073 * to hold the specified value. 1074 * 1075 * @param value the character literal 1076 */ 1077 CharacterLiteral(char value) { 1078 mValue = value; 1079 } 1080 1081 /** 1082 * {@inheritDoc} 1083 */ 1084 public int estimateLength() { 1085 return 1; 1086 } 1087 1088 /** 1089 * {@inheritDoc} 1090 */ 1091 public void appendTo(StringBuffer buffer, Calendar calendar) { 1092 buffer.append(mValue); 1093 } 1094 } 1095 1096 /** 1097 * <p>Inner class to output a constant string.</p> 1098 */ 1099 private static class StringLiteral implements Rule { 1100 private final String mValue; 1101 1102 /** 1103 * Constructs a new instance of <code>StringLiteral</code> 1104 * to hold the specified value. 1105 * 1106 * @param value the string literal 1107 */ 1108 StringLiteral(String value) { 1109 mValue = value; 1110 } 1111 1112 /** 1113 * {@inheritDoc} 1114 */ 1115 public int estimateLength() { 1116 return mValue.length(); 1117 } 1118 1119 /** 1120 * {@inheritDoc} 1121 */ 1122 public void appendTo(StringBuffer buffer, Calendar calendar) { 1123 buffer.append(mValue); 1124 } 1125 } 1126 1127 /** 1128 * <p>Inner class to output one of a set of values.</p> 1129 */ 1130 private static class TextField implements Rule { 1131 private final int mField; 1132 private final String[] mValues; 1133 1134 /** 1135 * Constructs an instance of <code>TextField</code> 1136 * with the specified field and values. 1137 * 1138 * @param field the field 1139 * @param values the field values 1140 */ 1141 TextField(int field, String[] values) { 1142 mField = field; 1143 mValues = values; 1144 } 1145 1146 /** 1147 * {@inheritDoc} 1148 */ 1149 public int estimateLength() { 1150 int max = 0; 1151 for (int i=mValues.length; --i >= 0; ) { 1152 int len = mValues[i].length(); 1153 if (len > max) { 1154 max = len; 1155 } 1156 } 1157 return max; 1158 } 1159 1160 /** 1161 * {@inheritDoc} 1162 */ 1163 public void appendTo(StringBuffer buffer, Calendar calendar) { 1164 buffer.append(mValues[calendar.get(mField)]); 1165 } 1166 } 1167 1168 /** 1169 * <p>Inner class to output an unpadded number.</p> 1170 */ 1171 private static class UnpaddedNumberField implements NumberRule { 1172 static final UnpaddedNumberField INSTANCE_YEAR = new UnpaddedNumberField(Calendar.YEAR); 1173 1174 private final int mField; 1175 1176 /** 1177 * Constructs an instance of <code>UnpadedNumberField</code> with the specified field. 1178 * 1179 * @param field the field 1180 */ 1181 UnpaddedNumberField(int field) { 1182 mField = field; 1183 } 1184 1185 /** 1186 * {@inheritDoc} 1187 */ 1188 public int estimateLength() { 1189 return 4; 1190 } 1191 1192 /** 1193 * {@inheritDoc} 1194 */ 1195 public void appendTo(StringBuffer buffer, Calendar calendar) { 1196 appendTo(buffer, calendar.get(mField)); 1197 } 1198 1199 /** 1200 * {@inheritDoc} 1201 */ 1202 public final void appendTo(StringBuffer buffer, int value) { 1203 if (value < 10) { 1204 buffer.append((char)(value + '0')); 1205 } else if (value < 100) { 1206 buffer.append((char)(value / 10 + '0')); 1207 buffer.append((char)(value % 10 + '0')); 1208 } else { 1209 buffer.append(Integer.toString(value)); 1210 } 1211 } 1212 } 1213 1214 /** 1215 * <p>Inner class to output an unpadded month.</p> 1216 */ 1217 private static class UnpaddedMonthField implements NumberRule { 1218 static final UnpaddedMonthField INSTANCE = new UnpaddedMonthField(); 1219 1220 /** 1221 * Constructs an instance of <code>UnpaddedMonthField</code>. 1222 * 1223 */ 1224 UnpaddedMonthField() { 1225 super(); 1226 } 1227 1228 /** 1229 * {@inheritDoc} 1230 */ 1231 public int estimateLength() { 1232 return 2; 1233 } 1234 1235 /** 1236 * {@inheritDoc} 1237 */ 1238 public void appendTo(StringBuffer buffer, Calendar calendar) { 1239 appendTo(buffer, calendar.get(Calendar.MONTH) + 1); 1240 } 1241 1242 /** 1243 * {@inheritDoc} 1244 */ 1245 public final void appendTo(StringBuffer buffer, int value) { 1246 if (value < 10) { 1247 buffer.append((char)(value + '0')); 1248 } else { 1249 buffer.append((char)(value / 10 + '0')); 1250 buffer.append((char)(value % 10 + '0')); 1251 } 1252 } 1253 } 1254 1255 /** 1256 * <p>Inner class to output a padded number.</p> 1257 */ 1258 private static class PaddedNumberField implements NumberRule { 1259 private final int mField; 1260 private final int mSize; 1261 1262 /** 1263 * Constructs an instance of <code>PaddedNumberField</code>. 1264 * 1265 * @param field the field 1266 * @param size size of the output field 1267 */ 1268 PaddedNumberField(int field, int size) { 1269 if (size < 3) { 1270 // Should use UnpaddedNumberField or TwoDigitNumberField. 1271 throw new IllegalArgumentException(); 1272 } 1273 mField = field; 1274 mSize = size; 1275 } 1276 1277 /** 1278 * {@inheritDoc} 1279 */ 1280 public int estimateLength() { 1281 return 4; 1282 } 1283 1284 /** 1285 * {@inheritDoc} 1286 */ 1287 public void appendTo(StringBuffer buffer, Calendar calendar) { 1288 appendTo(buffer, calendar.get(mField)); 1289 } 1290 1291 /** 1292 * {@inheritDoc} 1293 */ 1294 public final void appendTo(StringBuffer buffer, int value) { 1295 if (value < 100) { 1296 for (int i = mSize; --i >= 2; ) { 1297 buffer.append('0'); 1298 } 1299 buffer.append((char)(value / 10 + '0')); 1300 buffer.append((char)(value % 10 + '0')); 1301 } else { 1302 int digits; 1303 if (value < 1000) { 1304 digits = 3; 1305 } else { 1306 Validate.isTrue(value > -1, "Negative values should not be possible", value); 1307 digits = Integer.toString(value).length(); 1308 } 1309 for (int i = mSize; --i >= digits; ) { 1310 buffer.append('0'); 1311 } 1312 buffer.append(Integer.toString(value)); 1313 } 1314 } 1315 } 1316 1317 /** 1318 * <p>Inner class to output a two digit number.</p> 1319 */ 1320 private static class TwoDigitNumberField implements NumberRule { 1321 private final int mField; 1322 1323 /** 1324 * Constructs an instance of <code>TwoDigitNumberField</code> with the specified field. 1325 * 1326 * @param field the field 1327 */ 1328 TwoDigitNumberField(int field) { 1329 mField = field; 1330 } 1331 1332 /** 1333 * {@inheritDoc} 1334 */ 1335 public int estimateLength() { 1336 return 2; 1337 } 1338 1339 /** 1340 * {@inheritDoc} 1341 */ 1342 public void appendTo(StringBuffer buffer, Calendar calendar) { 1343 appendTo(buffer, calendar.get(mField)); 1344 } 1345 1346 /** 1347 * {@inheritDoc} 1348 */ 1349 public final void appendTo(StringBuffer buffer, int value) { 1350 if (value < 100) { 1351 buffer.append((char)(value / 10 + '0')); 1352 buffer.append((char)(value % 10 + '0')); 1353 } else { 1354 buffer.append(Integer.toString(value)); 1355 } 1356 } 1357 } 1358 1359 /** 1360 * <p>Inner class to output a two digit year.</p> 1361 */ 1362 private static class TwoDigitYearField implements NumberRule { 1363 static final TwoDigitYearField INSTANCE = new TwoDigitYearField(); 1364 1365 /** 1366 * Constructs an instance of <code>TwoDigitYearField</code>. 1367 */ 1368 TwoDigitYearField() { 1369 super(); 1370 } 1371 1372 /** 1373 * {@inheritDoc} 1374 */ 1375 public int estimateLength() { 1376 return 2; 1377 } 1378 1379 /** 1380 * {@inheritDoc} 1381 */ 1382 public void appendTo(StringBuffer buffer, Calendar calendar) { 1383 appendTo(buffer, calendar.get(Calendar.YEAR) % 100); 1384 } 1385 1386 /** 1387 * {@inheritDoc} 1388 */ 1389 public final void appendTo(StringBuffer buffer, int value) { 1390 buffer.append((char)(value / 10 + '0')); 1391 buffer.append((char)(value % 10 + '0')); 1392 } 1393 } 1394 1395 /** 1396 * <p>Inner class to output a two digit month.</p> 1397 */ 1398 private static class TwoDigitMonthField implements NumberRule { 1399 static final TwoDigitMonthField INSTANCE = new TwoDigitMonthField(); 1400 1401 /** 1402 * Constructs an instance of <code>TwoDigitMonthField</code>. 1403 */ 1404 TwoDigitMonthField() { 1405 super(); 1406 } 1407 1408 /** 1409 * {@inheritDoc} 1410 */ 1411 public int estimateLength() { 1412 return 2; 1413 } 1414 1415 /** 1416 * {@inheritDoc} 1417 */ 1418 public void appendTo(StringBuffer buffer, Calendar calendar) { 1419 appendTo(buffer, calendar.get(Calendar.MONTH) + 1); 1420 } 1421 1422 /** 1423 * {@inheritDoc} 1424 */ 1425 public final void appendTo(StringBuffer buffer, int value) { 1426 buffer.append((char)(value / 10 + '0')); 1427 buffer.append((char)(value % 10 + '0')); 1428 } 1429 } 1430 1431 /** 1432 * <p>Inner class to output the twelve hour field.</p> 1433 */ 1434 private static class TwelveHourField implements NumberRule { 1435 private final NumberRule mRule; 1436 1437 /** 1438 * Constructs an instance of <code>TwelveHourField</code> with the specified 1439 * <code>NumberRule</code>. 1440 * 1441 * @param rule the rule 1442 */ 1443 TwelveHourField(NumberRule rule) { 1444 mRule = rule; 1445 } 1446 1447 /** 1448 * {@inheritDoc} 1449 */ 1450 public int estimateLength() { 1451 return mRule.estimateLength(); 1452 } 1453 1454 /** 1455 * {@inheritDoc} 1456 */ 1457 public void appendTo(StringBuffer buffer, Calendar calendar) { 1458 int value = calendar.get(Calendar.HOUR); 1459 if (value == 0) { 1460 value = calendar.getLeastMaximum(Calendar.HOUR) + 1; 1461 } 1462 mRule.appendTo(buffer, value); 1463 } 1464 1465 /** 1466 * {@inheritDoc} 1467 */ 1468 public void appendTo(StringBuffer buffer, int value) { 1469 mRule.appendTo(buffer, value); 1470 } 1471 } 1472 1473 /** 1474 * <p>Inner class to output the twenty four hour field.</p> 1475 */ 1476 private static class TwentyFourHourField implements NumberRule { 1477 private final NumberRule mRule; 1478 1479 /** 1480 * Constructs an instance of <code>TwentyFourHourField</code> with the specified 1481 * <code>NumberRule</code>. 1482 * 1483 * @param rule the rule 1484 */ 1485 TwentyFourHourField(NumberRule rule) { 1486 mRule = rule; 1487 } 1488 1489 /** 1490 * {@inheritDoc} 1491 */ 1492 public int estimateLength() { 1493 return mRule.estimateLength(); 1494 } 1495 1496 /** 1497 * {@inheritDoc} 1498 */ 1499 public void appendTo(StringBuffer buffer, Calendar calendar) { 1500 int value = calendar.get(Calendar.HOUR_OF_DAY); 1501 if (value == 0) { 1502 value = calendar.getMaximum(Calendar.HOUR_OF_DAY) + 1; 1503 } 1504 mRule.appendTo(buffer, value); 1505 } 1506 1507 /** 1508 * {@inheritDoc} 1509 */ 1510 public void appendTo(StringBuffer buffer, int value) { 1511 mRule.appendTo(buffer, value); 1512 } 1513 } 1514 1515 /** 1516 * <p>Inner class to output a time zone name.</p> 1517 */ 1518 private static class TimeZoneNameRule implements Rule { 1519 private final TimeZone mTimeZone; 1520 private final boolean mTimeZoneForced; 1521 private final Locale mLocale; 1522 private final int mStyle; 1523 private final String mStandard; 1524 private final String mDaylight; 1525 1526 /** 1527 * Constructs an instance of <code>TimeZoneNameRule</code> with the specified properties. 1528 * 1529 * @param timeZone the time zone 1530 * @param timeZoneForced if <code>true</code> the time zone is forced into standard and daylight 1531 * @param locale the locale 1532 * @param style the style 1533 */ 1534 TimeZoneNameRule(TimeZone timeZone, boolean timeZoneForced, Locale locale, int style) { 1535 mTimeZone = timeZone; 1536 mTimeZoneForced = timeZoneForced; 1537 mLocale = locale; 1538 mStyle = style; 1539 1540 if (timeZoneForced) { 1541 mStandard = getTimeZoneDisplay(timeZone, false, style, locale); 1542 mDaylight = getTimeZoneDisplay(timeZone, true, style, locale); 1543 } else { 1544 mStandard = null; 1545 mDaylight = null; 1546 } 1547 } 1548 1549 /** 1550 * {@inheritDoc} 1551 */ 1552 public int estimateLength() { 1553 if (mTimeZoneForced) { 1554 return Math.max(mStandard.length(), mDaylight.length()); 1555 } else if (mStyle == TimeZone.SHORT) { 1556 return 4; 1557 } else { 1558 return 40; 1559 } 1560 } 1561 1562 /** 1563 * {@inheritDoc} 1564 */ 1565 public void appendTo(StringBuffer buffer, Calendar calendar) { 1566 if (mTimeZoneForced) { 1567 if (mTimeZone.useDaylightTime() && calendar.get(Calendar.DST_OFFSET) != 0) { 1568 buffer.append(mDaylight); 1569 } else { 1570 buffer.append(mStandard); 1571 } 1572 } else { 1573 TimeZone timeZone = calendar.getTimeZone(); 1574 if (timeZone.useDaylightTime() && calendar.get(Calendar.DST_OFFSET) != 0) { 1575 buffer.append(getTimeZoneDisplay(timeZone, true, mStyle, mLocale)); 1576 } else { 1577 buffer.append(getTimeZoneDisplay(timeZone, false, mStyle, mLocale)); 1578 } 1579 } 1580 } 1581 } 1582 1583 /** 1584 * <p>Inner class to output a time zone as a number <code>+/-HHMM</code> 1585 * or <code>+/-HH:MM</code>.</p> 1586 */ 1587 private static class TimeZoneNumberRule implements Rule { 1588 static final TimeZoneNumberRule INSTANCE_COLON = new TimeZoneNumberRule(true); 1589 static final TimeZoneNumberRule INSTANCE_NO_COLON = new TimeZoneNumberRule(false); 1590 1591 final boolean mColon; 1592 1593 /** 1594 * Constructs an instance of <code>TimeZoneNumberRule</code> with the specified properties. 1595 * 1596 * @param colon add colon between HH and MM in the output if <code>true</code> 1597 */ 1598 TimeZoneNumberRule(boolean colon) { 1599 mColon = colon; 1600 } 1601 1602 /** 1603 * {@inheritDoc} 1604 */ 1605 public int estimateLength() { 1606 return 5; 1607 } 1608 1609 /** 1610 * {@inheritDoc} 1611 */ 1612 public void appendTo(StringBuffer buffer, Calendar calendar) { 1613 int offset = calendar.get(Calendar.ZONE_OFFSET) + calendar.get(Calendar.DST_OFFSET); 1614 1615 if (offset < 0) { 1616 buffer.append('-'); 1617 offset = -offset; 1618 } else { 1619 buffer.append('+'); 1620 } 1621 1622 int hours = offset / (60 * 60 * 1000); 1623 buffer.append((char)(hours / 10 + '0')); 1624 buffer.append((char)(hours % 10 + '0')); 1625 1626 if (mColon) { 1627 buffer.append(':'); 1628 } 1629 1630 int minutes = offset / (60 * 1000) - 60 * hours; 1631 buffer.append((char)(minutes / 10 + '0')); 1632 buffer.append((char)(minutes % 10 + '0')); 1633 } 1634 } 1635 1636 // ---------------------------------------------------------------------- 1637 /** 1638 * <p>Inner class that acts as a compound key for time zone names.</p> 1639 */ 1640 private static class TimeZoneDisplayKey { 1641 private final TimeZone mTimeZone; 1642 private final int mStyle; 1643 private final Locale mLocale; 1644 1645 /** 1646 * Constructs an instance of <code>TimeZoneDisplayKey</code> with the specified properties. 1647 * 1648 * @param timeZone the time zone 1649 * @param daylight adjust the style for daylight saving time if <code>true</code> 1650 * @param style the timezone style 1651 * @param locale the timezone locale 1652 */ 1653 TimeZoneDisplayKey(TimeZone timeZone, 1654 boolean daylight, int style, Locale locale) { 1655 mTimeZone = timeZone; 1656 if (daylight) { 1657 style |= 0x80000000; 1658 } 1659 mStyle = style; 1660 mLocale = locale; 1661 } 1662 1663 /** 1664 * {@inheritDoc} 1665 */ 1666 public int hashCode() { 1667 return mStyle * 31 + mLocale.hashCode(); 1668 } 1669 1670 /** 1671 * {@inheritDoc} 1672 */ 1673 public boolean equals(Object obj) { 1674 if (this == obj) { 1675 return true; 1676 } 1677 if (obj instanceof TimeZoneDisplayKey) { 1678 TimeZoneDisplayKey other = (TimeZoneDisplayKey)obj; 1679 return 1680 mTimeZone.equals(other.mTimeZone) && 1681 mStyle == other.mStyle && 1682 mLocale.equals(other.mLocale); 1683 } 1684 return false; 1685 } 1686 } 1687 1688 // ---------------------------------------------------------------------- 1689 /** 1690 * <p>Helper class for creating compound objects.</p> 1691 * 1692 * <p>One use for this class is to create a hashtable key 1693 * out of multiple objects.</p> 1694 */ 1695 private static class Pair { 1696 private final Object mObj1; 1697 private final Object mObj2; 1698 1699 /** 1700 * Constructs an instance of <code>Pair</code> to hold the specified objects. 1701 * @param obj1 one object in the pair 1702 * @param obj2 second object in the pair 1703 */ 1704 public Pair(Object obj1, Object obj2) { 1705 mObj1 = obj1; 1706 mObj2 = obj2; 1707 } 1708 1709 /** 1710 * {@inheritDoc} 1711 */ 1712 public boolean equals(Object obj) { 1713 if (this == obj) { 1714 return true; 1715 } 1716 1717 if (!(obj instanceof Pair)) { 1718 return false; 1719 } 1720 1721 Pair key = (Pair)obj; 1722 1723 return 1724 (mObj1 == null ? 1725 key.mObj1 == null : mObj1.equals(key.mObj1)) && 1726 (mObj2 == null ? 1727 key.mObj2 == null : mObj2.equals(key.mObj2)); 1728 } 1729 1730 /** 1731 * {@inheritDoc} 1732 */ 1733 public int hashCode() { 1734 return 1735 (mObj1 == null ? 0 : mObj1.hashCode()) + 1736 (mObj2 == null ? 0 : mObj2.hashCode()); 1737 } 1738 1739 /** 1740 * {@inheritDoc} 1741 */ 1742 public String toString() { 1743 return "[" + mObj1 + ':' + mObj2 + ']'; 1744 } 1745 } 1746 1747 }