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