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 import org.apache.commons.lang.text.StrBuilder; 039 040 /** 041 * <p>FastDateFormat is a fast and thread-safe version of 042 * {@link java.text.SimpleDateFormat}.</p> 043 * 044 * <p>This class can be used as a direct replacement to 045 * <code>SimpleDateFormat</code> in most formatting situations. 046 * This class is especially useful in multi-threaded server environments. 047 * <code>SimpleDateFormat</code> is not thread-safe in any JDK version, 048 * nor will it be as Sun have closed the bug/RFE. 049 * </p> 050 * 051 * <p>Only formatting is supported, but all patterns are compatible with 052 * SimpleDateFormat (except time zones - see below).</p> 053 * 054 * <p>Java 1.4 introduced a new pattern letter, <code>'Z'</code>, to represent 055 * time zones in RFC822 format (eg. <code>+0800</code> or <code>-1100</code>). 056 * This pattern letter can be used here (on all JDK versions).</p> 057 * 058 * <p>In addition, the pattern <code>'ZZ'</code> has been made to represent 059 * ISO8601 full format time zones (eg. <code>+08:00</code> or <code>-11:00</code>). 060 * This introduces a minor incompatibility with Java 1.4, but at a gain of 061 * useful functionality.</p> 062 * 063 * @author Apache Software Foundation 064 * @author TeaTrove project 065 * @author Brian S O'Neill 066 * @author Sean Schofield 067 * @author Gary Gregory 068 * @author Nikolay Metchev 069 * @since 2.0 070 * @version $Id: FastDateFormat.java 1057072 2011-01-10 01:55:57Z niallp $ 071 */ 072 public class FastDateFormat extends Format { 073 // A lot of the speed in this class comes from caching, but some comes 074 // from the special int to StringBuffer conversion. 075 // 076 // The following produces a padded 2 digit number: 077 // buffer.append((char)(value / 10 + '0')); 078 // buffer.append((char)(value % 10 + '0')); 079 // 080 // Note that the fastest append to StringBuffer is a single char (used here). 081 // Note that Integer.toString() is not called, the conversion is simply 082 // taking the value and adding (mathematically) the ASCII value for '0'. 083 // So, don't change this code! It works and is very fast. 084 085 /** 086 * Required for serialization support. 087 * 088 * @see java.io.Serializable 089 */ 090 private static final long serialVersionUID = 1L; 091 092 /** 093 * FULL locale dependent date or time style. 094 */ 095 public static final int FULL = DateFormat.FULL; 096 /** 097 * LONG locale dependent date or time style. 098 */ 099 public static final int LONG = DateFormat.LONG; 100 /** 101 * MEDIUM locale dependent date or time style. 102 */ 103 public static final int MEDIUM = DateFormat.MEDIUM; 104 /** 105 * SHORT locale dependent date or time style. 106 */ 107 public static final int SHORT = DateFormat.SHORT; 108 109 private static String cDefaultPattern; // lazily initialised by getInstance() 110 111 private static final Map cInstanceCache = new HashMap(7); 112 private static final Map cDateInstanceCache = new HashMap(7); 113 private static final Map cTimeInstanceCache = new HashMap(7); 114 private static final Map cDateTimeInstanceCache = new HashMap(7); 115 private static final Map cTimeZoneDisplayCache = new HashMap(7); 116 117 /** 118 * The pattern. 119 */ 120 private final String mPattern; 121 /** 122 * The time zone. 123 */ 124 private final TimeZone mTimeZone; 125 /** 126 * Whether the time zone overrides any on Calendars. 127 */ 128 private final boolean mTimeZoneForced; 129 /** 130 * The locale. 131 */ 132 private final Locale mLocale; 133 /** 134 * Whether the locale overrides the default. 135 */ 136 private final boolean mLocaleForced; 137 /** 138 * The parsed rules. 139 */ 140 private transient Rule[] mRules; 141 /** 142 * The estimated maximum length. 143 */ 144 private transient int mMaxLengthEstimate; 145 146 //----------------------------------------------------------------------- 147 /** 148 * <p>Gets a formatter instance using the default pattern in the 149 * default locale.</p> 150 * 151 * @return a date/time formatter 152 */ 153 public static FastDateFormat getInstance() { 154 return getInstance(getDefaultPattern(), null, null); 155 } 156 157 /** 158 * <p>Gets a formatter instance using the specified pattern in the 159 * default locale.</p> 160 * 161 * @param pattern {@link java.text.SimpleDateFormat} compatible 162 * pattern 163 * @return a pattern based date/time formatter 164 * @throws IllegalArgumentException if pattern is invalid 165 */ 166 public static FastDateFormat getInstance(String pattern) { 167 return getInstance(pattern, null, null); 168 } 169 170 /** 171 * <p>Gets a formatter instance using the specified pattern and 172 * time zone.</p> 173 * 174 * @param pattern {@link java.text.SimpleDateFormat} compatible 175 * pattern 176 * @param timeZone optional time zone, overrides time zone of 177 * formatted date 178 * @return a pattern based date/time formatter 179 * @throws IllegalArgumentException if pattern is invalid 180 */ 181 public static FastDateFormat getInstance(String pattern, TimeZone timeZone) { 182 return getInstance(pattern, timeZone, null); 183 } 184 185 /** 186 * <p>Gets a formatter instance using the specified pattern and 187 * locale.</p> 188 * 189 * @param pattern {@link java.text.SimpleDateFormat} compatible 190 * pattern 191 * @param locale optional locale, overrides system locale 192 * @return a pattern based date/time formatter 193 * @throws IllegalArgumentException if pattern is invalid 194 */ 195 public static FastDateFormat getInstance(String pattern, Locale locale) { 196 return getInstance(pattern, null, locale); 197 } 198 199 /** 200 * <p>Gets a formatter instance using the specified pattern, time zone 201 * and locale.</p> 202 * 203 * @param pattern {@link java.text.SimpleDateFormat} compatible 204 * pattern 205 * @param timeZone optional time zone, overrides time zone of 206 * formatted date 207 * @param locale optional locale, overrides system locale 208 * @return a pattern based date/time formatter 209 * @throws IllegalArgumentException if pattern is invalid 210 * or <code>null</code> 211 */ 212 public static synchronized FastDateFormat getInstance(String pattern, TimeZone timeZone, Locale locale) { 213 FastDateFormat emptyFormat = new FastDateFormat(pattern, timeZone, locale); 214 FastDateFormat format = (FastDateFormat) cInstanceCache.get(emptyFormat); 215 if (format == null) { 216 format = emptyFormat; 217 format.init(); // convert shell format into usable one 218 cInstanceCache.put(format, format); // this is OK! 219 } 220 return format; 221 } 222 223 //----------------------------------------------------------------------- 224 /** 225 * <p>Gets a date formatter instance using the specified style in the 226 * default time zone and locale.</p> 227 * 228 * @param style date style: FULL, LONG, MEDIUM, or SHORT 229 * @return a localized standard date formatter 230 * @throws IllegalArgumentException if the Locale has no date 231 * pattern defined 232 * @since 2.1 233 */ 234 public static FastDateFormat getDateInstance(int style) { 235 return getDateInstance(style, null, null); 236 } 237 238 /** 239 * <p>Gets a date formatter instance using the specified style and 240 * locale in the default time zone.</p> 241 * 242 * @param style date style: FULL, LONG, MEDIUM, or SHORT 243 * @param locale optional locale, overrides system locale 244 * @return a localized standard date formatter 245 * @throws IllegalArgumentException if the Locale has no date 246 * pattern defined 247 * @since 2.1 248 */ 249 public static FastDateFormat getDateInstance(int style, Locale locale) { 250 return getDateInstance(style, null, locale); 251 } 252 253 /** 254 * <p>Gets a date formatter instance using the specified style and 255 * time zone in the default locale.</p> 256 * 257 * @param style date style: FULL, LONG, MEDIUM, or SHORT 258 * @param timeZone optional time zone, overrides time zone of 259 * formatted date 260 * @return a localized standard date formatter 261 * @throws IllegalArgumentException if the Locale has no date 262 * pattern defined 263 * @since 2.1 264 */ 265 public static FastDateFormat getDateInstance(int style, TimeZone timeZone) { 266 return getDateInstance(style, timeZone, null); 267 } 268 /** 269 * <p>Gets a date formatter instance using the specified style, time 270 * zone and locale.</p> 271 * 272 * @param style date style: FULL, LONG, MEDIUM, or SHORT 273 * @param timeZone optional time zone, overrides time zone of 274 * formatted date 275 * @param locale optional locale, overrides system locale 276 * @return a localized standard date formatter 277 * @throws IllegalArgumentException if the Locale has no date 278 * pattern defined 279 */ 280 public static synchronized FastDateFormat getDateInstance(int style, TimeZone timeZone, Locale locale) { 281 Object key = new Integer(style); 282 if (timeZone != null) { 283 key = new Pair(key, timeZone); 284 } 285 286 if (locale == null) { 287 locale = Locale.getDefault(); 288 } 289 290 key = new Pair(key, locale); 291 292 FastDateFormat format = (FastDateFormat) cDateInstanceCache.get(key); 293 if (format == null) { 294 try { 295 SimpleDateFormat formatter = (SimpleDateFormat) DateFormat.getDateInstance(style, locale); 296 String pattern = formatter.toPattern(); 297 format = getInstance(pattern, timeZone, locale); 298 cDateInstanceCache.put(key, format); 299 300 } catch (ClassCastException ex) { 301 throw new IllegalArgumentException("No date pattern for locale: " + locale); 302 } 303 } 304 return format; 305 } 306 307 //----------------------------------------------------------------------- 308 /** 309 * <p>Gets a time formatter instance using the specified style in the 310 * default time zone and locale.</p> 311 * 312 * @param style time style: FULL, LONG, MEDIUM, or SHORT 313 * @return a localized standard time formatter 314 * @throws IllegalArgumentException if the Locale has no time 315 * pattern defined 316 * @since 2.1 317 */ 318 public static FastDateFormat getTimeInstance(int style) { 319 return getTimeInstance(style, null, null); 320 } 321 322 /** 323 * <p>Gets a time formatter instance using the specified style and 324 * locale in the default time zone.</p> 325 * 326 * @param style time style: FULL, LONG, MEDIUM, or SHORT 327 * @param locale optional locale, overrides system locale 328 * @return a localized standard time formatter 329 * @throws IllegalArgumentException if the Locale has no time 330 * pattern defined 331 * @since 2.1 332 */ 333 public static FastDateFormat getTimeInstance(int style, Locale locale) { 334 return getTimeInstance(style, null, locale); 335 } 336 337 /** 338 * <p>Gets a time formatter instance using the specified style and 339 * time zone in the default locale.</p> 340 * 341 * @param style time style: FULL, LONG, MEDIUM, or SHORT 342 * @param timeZone optional time zone, overrides time zone of 343 * formatted time 344 * @return a localized standard time formatter 345 * @throws IllegalArgumentException if the Locale has no time 346 * pattern defined 347 * @since 2.1 348 */ 349 public static FastDateFormat getTimeInstance(int style, TimeZone timeZone) { 350 return getTimeInstance(style, timeZone, null); 351 } 352 353 /** 354 * <p>Gets a time formatter instance using the specified style, time 355 * zone and locale.</p> 356 * 357 * @param style time style: FULL, LONG, MEDIUM, or SHORT 358 * @param timeZone optional time zone, overrides time zone of 359 * formatted time 360 * @param locale optional locale, overrides system locale 361 * @return a localized standard time formatter 362 * @throws IllegalArgumentException if the Locale has no time 363 * pattern defined 364 */ 365 public static synchronized FastDateFormat getTimeInstance(int style, TimeZone timeZone, Locale locale) { 366 Object key = new Integer(style); 367 if (timeZone != null) { 368 key = new Pair(key, timeZone); 369 } 370 if (locale != null) { 371 key = new Pair(key, locale); 372 } 373 374 FastDateFormat format = (FastDateFormat) cTimeInstanceCache.get(key); 375 if (format == null) { 376 if (locale == null) { 377 locale = Locale.getDefault(); 378 } 379 380 try { 381 SimpleDateFormat formatter = (SimpleDateFormat) DateFormat.getTimeInstance(style, locale); 382 String pattern = formatter.toPattern(); 383 format = getInstance(pattern, timeZone, locale); 384 cTimeInstanceCache.put(key, format); 385 386 } catch (ClassCastException ex) { 387 throw new IllegalArgumentException("No date pattern for locale: " + locale); 388 } 389 } 390 return format; 391 } 392 393 //----------------------------------------------------------------------- 394 /** 395 * <p>Gets a date/time formatter instance using the specified style 396 * in the default time zone and locale.</p> 397 * 398 * @param dateStyle date style: FULL, LONG, MEDIUM, or SHORT 399 * @param timeStyle time style: FULL, LONG, MEDIUM, or SHORT 400 * @return a localized standard date/time formatter 401 * @throws IllegalArgumentException if the Locale has no date/time 402 * pattern defined 403 * @since 2.1 404 */ 405 public static FastDateFormat getDateTimeInstance( 406 int dateStyle, int timeStyle) { 407 return getDateTimeInstance(dateStyle, timeStyle, null, null); 408 } 409 410 /** 411 * <p>Gets a date/time formatter instance using the specified style and 412 * locale in the default time zone.</p> 413 * 414 * @param dateStyle date style: FULL, LONG, MEDIUM, or SHORT 415 * @param timeStyle time style: FULL, LONG, MEDIUM, or SHORT 416 * @param locale optional locale, overrides system locale 417 * @return a localized standard date/time formatter 418 * @throws IllegalArgumentException if the Locale has no date/time 419 * pattern defined 420 * @since 2.1 421 */ 422 public static FastDateFormat getDateTimeInstance( 423 int dateStyle, int timeStyle, Locale locale) { 424 return getDateTimeInstance(dateStyle, timeStyle, null, locale); 425 } 426 427 /** 428 * <p>Gets a date/time formatter instance using the specified style and 429 * time zone in the default locale.</p> 430 * 431 * @param dateStyle date style: FULL, LONG, MEDIUM, or SHORT 432 * @param timeStyle time style: FULL, LONG, MEDIUM, or SHORT 433 * @param timeZone optional time zone, overrides time zone of 434 * formatted date 435 * @return a localized standard date/time formatter 436 * @throws IllegalArgumentException if the Locale has no date/time 437 * pattern defined 438 * @since 2.1 439 */ 440 public static FastDateFormat getDateTimeInstance( 441 int dateStyle, int timeStyle, TimeZone timeZone) { 442 return getDateTimeInstance(dateStyle, timeStyle, timeZone, null); 443 } 444 /** 445 * <p>Gets a date/time formatter instance using the specified style, 446 * time zone and locale.</p> 447 * 448 * @param dateStyle date style: FULL, LONG, MEDIUM, or SHORT 449 * @param timeStyle time style: FULL, LONG, MEDIUM, or SHORT 450 * @param timeZone optional time zone, overrides time zone of 451 * formatted date 452 * @param locale optional locale, overrides system locale 453 * @return a localized standard date/time formatter 454 * @throws IllegalArgumentException if the Locale has no date/time 455 * pattern defined 456 */ 457 public static synchronized FastDateFormat getDateTimeInstance(int dateStyle, int timeStyle, TimeZone timeZone, 458 Locale locale) { 459 460 Object key = new Pair(new Integer(dateStyle), new Integer(timeStyle)); 461 if (timeZone != null) { 462 key = new Pair(key, timeZone); 463 } 464 if (locale == null) { 465 locale = Locale.getDefault(); 466 } 467 key = new Pair(key, locale); 468 469 FastDateFormat format = (FastDateFormat) cDateTimeInstanceCache.get(key); 470 if (format == null) { 471 try { 472 SimpleDateFormat formatter = (SimpleDateFormat) DateFormat.getDateTimeInstance(dateStyle, timeStyle, 473 locale); 474 String pattern = formatter.toPattern(); 475 format = getInstance(pattern, timeZone, locale); 476 cDateTimeInstanceCache.put(key, format); 477 478 } catch (ClassCastException ex) { 479 throw new IllegalArgumentException("No date time pattern for locale: " + locale); 480 } 481 } 482 return format; 483 } 484 485 //----------------------------------------------------------------------- 486 /** 487 * <p>Gets the time zone display name, using a cache for performance.</p> 488 * 489 * @param tz the zone to query 490 * @param daylight true if daylight savings 491 * @param style the style to use <code>TimeZone.LONG</code> 492 * or <code>TimeZone.SHORT</code> 493 * @param locale the locale to use 494 * @return the textual name of the time zone 495 */ 496 static synchronized String getTimeZoneDisplay(TimeZone tz, boolean daylight, int style, Locale locale) { 497 Object key = new TimeZoneDisplayKey(tz, daylight, style, locale); 498 String value = (String) cTimeZoneDisplayCache.get(key); 499 if (value == null) { 500 // This is a very slow call, so cache the results. 501 value = tz.getDisplayName(daylight, style, locale); 502 cTimeZoneDisplayCache.put(key, value); 503 } 504 return value; 505 } 506 507 /** 508 * <p>Gets the default pattern.</p> 509 * 510 * @return the default pattern 511 */ 512 private static synchronized String getDefaultPattern() { 513 if (cDefaultPattern == null) { 514 cDefaultPattern = new SimpleDateFormat().toPattern(); 515 } 516 return cDefaultPattern; 517 } 518 519 // Constructor 520 //----------------------------------------------------------------------- 521 /** 522 * <p>Constructs a new FastDateFormat.</p> 523 * 524 * @param pattern {@link java.text.SimpleDateFormat} compatible 525 * pattern 526 * @param timeZone time zone to use, <code>null</code> means use 527 * default for <code>Date</code> and value within for 528 * <code>Calendar</code> 529 * @param locale locale, <code>null</code> means use system 530 * default 531 * @throws IllegalArgumentException if pattern is invalid or 532 * <code>null</code> 533 */ 534 protected FastDateFormat(String pattern, TimeZone timeZone, Locale locale) { 535 super(); 536 if (pattern == null) { 537 throw new IllegalArgumentException("The pattern must not be null"); 538 } 539 mPattern = pattern; 540 541 mTimeZoneForced = (timeZone != null); 542 if (timeZone == null) { 543 timeZone = TimeZone.getDefault(); 544 } 545 mTimeZone = timeZone; 546 547 mLocaleForced = (locale != null); 548 if (locale == null) { 549 locale = Locale.getDefault(); 550 } 551 mLocale = locale; 552 } 553 554 /** 555 * <p>Initializes the instance for first use.</p> 556 */ 557 protected void init() { 558 List rulesList = parsePattern(); 559 mRules = (Rule[]) rulesList.toArray(new Rule[rulesList.size()]); 560 561 int len = 0; 562 for (int i=mRules.length; --i >= 0; ) { 563 len += mRules[i].estimateLength(); 564 } 565 566 mMaxLengthEstimate = len; 567 } 568 569 // Parse the pattern 570 //----------------------------------------------------------------------- 571 /** 572 * <p>Returns a list of Rules given a pattern.</p> 573 * 574 * @return a <code>List</code> of Rule objects 575 * @throws IllegalArgumentException if pattern is invalid 576 */ 577 protected List parsePattern() { 578 DateFormatSymbols symbols = new DateFormatSymbols(mLocale); 579 List rules = new ArrayList(); 580 581 String[] ERAs = symbols.getEras(); 582 String[] months = symbols.getMonths(); 583 String[] shortMonths = symbols.getShortMonths(); 584 String[] weekdays = symbols.getWeekdays(); 585 String[] shortWeekdays = symbols.getShortWeekdays(); 586 String[] AmPmStrings = symbols.getAmPmStrings(); 587 588 int length = mPattern.length(); 589 int[] indexRef = new int[1]; 590 591 for (int i = 0; i < length; i++) { 592 indexRef[0] = i; 593 String token = parseToken(mPattern, indexRef); 594 i = indexRef[0]; 595 596 int tokenLen = token.length(); 597 if (tokenLen == 0) { 598 break; 599 } 600 601 Rule rule; 602 char c = token.charAt(0); 603 604 switch (c) { 605 case 'G': // era designator (text) 606 rule = new TextField(Calendar.ERA, ERAs); 607 break; 608 case 'y': // year (number) 609 if (tokenLen >= 4) { 610 rule = selectNumberRule(Calendar.YEAR, tokenLen); 611 } else { 612 rule = TwoDigitYearField.INSTANCE; 613 } 614 break; 615 case 'M': // month in year (text and number) 616 if (tokenLen >= 4) { 617 rule = new TextField(Calendar.MONTH, months); 618 } else if (tokenLen == 3) { 619 rule = new TextField(Calendar.MONTH, shortMonths); 620 } else if (tokenLen == 2) { 621 rule = TwoDigitMonthField.INSTANCE; 622 } else { 623 rule = UnpaddedMonthField.INSTANCE; 624 } 625 break; 626 case 'd': // day in month (number) 627 rule = selectNumberRule(Calendar.DAY_OF_MONTH, tokenLen); 628 break; 629 case 'h': // hour in am/pm (number, 1..12) 630 rule = new TwelveHourField(selectNumberRule(Calendar.HOUR, tokenLen)); 631 break; 632 case 'H': // hour in day (number, 0..23) 633 rule = selectNumberRule(Calendar.HOUR_OF_DAY, tokenLen); 634 break; 635 case 'm': // minute in hour (number) 636 rule = selectNumberRule(Calendar.MINUTE, tokenLen); 637 break; 638 case 's': // second in minute (number) 639 rule = selectNumberRule(Calendar.SECOND, tokenLen); 640 break; 641 case 'S': // millisecond (number) 642 rule = selectNumberRule(Calendar.MILLISECOND, tokenLen); 643 break; 644 case 'E': // day in week (text) 645 rule = new TextField(Calendar.DAY_OF_WEEK, tokenLen < 4 ? shortWeekdays : weekdays); 646 break; 647 case 'D': // day in year (number) 648 rule = selectNumberRule(Calendar.DAY_OF_YEAR, tokenLen); 649 break; 650 case 'F': // day of week in month (number) 651 rule = selectNumberRule(Calendar.DAY_OF_WEEK_IN_MONTH, tokenLen); 652 break; 653 case 'w': // week in year (number) 654 rule = selectNumberRule(Calendar.WEEK_OF_YEAR, tokenLen); 655 break; 656 case 'W': // week in month (number) 657 rule = selectNumberRule(Calendar.WEEK_OF_MONTH, tokenLen); 658 break; 659 case 'a': // am/pm marker (text) 660 rule = new TextField(Calendar.AM_PM, AmPmStrings); 661 break; 662 case 'k': // hour in day (1..24) 663 rule = new TwentyFourHourField(selectNumberRule(Calendar.HOUR_OF_DAY, tokenLen)); 664 break; 665 case 'K': // hour in am/pm (0..11) 666 rule = selectNumberRule(Calendar.HOUR, tokenLen); 667 break; 668 case 'z': // time zone (text) 669 if (tokenLen >= 4) { 670 rule = new TimeZoneNameRule(mTimeZone, mTimeZoneForced, mLocale, TimeZone.LONG); 671 } else { 672 rule = new TimeZoneNameRule(mTimeZone, mTimeZoneForced, mLocale, TimeZone.SHORT); 673 } 674 break; 675 case 'Z': // time zone (value) 676 if (tokenLen == 1) { 677 rule = TimeZoneNumberRule.INSTANCE_NO_COLON; 678 } else { 679 rule = TimeZoneNumberRule.INSTANCE_COLON; 680 } 681 break; 682 case '\'': // literal text 683 String sub = token.substring(1); 684 if (sub.length() == 1) { 685 rule = new CharacterLiteral(sub.charAt(0)); 686 } else { 687 rule = new StringLiteral(sub); 688 } 689 break; 690 default: 691 throw new IllegalArgumentException("Illegal pattern component: " + token); 692 } 693 694 rules.add(rule); 695 } 696 697 return rules; 698 } 699 700 /** 701 * <p>Performs the parsing of tokens.</p> 702 * 703 * @param pattern the pattern 704 * @param indexRef index references 705 * @return parsed token 706 */ 707 protected String parseToken(String pattern, int[] indexRef) { 708 StrBuilder buf = new StrBuilder(); 709 710 int i = indexRef[0]; 711 int length = pattern.length(); 712 713 char c = pattern.charAt(i); 714 if (c >= 'A' && c <= 'Z' || c >= 'a' && c <= 'z') { 715 // Scan a run of the same character, which indicates a time 716 // pattern. 717 buf.append(c); 718 719 while (i + 1 < length) { 720 char peek = pattern.charAt(i + 1); 721 if (peek == c) { 722 buf.append(c); 723 i++; 724 } else { 725 break; 726 } 727 } 728 } else { 729 // This will identify token as text. 730 buf.append('\''); 731 732 boolean inLiteral = false; 733 734 for (; i < length; i++) { 735 c = pattern.charAt(i); 736 737 if (c == '\'') { 738 if (i + 1 < length && pattern.charAt(i + 1) == '\'') { 739 // '' is treated as escaped ' 740 i++; 741 buf.append(c); 742 } else { 743 inLiteral = !inLiteral; 744 } 745 } else if (!inLiteral && 746 (c >= 'A' && c <= 'Z' || c >= 'a' && c <= 'z')) { 747 i--; 748 break; 749 } else { 750 buf.append(c); 751 } 752 } 753 } 754 755 indexRef[0] = i; 756 return buf.toString(); 757 } 758 759 /** 760 * <p>Gets an appropriate rule for the padding required.</p> 761 * 762 * @param field the field to get a rule for 763 * @param padding the padding required 764 * @return a new rule with the correct padding 765 */ 766 protected NumberRule selectNumberRule(int field, int padding) { 767 switch (padding) { 768 case 1: 769 return new UnpaddedNumberField(field); 770 case 2: 771 return new TwoDigitNumberField(field); 772 default: 773 return new PaddedNumberField(field, padding); 774 } 775 } 776 777 // Format methods 778 //----------------------------------------------------------------------- 779 /** 780 * <p>Formats a <code>Date</code>, <code>Calendar</code> or 781 * <code>Long</code> (milliseconds) object.</p> 782 * 783 * @param obj the object to format 784 * @param toAppendTo the buffer to append to 785 * @param pos the position - ignored 786 * @return the buffer passed in 787 */ 788 public StringBuffer format(Object obj, StringBuffer toAppendTo, FieldPosition pos) { 789 if (obj instanceof Date) { 790 return format((Date) obj, toAppendTo); 791 } else if (obj instanceof Calendar) { 792 return format((Calendar) obj, toAppendTo); 793 } else if (obj instanceof Long) { 794 return format(((Long) obj).longValue(), toAppendTo); 795 } else { 796 throw new IllegalArgumentException("Unknown class: " + 797 (obj == null ? "<null>" : obj.getClass().getName())); 798 } 799 } 800 801 /** 802 * <p>Formats a millisecond <code>long</code> value.</p> 803 * 804 * @param millis the millisecond value to format 805 * @return the formatted string 806 * @since 2.1 807 */ 808 public String format(long millis) { 809 return format(new Date(millis)); 810 } 811 812 /** 813 * <p>Formats a <code>Date</code> object.</p> 814 * 815 * @param date the date to format 816 * @return the formatted string 817 */ 818 public String format(Date date) { 819 Calendar c = new GregorianCalendar(mTimeZone, mLocale); 820 c.setTime(date); 821 return applyRules(c, new StringBuffer(mMaxLengthEstimate)).toString(); 822 } 823 824 /** 825 * <p>Formats a <code>Calendar</code> object.</p> 826 * 827 * @param calendar the calendar to format 828 * @return the formatted string 829 */ 830 public String format(Calendar calendar) { 831 return format(calendar, new StringBuffer(mMaxLengthEstimate)).toString(); 832 } 833 834 /** 835 * <p>Formats a milliseond <code>long</code> value into the 836 * supplied <code>StringBuffer</code>.</p> 837 * 838 * @param millis the millisecond value to format 839 * @param buf the buffer to format into 840 * @return the specified string buffer 841 * @since 2.1 842 */ 843 public StringBuffer format(long millis, StringBuffer buf) { 844 return format(new Date(millis), buf); 845 } 846 847 /** 848 * <p>Formats a <code>Date</code> object into the 849 * supplied <code>StringBuffer</code>.</p> 850 * 851 * @param date the date to format 852 * @param buf the buffer to format into 853 * @return the specified string buffer 854 */ 855 public StringBuffer format(Date date, StringBuffer buf) { 856 Calendar c = new GregorianCalendar(mTimeZone); 857 c.setTime(date); 858 return applyRules(c, buf); 859 } 860 861 /** 862 * <p>Formats a <code>Calendar</code> object into the 863 * supplied <code>StringBuffer</code>.</p> 864 * 865 * @param calendar the calendar to format 866 * @param buf the buffer to format into 867 * @return the specified string buffer 868 */ 869 public StringBuffer format(Calendar calendar, StringBuffer buf) { 870 if (mTimeZoneForced) { 871 calendar.getTime(); /// LANG-538 872 calendar = (Calendar) calendar.clone(); 873 calendar.setTimeZone(mTimeZone); 874 } 875 return applyRules(calendar, buf); 876 } 877 878 /** 879 * <p>Performs the formatting by applying the rules to the 880 * specified calendar.</p> 881 * 882 * @param calendar the calendar to format 883 * @param buf the buffer to format into 884 * @return the specified string buffer 885 */ 886 protected StringBuffer applyRules(Calendar calendar, StringBuffer buf) { 887 Rule[] rules = mRules; 888 int len = mRules.length; 889 for (int i = 0; i < len; i++) { 890 rules[i].appendTo(buf, calendar); 891 } 892 return buf; 893 } 894 895 // Parsing 896 //----------------------------------------------------------------------- 897 /** 898 * <p>Parsing is not supported.</p> 899 * 900 * @param source the string to parse 901 * @param pos the parsing position 902 * @return <code>null</code> as not supported 903 */ 904 public Object parseObject(String source, ParsePosition pos) { 905 pos.setIndex(0); 906 pos.setErrorIndex(0); 907 return null; 908 } 909 910 // Accessors 911 //----------------------------------------------------------------------- 912 /** 913 * <p>Gets the pattern used by this formatter.</p> 914 * 915 * @return the pattern, {@link java.text.SimpleDateFormat} compatible 916 */ 917 public String getPattern() { 918 return mPattern; 919 } 920 921 /** 922 * <p>Gets the time zone used by this formatter.</p> 923 * 924 * <p>This zone is always used for <code>Date</code> formatting. 925 * If a <code>Calendar</code> is passed in to be formatted, the 926 * time zone on that may be used depending on 927 * {@link #getTimeZoneOverridesCalendar()}.</p> 928 * 929 * @return the time zone 930 */ 931 public TimeZone getTimeZone() { 932 return mTimeZone; 933 } 934 935 /** 936 * <p>Returns <code>true</code> if the time zone of the 937 * calendar overrides the time zone of the formatter.</p> 938 * 939 * @return <code>true</code> if time zone of formatter 940 * overridden for calendars 941 */ 942 public boolean getTimeZoneOverridesCalendar() { 943 return mTimeZoneForced; 944 } 945 946 /** 947 * <p>Gets the locale used by this formatter.</p> 948 * 949 * @return the locale 950 */ 951 public Locale getLocale() { 952 return mLocale; 953 } 954 955 /** 956 * <p>Gets an estimate for the maximum string length that the 957 * formatter will produce.</p> 958 * 959 * <p>The actual formatted length will almost always be less than or 960 * equal to this amount.</p> 961 * 962 * @return the maximum formatted length 963 */ 964 public int getMaxLengthEstimate() { 965 return mMaxLengthEstimate; 966 } 967 968 // Basics 969 //----------------------------------------------------------------------- 970 /** 971 * <p>Compares two objects for equality.</p> 972 * 973 * @param obj the object to compare to 974 * @return <code>true</code> if equal 975 */ 976 public boolean equals(Object obj) { 977 if (obj instanceof FastDateFormat == false) { 978 return false; 979 } 980 FastDateFormat other = (FastDateFormat) obj; 981 if ( 982 (mPattern == other.mPattern || mPattern.equals(other.mPattern)) && 983 (mTimeZone == other.mTimeZone || mTimeZone.equals(other.mTimeZone)) && 984 (mLocale == other.mLocale || mLocale.equals(other.mLocale)) && 985 (mTimeZoneForced == other.mTimeZoneForced) && 986 (mLocaleForced == other.mLocaleForced) 987 ) { 988 return true; 989 } 990 return false; 991 } 992 993 /** 994 * <p>Returns a hashcode compatible with equals.</p> 995 * 996 * @return a hashcode compatible with equals 997 */ 998 public int hashCode() { 999 int total = 0; 1000 total += mPattern.hashCode(); 1001 total += mTimeZone.hashCode(); 1002 total += (mTimeZoneForced ? 1 : 0); 1003 total += mLocale.hashCode(); 1004 total += (mLocaleForced ? 1 : 0); 1005 return total; 1006 } 1007 1008 /** 1009 * <p>Gets a debugging string version of this formatter.</p> 1010 * 1011 * @return a debugging string 1012 */ 1013 public String toString() { 1014 return "FastDateFormat[" + mPattern + "]"; 1015 } 1016 1017 // Serializing 1018 //----------------------------------------------------------------------- 1019 /** 1020 * Create the object after serialization. This implementation reinitializes the 1021 * transient properties. 1022 * 1023 * @param in ObjectInputStream from which the object is being deserialized. 1024 * @throws IOException if there is an IO issue. 1025 * @throws ClassNotFoundException if a class cannot be found. 1026 */ 1027 private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException { 1028 in.defaultReadObject(); 1029 init(); 1030 } 1031 1032 // Rules 1033 //----------------------------------------------------------------------- 1034 /** 1035 * <p>Inner class defining a rule.</p> 1036 */ 1037 private interface Rule { 1038 /** 1039 * Returns the estimated lentgh of the result. 1040 * 1041 * @return the estimated length 1042 */ 1043 int estimateLength(); 1044 1045 /** 1046 * Appends the value of the specified calendar to the output buffer based on the rule implementation. 1047 * 1048 * @param buffer the output buffer 1049 * @param calendar calendar to be appended 1050 */ 1051 void appendTo(StringBuffer buffer, Calendar calendar); 1052 } 1053 1054 /** 1055 * <p>Inner class defining a numeric rule.</p> 1056 */ 1057 private interface NumberRule extends Rule { 1058 /** 1059 * Appends the specified value to the output buffer based on the rule implementation. 1060 * 1061 * @param buffer the output buffer 1062 * @param value the value to be appended 1063 */ 1064 void appendTo(StringBuffer buffer, int value); 1065 } 1066 1067 /** 1068 * <p>Inner class to output a constant single character.</p> 1069 */ 1070 private static class CharacterLiteral implements Rule { 1071 private final char mValue; 1072 1073 /** 1074 * Constructs a new instance of <code>CharacterLiteral</code> 1075 * to hold the specified value. 1076 * 1077 * @param value the character literal 1078 */ 1079 CharacterLiteral(char value) { 1080 mValue = value; 1081 } 1082 1083 /** 1084 * {@inheritDoc} 1085 */ 1086 public int estimateLength() { 1087 return 1; 1088 } 1089 1090 /** 1091 * {@inheritDoc} 1092 */ 1093 public void appendTo(StringBuffer buffer, Calendar calendar) { 1094 buffer.append(mValue); 1095 } 1096 } 1097 1098 /** 1099 * <p>Inner class to output a constant string.</p> 1100 */ 1101 private static class StringLiteral implements Rule { 1102 private final String mValue; 1103 1104 /** 1105 * Constructs a new instance of <code>StringLiteral</code> 1106 * to hold the specified value. 1107 * 1108 * @param value the string literal 1109 */ 1110 StringLiteral(String value) { 1111 mValue = value; 1112 } 1113 1114 /** 1115 * {@inheritDoc} 1116 */ 1117 public int estimateLength() { 1118 return mValue.length(); 1119 } 1120 1121 /** 1122 * {@inheritDoc} 1123 */ 1124 public void appendTo(StringBuffer buffer, Calendar calendar) { 1125 buffer.append(mValue); 1126 } 1127 } 1128 1129 /** 1130 * <p>Inner class to output one of a set of values.</p> 1131 */ 1132 private static class TextField implements Rule { 1133 private final int mField; 1134 private final String[] mValues; 1135 1136 /** 1137 * Constructs an instance of <code>TextField</code> 1138 * with the specified field and values. 1139 * 1140 * @param field the field 1141 * @param values the field values 1142 */ 1143 TextField(int field, String[] values) { 1144 mField = field; 1145 mValues = values; 1146 } 1147 1148 /** 1149 * {@inheritDoc} 1150 */ 1151 public int estimateLength() { 1152 int max = 0; 1153 for (int i=mValues.length; --i >= 0; ) { 1154 int len = mValues[i].length(); 1155 if (len > max) { 1156 max = len; 1157 } 1158 } 1159 return max; 1160 } 1161 1162 /** 1163 * {@inheritDoc} 1164 */ 1165 public void appendTo(StringBuffer buffer, Calendar calendar) { 1166 buffer.append(mValues[calendar.get(mField)]); 1167 } 1168 } 1169 1170 /** 1171 * <p>Inner class to output an unpadded number.</p> 1172 */ 1173 private static class UnpaddedNumberField implements NumberRule { 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 }