KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > apache > commons > lang > time > FastDateFormat


1 /*
2  * Copyright 2002-2005 The Apache Software Foundation.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */

16 package org.apache.commons.lang.time;
17
18 import java.text.DateFormat JavaDoc;
19 import java.text.DateFormatSymbols JavaDoc;
20 import java.text.FieldPosition JavaDoc;
21 import java.text.Format JavaDoc;
22 import java.text.ParsePosition JavaDoc;
23 import java.text.SimpleDateFormat JavaDoc;
24 import java.util.ArrayList JavaDoc;
25 import java.util.Calendar JavaDoc;
26 import java.util.Date JavaDoc;
27 import java.util.GregorianCalendar JavaDoc;
28 import java.util.HashMap JavaDoc;
29 import java.util.List JavaDoc;
30 import java.util.Locale JavaDoc;
31 import java.util.Map JavaDoc;
32 import java.util.TimeZone JavaDoc;
33
34 /**
35  * <p>FastDateFormat is a fast and thread-safe version of
36  * {@link java.text.SimpleDateFormat}.</p>
37  *
38  * <p>This class can be used as a direct replacement to
39  * <code>SimpleDateFormat</code> in most formatting situations.
40  * This class is especially useful in multi-threaded server environments.
41  * <code>SimpleDateFormat</code> is not thread-safe in any JDK version,
42  * nor will it be as Sun have closed the bug/RFE.
43  * </p>
44  *
45  * <p>Only formatting is supported, but all patterns are compatible with
46  * SimpleDateFormat (except time zones - see below).</p>
47  *
48  * <p>Java 1.4 introduced a new pattern letter, <code>'Z'</code>, to represent
49  * time zones in RFC822 format (eg. <code>+0800</code> or <code>-1100</code>).
50  * This pattern letter can be used here (on all JDK versions).</p>
51  *
52  * <p>In addition, the pattern <code>'ZZ'</code> has been made to represent
53  * ISO8601 full format time zones (eg. <code>+08:00</code> or <code>-11:00</code>).
54  * This introduces a minor incompatibility with Java 1.4, but at a gain of
55  * useful functionality.</p>
56  *
57  * @author TeaTrove project
58  * @author Brian S O'Neill
59  * @author Sean Schofield
60  * @author Gary Gregory
61  * @author Stephen Colebourne
62  * @author Nikolay Metchev
63  * @since 2.0
64  * @version $Id: FastDateFormat.java 165657 2005-05-02 18:31:49Z ggregory $
65  */

66 public class FastDateFormat extends Format JavaDoc {
67     // A lot of the speed in this class comes from caching, but some comes
68
// from the special int to StringBuffer conversion.
69
//
70
// The following produces a padded 2 digit number:
71
// buffer.append((char)(value / 10 + '0'));
72
// buffer.append((char)(value % 10 + '0'));
73
//
74
// Note that the fastest append to StringBuffer is a single char (used here).
75
// Note that Integer.toString() is not called, the conversion is simply
76
// taking the value and adding (mathematically) the ASCII value for '0'.
77
// So, don't change this code! It works and is very fast.
78

79     /**
80      * FULL locale dependent date or time style.
81      */

82     public static final int FULL = DateFormat.FULL;
83     /**
84      * LONG locale dependent date or time style.
85      */

86     public static final int LONG = DateFormat.LONG;
87     /**
88      * MEDIUM locale dependent date or time style.
89      */

90     public static final int MEDIUM = DateFormat.MEDIUM;
91     /**
92      * SHORT locale dependent date or time style.
93      */

94     public static final int SHORT = DateFormat.SHORT;
95     
96     // package scoped as used by inner class
97
static final double LOG_10 = Math.log(10);
98
99     private static String JavaDoc cDefaultPattern;
100
101     private static Map JavaDoc cInstanceCache = new HashMap JavaDoc(7);
102     private static Map JavaDoc cDateInstanceCache = new HashMap JavaDoc(7);
103     private static Map JavaDoc cTimeInstanceCache = new HashMap JavaDoc(7);
104     private static Map JavaDoc cDateTimeInstanceCache = new HashMap JavaDoc(7);
105     private static Map JavaDoc cTimeZoneDisplayCache = new HashMap JavaDoc(7);
106
107     /**
108      * The pattern.
109      */

110     private final String JavaDoc mPattern;
111     /**
112      * The time zone.
113      */

114     private final TimeZone JavaDoc mTimeZone;
115     /**
116      * Whether the time zone overrides any on Calendars.
117      */

118     private final boolean mTimeZoneForced;
119     /**
120      * The locale.
121      */

122     private final Locale JavaDoc mLocale;
123     /**
124      * Whether the locale overrides the default.
125      */

126     private final boolean mLocaleForced;
127     /**
128      * The parsed rules.
129      */

130     private Rule[] mRules;
131     /**
132      * The estimated maximum length.
133      */

134     private int mMaxLengthEstimate;
135
136     //-----------------------------------------------------------------------
137
/**
138      * <p>Gets a formatter instance using the default pattern in the
139      * default locale.</p>
140      *
141      * @return a date/time formatter
142      */

143     public static FastDateFormat getInstance() {
144         return getInstance(getDefaultPattern(), null, null);
145     }
146
147     /**
148      * <p>Gets a formatter instance using the specified pattern in the
149      * default locale.</p>
150      *
151      * @param pattern {@link java.text.SimpleDateFormat} compatible
152      * pattern
153      * @return a pattern based date/time formatter
154      * @throws IllegalArgumentException if pattern is invalid
155      */

156     public static FastDateFormat getInstance(String JavaDoc pattern) {
157         return getInstance(pattern, null, null);
158     }
159
160     /**
161      * <p>Gets a formatter instance using the specified pattern and
162      * time zone.</p>
163      *
164      * @param pattern {@link java.text.SimpleDateFormat} compatible
165      * pattern
166      * @param timeZone optional time zone, overrides time zone of
167      * formatted date
168      * @return a pattern based date/time formatter
169      * @throws IllegalArgumentException if pattern is invalid
170      */

171     public static FastDateFormat getInstance(String JavaDoc pattern, TimeZone JavaDoc timeZone) {
172         return getInstance(pattern, timeZone, null);
173     }
174
175     /**
176      * <p>Gets a formatter instance using the specified pattern and
177      * locale.</p>
178      *
179      * @param pattern {@link java.text.SimpleDateFormat} compatible
180      * pattern
181      * @param locale optional locale, overrides system locale
182      * @return a pattern based date/time formatter
183      * @throws IllegalArgumentException if pattern is invalid
184      */

185     public static FastDateFormat getInstance(String JavaDoc pattern, Locale JavaDoc locale) {
186         return getInstance(pattern, null, locale);
187     }
188
189     /**
190      * <p>Gets a formatter instance using the specified pattern, time zone
191      * and locale.</p>
192      *
193      * @param pattern {@link java.text.SimpleDateFormat} compatible
194      * pattern
195      * @param timeZone optional time zone, overrides time zone of
196      * formatted date
197      * @param locale optional locale, overrides system locale
198      * @return a pattern based date/time formatter
199      * @throws IllegalArgumentException if pattern is invalid
200      * or <code>null</code>
201      */

202     public static synchronized FastDateFormat getInstance(String JavaDoc pattern, TimeZone JavaDoc timeZone, Locale JavaDoc locale) {
203         FastDateFormat emptyFormat = new FastDateFormat(pattern, timeZone, locale);
204         FastDateFormat format = (FastDateFormat) cInstanceCache.get(emptyFormat);
205         if (format == null) {
206             format = emptyFormat;
207             format.init(); // convert shell format into usable one
208
cInstanceCache.put(format, format); // this is OK!
209
}
210         return format;
211     }
212
213     //-----------------------------------------------------------------------
214
/**
215      * <p>Gets a date formatter instance using the specified style in the
216      * default time zone and locale.</p>
217      *
218      * @param style date style: FULL, LONG, MEDIUM, or SHORT
219      * @return a localized standard date formatter
220      * @throws IllegalArgumentException if the Locale has no date
221      * pattern defined
222      * @since 2.1
223      */

224     public static FastDateFormat getDateInstance(int style) {
225         return getDateInstance(style, null, null);
226     }
227
228     /**
229      * <p>Gets a date formatter instance using the specified style and
230      * locale in the default time zone.</p>
231      *
232      * @param style date style: FULL, LONG, MEDIUM, or SHORT
233      * @param locale optional locale, overrides system locale
234      * @return a localized standard date formatter
235      * @throws IllegalArgumentException if the Locale has no date
236      * pattern defined
237      * @since 2.1
238      */

239     public static FastDateFormat getDateInstance(int style, Locale JavaDoc locale) {
240         return getDateInstance(style, null, locale);
241     }
242
243     /**
244      * <p>Gets a date formatter instance using the specified style and
245      * time zone in the default locale.</p>
246      *
247      * @param style date style: FULL, LONG, MEDIUM, or SHORT
248      * @param timeZone optional time zone, overrides time zone of
249      * formatted date
250      * @return a localized standard date formatter
251      * @throws IllegalArgumentException if the Locale has no date
252      * pattern defined
253      * @since 2.1
254      */

255     public static FastDateFormat getDateInstance(int style, TimeZone JavaDoc timeZone) {
256         return getDateInstance(style, timeZone, null);
257     }
258     /**
259      * <p>Gets a date formatter instance using the specified style, time
260      * zone and locale.</p>
261      *
262      * @param style date style: FULL, LONG, MEDIUM, or SHORT
263      * @param timeZone optional time zone, overrides time zone of
264      * formatted date
265      * @param locale optional locale, overrides system locale
266      * @return a localized standard date formatter
267      * @throws IllegalArgumentException if the Locale has no date
268      * pattern defined
269      */

270     public static synchronized FastDateFormat getDateInstance(int style, TimeZone JavaDoc timeZone, Locale JavaDoc locale) {
271         Object JavaDoc key = new Integer JavaDoc(style);
272         if (timeZone != null) {
273             key = new Pair(key, timeZone);
274         }
275         if (locale != null) {
276             key = new Pair(key, locale);
277         }
278
279         FastDateFormat format = (FastDateFormat) cDateInstanceCache.get(key);
280         if (format == null) {
281             if (locale == null) {
282                 locale = Locale.getDefault();
283             }
284
285             try {
286                 SimpleDateFormat JavaDoc formatter = (SimpleDateFormat JavaDoc) DateFormat.getDateInstance(style, locale);
287                 String JavaDoc pattern = formatter.toPattern();
288                 format = getInstance(pattern, timeZone, locale);
289                 cDateInstanceCache.put(key, format);
290                 
291             } catch (ClassCastException JavaDoc ex) {
292                 throw new IllegalArgumentException JavaDoc("No date pattern for locale: " + locale);
293             }
294         }
295         return format;
296     }
297
298     //-----------------------------------------------------------------------
299
/**
300      * <p>Gets a time formatter instance using the specified style in the
301      * default time zone and locale.</p>
302      *
303      * @param style time style: FULL, LONG, MEDIUM, or SHORT
304      * @return a localized standard time formatter
305      * @throws IllegalArgumentException if the Locale has no time
306      * pattern defined
307      * @since 2.1
308      */

309     public static FastDateFormat getTimeInstance(int style) {
310         return getTimeInstance(style, null, null);
311     }
312
313     /**
314      * <p>Gets a time formatter instance using the specified style and
315      * locale in the default time zone.</p>
316      *
317      * @param style time style: FULL, LONG, MEDIUM, or SHORT
318      * @param locale optional locale, overrides system locale
319      * @return a localized standard time formatter
320      * @throws IllegalArgumentException if the Locale has no time
321      * pattern defined
322      * @since 2.1
323      */

324     public static FastDateFormat getTimeInstance(int style, Locale JavaDoc locale) {
325         return getTimeInstance(style, null, locale);
326     }
327     
328     /**
329      * <p>Gets a time formatter instance using the specified style and
330      * time zone in the default locale.</p>
331      *
332      * @param style time style: FULL, LONG, MEDIUM, or SHORT
333      * @param timeZone optional time zone, overrides time zone of
334      * formatted time
335      * @return a localized standard time formatter
336      * @throws IllegalArgumentException if the Locale has no time
337      * pattern defined
338      * @since 2.1
339      */

340     public static FastDateFormat getTimeInstance(int style, TimeZone JavaDoc timeZone) {
341         return getTimeInstance(style, timeZone, null);
342     }
343     
344     /**
345      * <p>Gets a time formatter instance using the specified style, time
346      * zone and locale.</p>
347      *
348      * @param style time style: FULL, LONG, MEDIUM, or SHORT
349      * @param timeZone optional time zone, overrides time zone of
350      * formatted time
351      * @param locale optional locale, overrides system locale
352      * @return a localized standard time formatter
353      * @throws IllegalArgumentException if the Locale has no time
354      * pattern defined
355      */

356     public static synchronized FastDateFormat getTimeInstance(int style, TimeZone JavaDoc timeZone, Locale JavaDoc locale) {
357         Object JavaDoc key = new Integer JavaDoc(style);
358         if (timeZone != null) {
359             key = new Pair(key, timeZone);
360         }
361         if (locale != null) {
362             key = new Pair(key, locale);
363         }
364
365         FastDateFormat format = (FastDateFormat) cTimeInstanceCache.get(key);
366         if (format == null) {
367             if (locale == null) {
368                 locale = Locale.getDefault();
369             }
370
371             try {
372                 SimpleDateFormat JavaDoc formatter = (SimpleDateFormat JavaDoc) DateFormat.getTimeInstance(style, locale);
373                 String JavaDoc pattern = formatter.toPattern();
374                 format = getInstance(pattern, timeZone, locale);
375                 cTimeInstanceCache.put(key, format);
376             
377             } catch (ClassCastException JavaDoc ex) {
378                 throw new IllegalArgumentException JavaDoc("No date pattern for locale: " + locale);
379             }
380         }
381         return format;
382     }
383
384     //-----------------------------------------------------------------------
385
/**
386      * <p>Gets a date/time formatter instance using the specified style
387      * in the default time zone and locale.</p>
388      *
389      * @param dateStyle date style: FULL, LONG, MEDIUM, or SHORT
390      * @param timeStyle time style: FULL, LONG, MEDIUM, or SHORT
391      * @return a localized standard date/time formatter
392      * @throws IllegalArgumentException if the Locale has no date/time
393      * pattern defined
394      * @since 2.1
395      */

396     public static FastDateFormat getDateTimeInstance(
397             int dateStyle, int timeStyle) {
398         return getDateTimeInstance(dateStyle, timeStyle, null, null);
399     }
400     
401     /**
402      * <p>Gets a date/time formatter instance using the specified style and
403      * locale in the default time zone.</p>
404      *
405      * @param dateStyle date style: FULL, LONG, MEDIUM, or SHORT
406      * @param timeStyle time style: FULL, LONG, MEDIUM, or SHORT
407      * @param locale optional locale, overrides system locale
408      * @return a localized standard date/time formatter
409      * @throws IllegalArgumentException if the Locale has no date/time
410      * pattern defined
411      * @since 2.1
412      */

413     public static FastDateFormat getDateTimeInstance(
414             int dateStyle, int timeStyle, Locale JavaDoc locale) {
415         return getDateTimeInstance(dateStyle, timeStyle, null, locale);
416     }
417     
418     /**
419      * <p>Gets a date/time formatter instance using the specified style and
420      * time zone in the default locale.</p>
421      *
422      * @param dateStyle date style: FULL, LONG, MEDIUM, or SHORT
423      * @param timeStyle time style: FULL, LONG, MEDIUM, or SHORT
424      * @param timeZone optional time zone, overrides time zone of
425      * formatted date
426      * @return a localized standard date/time formatter
427      * @throws IllegalArgumentException if the Locale has no date/time
428      * pattern defined
429      * @since 2.1
430      */

431     public static FastDateFormat getDateTimeInstance(
432             int dateStyle, int timeStyle, TimeZone JavaDoc timeZone) {
433         return getDateTimeInstance(dateStyle, timeStyle, timeZone, null);
434     }
435     /**
436      * <p>Gets a date/time formatter instance using the specified style,
437      * time zone and locale.</p>
438      *
439      * @param dateStyle date style: FULL, LONG, MEDIUM, or SHORT
440      * @param timeStyle time style: FULL, LONG, MEDIUM, or SHORT
441      * @param timeZone optional time zone, overrides time zone of
442      * formatted date
443      * @param locale optional locale, overrides system locale
444      * @return a localized standard date/time formatter
445      * @throws IllegalArgumentException if the Locale has no date/time
446      * pattern defined
447      */

448     public static synchronized FastDateFormat getDateTimeInstance(int dateStyle, int timeStyle, TimeZone JavaDoc timeZone,
449             Locale JavaDoc locale) {
450
451         Object JavaDoc key = new Pair(new Integer JavaDoc(dateStyle), new Integer JavaDoc(timeStyle));
452         if (timeZone != null) {
453             key = new Pair(key, timeZone);
454         }
455         if (locale != null) {
456             key = new Pair(key, locale);
457         }
458
459         FastDateFormat format = (FastDateFormat) cDateTimeInstanceCache.get(key);
460         if (format == null) {
461             if (locale == null) {
462                 locale = Locale.getDefault();
463             }
464
465             try {
466                 SimpleDateFormat JavaDoc formatter = (SimpleDateFormat JavaDoc) DateFormat.getDateTimeInstance(dateStyle, timeStyle,
467                         locale);
468                 String JavaDoc pattern = formatter.toPattern();
469                 format = getInstance(pattern, timeZone, locale);
470                 cDateTimeInstanceCache.put(key, format);
471
472             } catch (ClassCastException JavaDoc ex) {
473                 throw new IllegalArgumentException JavaDoc("No date time pattern for locale: " + locale);
474             }
475         }
476         return format;
477     }
478
479     //-----------------------------------------------------------------------
480
/**
481      * <p>Gets the time zone display name, using a cache for performance.</p>
482      *
483      * @param tz the zone to query
484      * @param daylight true if daylight savings
485      * @param style the style to use <code>TimeZone.LONG</code>
486      * or <code>TimeZone.SHORT</code>
487      * @param locale the locale to use
488      * @return the textual name of the time zone
489      */

490     static synchronized String JavaDoc getTimeZoneDisplay(TimeZone JavaDoc tz, boolean daylight, int style, Locale JavaDoc locale) {
491         Object JavaDoc key = new TimeZoneDisplayKey(tz, daylight, style, locale);
492         String JavaDoc value = (String JavaDoc) cTimeZoneDisplayCache.get(key);
493         if (value == null) {
494             // This is a very slow call, so cache the results.
495
value = tz.getDisplayName(daylight, style, locale);
496             cTimeZoneDisplayCache.put(key, value);
497         }
498         return value;
499     }
500
501     /**
502      * <p>Gets the default pattern.</p>
503      *
504      * @return the default pattern
505      */

506     private static synchronized String JavaDoc getDefaultPattern() {
507         if (cDefaultPattern == null) {
508             cDefaultPattern = new SimpleDateFormat JavaDoc().toPattern();
509         }
510         return cDefaultPattern;
511     }
512
513     // Constructor
514
//-----------------------------------------------------------------------
515
/**
516      * <p>Constructs a new FastDateFormat.</p>
517      *
518      * @param pattern {@link java.text.SimpleDateFormat} compatible
519      * pattern
520      * @param timeZone time zone to use, <code>null</code> means use
521      * default for <code>Date</code> and value within for
522      * <code>Calendar</code>
523      * @param locale locale, <code>null</code> means use system
524      * default
525      * @throws IllegalArgumentException if pattern is invalid or
526      * <code>null</code>
527      */

528     protected FastDateFormat(String JavaDoc pattern, TimeZone JavaDoc timeZone, Locale JavaDoc locale) {
529         super();
530         if (pattern == null) {
531             throw new IllegalArgumentException JavaDoc("The pattern must not be null");
532         }
533         mPattern = pattern;
534         
535         mTimeZoneForced = (timeZone != null);
536         if (timeZone == null) {
537             timeZone = TimeZone.getDefault();
538         }
539         mTimeZone = timeZone;
540         
541         mLocaleForced = (locale != null);
542         if (locale == null) {
543             locale = Locale.getDefault();
544         }
545         mLocale = locale;
546     }
547
548     /**
549      * <p>Initialise the instance for first use.</p>
550      */

551     protected void init() {
552         List JavaDoc rulesList = parsePattern();
553         mRules = (Rule[]) rulesList.toArray(new Rule[rulesList.size()]);
554
555         int len = 0;
556         for (int i=mRules.length; --i >= 0; ) {
557             len += mRules[i].estimateLength();
558         }
559
560         mMaxLengthEstimate = len;
561     }
562
563     // Parse the pattern
564
//-----------------------------------------------------------------------
565
/**
566      * <p>Returns a list of Rules given a pattern.</p>
567      *
568      * @return a <code>List</code> of Rule objects
569      * @throws IllegalArgumentException if pattern is invalid
570      */

571     protected List JavaDoc parsePattern() {
572         DateFormatSymbols JavaDoc symbols = new DateFormatSymbols JavaDoc(mLocale);
573         List JavaDoc rules = new ArrayList JavaDoc();
574
575         String JavaDoc[] ERAs = symbols.getEras();
576         String JavaDoc[] months = symbols.getMonths();
577         String JavaDoc[] shortMonths = symbols.getShortMonths();
578         String JavaDoc[] weekdays = symbols.getWeekdays();
579         String JavaDoc[] shortWeekdays = symbols.getShortWeekdays();
580         String JavaDoc[] AmPmStrings = symbols.getAmPmStrings();
581
582         int length = mPattern.length();
583         int[] indexRef = new int[1];
584
585         for (int i = 0; i < length; i++) {
586             indexRef[0] = i;
587             String JavaDoc token = parseToken(mPattern, indexRef);
588             i = indexRef[0];
589
590             int tokenLen = token.length();
591             if (tokenLen == 0) {
592                 break;
593             }
594
595             Rule rule;
596             char c = token.charAt(0);
597
598             switch (c) {
599             case 'G': // era designator (text)
600
rule = new TextField(Calendar.ERA, ERAs);
601                 break;
602             case 'y': // year (number)
603
if (tokenLen >= 4) {
604                     rule = selectNumberRule(Calendar.YEAR, tokenLen);
605                 } else {
606                     rule = TwoDigitYearField.INSTANCE;
607                 }
608                 break;
609             case 'M': // month in year (text and number)
610
if (tokenLen >= 4) {
611                     rule = new TextField(Calendar.MONTH, months);
612                 } else if (tokenLen == 3) {
613                     rule = new TextField(Calendar.MONTH, shortMonths);
614                 } else if (tokenLen == 2) {
615                     rule = TwoDigitMonthField.INSTANCE;
616                 } else {
617                     rule = UnpaddedMonthField.INSTANCE;
618                 }
619                 break;
620             case 'd': // day in month (number)
621
rule = selectNumberRule(Calendar.DAY_OF_MONTH, tokenLen);
622                 break;
623             case 'h': // hour in am/pm (number, 1..12)
624
rule = new TwelveHourField(selectNumberRule(Calendar.HOUR, tokenLen));
625                 break;
626             case 'H': // hour in day (number, 0..23)
627
rule = selectNumberRule(Calendar.HOUR_OF_DAY, tokenLen);
628                 break;
629             case 'm': // minute in hour (number)
630
rule = selectNumberRule(Calendar.MINUTE, tokenLen);
631                 break;
632             case 's': // second in minute (number)
633
rule = selectNumberRule(Calendar.SECOND, tokenLen);
634                 break;
635             case 'S': // millisecond (number)
636
rule = selectNumberRule(Calendar.MILLISECOND, tokenLen);
637                 break;
638             case 'E': // day in week (text)
639
rule = new TextField(Calendar.DAY_OF_WEEK, tokenLen < 4 ? shortWeekdays : weekdays);
640                 break;
641             case 'D': // day in year (number)
642
rule = selectNumberRule(Calendar.DAY_OF_YEAR, tokenLen);
643                 break;
644             case 'F': // day of week in month (number)
645
rule = selectNumberRule(Calendar.DAY_OF_WEEK_IN_MONTH, tokenLen);
646                 break;
647             case 'w': // week in year (number)
648
rule = selectNumberRule(Calendar.WEEK_OF_YEAR, tokenLen);
649                 break;
650             case 'W': // week in month (number)
651
rule = selectNumberRule(Calendar.WEEK_OF_MONTH, tokenLen);
652                 break;
653             case 'a': // am/pm marker (text)
654
rule = new TextField(Calendar.AM_PM, AmPmStrings);
655                 break;
656             case 'k': // hour in day (1..24)
657
rule = new TwentyFourHourField(selectNumberRule(Calendar.HOUR_OF_DAY, tokenLen));
658                 break;
659             case 'K': // hour in am/pm (0..11)
660
rule = selectNumberRule(Calendar.HOUR, tokenLen);
661                 break;
662             case 'z': // time zone (text)
663
if (tokenLen >= 4) {
664                     rule = new TimeZoneNameRule(mTimeZone, mTimeZoneForced, mLocale, TimeZone.LONG);
665                 } else {
666                     rule = new TimeZoneNameRule(mTimeZone, mTimeZoneForced, mLocale, TimeZone.SHORT);
667                 }
668                 break;
669             case 'Z': // time zone (value)
670
if (tokenLen == 1) {
671                     rule = TimeZoneNumberRule.INSTANCE_NO_COLON;
672                 } else {
673                     rule = TimeZoneNumberRule.INSTANCE_COLON;
674                 }
675                 break;
676             case '\'': // literal text
677
String JavaDoc sub = token.substring(1);
678                 if (sub.length() == 1) {
679                     rule = new CharacterLiteral(sub.charAt(0));
680                 } else {
681                     rule = new StringLiteral(sub);
682                 }
683                 break;
684             default:
685                 throw new IllegalArgumentException JavaDoc("Illegal pattern component: " + token);
686             }
687
688             rules.add(rule);
689         }
690
691         return rules;
692     }
693
694     /**
695      * <p>Performs the parsing of tokens.</p>
696      *
697      * @param pattern the pattern
698      * @param indexRef index references
699      * @return parsed token
700      */

701     protected String JavaDoc parseToken(String JavaDoc pattern, int[] indexRef) {
702         StringBuffer JavaDoc buf = new StringBuffer JavaDoc();
703
704         int i = indexRef[0];
705         int length = pattern.length();
706
707         char c = pattern.charAt(i);
708         if (c >= 'A' && c <= 'Z' || c >= 'a' && c <= 'z') {
709             // Scan a run of the same character, which indicates a time
710
// pattern.
711
buf.append(c);
712
713             while (i + 1 < length) {
714                 char peek = pattern.charAt(i + 1);
715                 if (peek == c) {
716                     buf.append(c);
717                     i++;
718                 } else {
719                     break;
720                 }
721             }
722         } else {
723             // This will identify token as text.
724
buf.append('\'');
725
726             boolean inLiteral = false;
727
728             for (; i < length; i++) {
729                 c = pattern.charAt(i);
730
731                 if (c == '\'') {
732                     if (i + 1 < length && pattern.charAt(i + 1) == '\'') {
733                         // '' is treated as escaped '
734
i++;
735                         buf.append(c);
736                     } else {
737                         inLiteral = !inLiteral;
738                     }
739                 } else if (!inLiteral &&
740                          (c >= 'A' && c <= 'Z' || c >= 'a' && c <= 'z')) {
741                     i--;
742                     break;
743                 } else {
744                     buf.append(c);
745                 }
746             }
747         }
748
749         indexRef[0] = i;
750         return buf.toString();
751     }
752
753     /**
754      * <p>Gets an appropriate rule for the padding required.</p>
755      *
756      * @param field the field to get a rule for
757      * @param padding the padding required
758      * @return a new rule with the correct padding
759      */

760     protected NumberRule selectNumberRule(int field, int padding) {
761         switch (padding) {
762         case 1:
763             return new UnpaddedNumberField(field);
764         case 2:
765             return new TwoDigitNumberField(field);
766         default:
767             return new PaddedNumberField(field, padding);
768         }
769     }
770
771     // Format methods
772
//-----------------------------------------------------------------------
773
/**
774      * <p>Formats a <code>Date</code>, <code>Calendar</code> or
775      * <code>Long</code> (milliseconds) object.</p>
776      *
777      * @param obj the object to format
778      * @param toAppendTo the buffer to append to
779      * @param pos the position - ignored
780      * @return the buffer passed in
781      */

782     public StringBuffer JavaDoc format(Object JavaDoc obj, StringBuffer JavaDoc toAppendTo, FieldPosition JavaDoc pos) {
783         if (obj instanceof Date JavaDoc) {
784             return format((Date JavaDoc) obj, toAppendTo);
785         } else if (obj instanceof Calendar JavaDoc) {
786             return format((Calendar JavaDoc) obj, toAppendTo);
787         } else if (obj instanceof Long JavaDoc) {
788             return format(((Long JavaDoc) obj).longValue(), toAppendTo);
789         } else {
790             throw new IllegalArgumentException JavaDoc("Unknown class: " +
791                 (obj == null ? "<null>" : obj.getClass().getName()));
792         }
793     }
794
795     /**
796      * <p>Formats a millisecond <code>long</code> value.</p>
797      *
798      * @param millis the millisecond value to format
799      * @return the formatted string
800      * @since 2.1
801      */

802     public String JavaDoc format(long millis) {
803         return format(new Date JavaDoc(millis));
804     }
805
806     /**
807      * <p>Formats a <code>Date</code> object.</p>
808      *
809      * @param date the date to format
810      * @return the formatted string
811      */

812     public String JavaDoc format(Date JavaDoc date) {
813         Calendar JavaDoc c = new GregorianCalendar JavaDoc(mTimeZone);
814         c.setTime(date);
815         return applyRules(c, new StringBuffer JavaDoc(mMaxLengthEstimate)).toString();
816     }
817
818     /**
819      * <p>Formats a <code>Calendar</code> object.</p>
820      *
821      * @param calendar the calendar to format
822      * @return the formatted string
823      */

824     public String JavaDoc format(Calendar JavaDoc calendar) {
825         return format(calendar, new StringBuffer JavaDoc(mMaxLengthEstimate)).toString();
826     }
827
828     /**
829      * <p>Formats a milliseond <code>long</code> value into the
830      * supplied <code>StringBuffer</code>.</p>
831      *
832      * @param millis the millisecond value to format
833      * @param buf the buffer to format into
834      * @return the specified string buffer
835      * @since 2.1
836      */

837     public StringBuffer JavaDoc format(long millis, StringBuffer JavaDoc buf) {
838         return format(new Date JavaDoc(millis), buf);
839     }
840
841     /**
842      * <p>Formats a <code>Date</code> object into the
843      * supplied <code>StringBuffer</code>.</p>
844      *
845      * @param date the date to format
846      * @param buf the buffer to format into
847      * @return the specified string buffer
848      */

849     public StringBuffer JavaDoc format(Date JavaDoc date, StringBuffer JavaDoc buf) {
850         Calendar JavaDoc c = new GregorianCalendar JavaDoc(mTimeZone);
851         c.setTime(date);
852         return applyRules(c, buf);
853     }
854
855     /**
856      * <p>Formats a <code>Calendar</code> object into the
857      * supplied <code>StringBuffer</code>.</p>
858      *
859      * @param calendar the calendar to format
860      * @param buf the buffer to format into
861      * @return the specified string buffer
862      */

863     public StringBuffer JavaDoc format(Calendar JavaDoc calendar, StringBuffer JavaDoc buf) {
864         if (mTimeZoneForced) {
865             calendar = (Calendar JavaDoc) calendar.clone();
866             calendar.setTimeZone(mTimeZone);
867         }
868         return applyRules(calendar, buf);
869     }
870
871     /**
872      * <p>Performs the formatting by applying the rules to the
873      * specified calendar.</p>
874      *
875      * @param calendar the calendar to format
876      * @param buf the buffer to format into
877      * @return the specified string buffer
878      */

879     protected StringBuffer JavaDoc applyRules(Calendar JavaDoc calendar, StringBuffer JavaDoc buf) {
880         Rule[] rules = mRules;
881         int len = mRules.length;
882         for (int i = 0; i < len; i++) {
883             rules[i].appendTo(buf, calendar);
884         }
885         return buf;
886     }
887
888     // Parsing
889
//-----------------------------------------------------------------------
890
/**
891      * <p>Parsing not supported.</p>
892      *
893      * @param source the string to parse
894      * @param pos the parsing position
895      * @return <code>null</code> as not supported
896      */

897     public Object JavaDoc parseObject(String JavaDoc source, ParsePosition JavaDoc pos) {
898         pos.setIndex(0);
899         pos.setErrorIndex(0);
900         return null;
901     }
902     
903     // Accessors
904
//-----------------------------------------------------------------------
905
/**
906      * <p>Gets the pattern used by this formatter.</p>
907      *
908      * @return the pattern, {@link java.text.SimpleDateFormat} compatible
909      */

910     public String JavaDoc getPattern() {
911         return mPattern;
912     }
913
914     /**
915      * <p>Gets the time zone used by this formatter.</p>
916      *
917      * <p>This zone is always used for <code>Date</code> formatting.
918      * If a <code>Calendar</code> is passed in to be formatted, the
919      * time zone on that may be used depending on
920      * {@link #getTimeZoneOverridesCalendar()}.</p>
921      *
922      * @return the time zone
923      */

924     public TimeZone JavaDoc getTimeZone() {
925         return mTimeZone;
926     }
927
928     /**
929      * <p>Returns <code>true</code> if the time zone of the
930      * calendar overrides the time zone of the formatter.</p>
931      *
932      * @return <code>true</code> if time zone of formatter
933      * overridden for calendars
934      */

935     public boolean getTimeZoneOverridesCalendar() {
936         return mTimeZoneForced;
937     }
938
939     /**
940      * <p>Gets the locale used by this formatter.</p>
941      *
942      * @return the locale
943      */

944     public Locale JavaDoc getLocale() {
945         return mLocale;
946     }
947
948     /**
949      * <p>Gets an estimate for the maximum string length that the
950      * formatter will produce.</p>
951      *
952      * <p>The actual formatted length will almost always be less than or
953      * equal to this amount.</p>
954      *
955      * @return the maximum formatted length
956      */

957     public int getMaxLengthEstimate() {
958         return mMaxLengthEstimate;
959     }
960
961     // Basics
962
//-----------------------------------------------------------------------
963
/**
964      * <p>Compare two objects for equality.</p>
965      *
966      * @param obj the object to compare to
967      * @return <code>true</code> if equal
968      */

969     public boolean equals(Object JavaDoc obj) {
970         if (obj instanceof FastDateFormat == false) {
971             return false;
972         }
973         FastDateFormat other = (FastDateFormat) obj;
974         if (
975             (mPattern == other.mPattern || mPattern.equals(other.mPattern)) &&
976             (mTimeZone == other.mTimeZone || mTimeZone.equals(other.mTimeZone)) &&
977             (mLocale == other.mLocale || mLocale.equals(other.mLocale)) &&
978             (mTimeZoneForced == other.mTimeZoneForced) &&
979             (mLocaleForced == other.mLocaleForced)
980             ) {
981             return true;
982         }
983         return false;
984     }
985
986     /**
987      * <p>A suitable hashcode.</p>
988      *
989      * @return a hashcode compatible with equals
990      */

991     public int hashCode() {
992         int total = 0;
993         total += mPattern.hashCode();
994         total += mTimeZone.hashCode();
995         total += (mTimeZoneForced ? 1 : 0);
996         total += mLocale.hashCode();
997         total += (mLocaleForced ? 1 : 0);
998         return total;
999     }
1000
1001    /**
1002     * <p>Gets a debugging string version of this formatter.</p>
1003     *
1004     * @return a debugging string
1005     */

1006    public String JavaDoc toString() {
1007        return "FastDateFormat[" + mPattern + "]";
1008    }
1009    
1010    // Rules
1011
//-----------------------------------------------------------------------
1012
/**
1013     * <p>Inner class defining a rule.</p>
1014     */

1015    private interface Rule {
1016        int estimateLength();
1017        void appendTo(StringBuffer JavaDoc buffer, Calendar JavaDoc calendar);
1018    }
1019
1020    /**
1021     * <p>Inner class defining a numeric rule.</p>
1022     */

1023    private interface NumberRule extends Rule {
1024        void appendTo(StringBuffer JavaDoc buffer, int value);
1025    }
1026
1027    /**
1028     * <p>Inner class to output a constant single character.</p>
1029     */

1030    private static class CharacterLiteral implements Rule {
1031        private final char mValue;
1032
1033        CharacterLiteral(char value) {
1034            mValue = value;
1035        }
1036
1037        public int estimateLength() {
1038            return 1;
1039        }
1040
1041        public void appendTo(StringBuffer JavaDoc buffer, Calendar JavaDoc calendar) {
1042            buffer.append(mValue);
1043        }
1044    }
1045
1046    /**
1047     * <p>Inner class to output a constant string.</p>
1048     */

1049    private static class StringLiteral implements Rule {
1050        private final String JavaDoc mValue;
1051
1052        StringLiteral(String JavaDoc value) {
1053            mValue = value;
1054        }
1055
1056        public int estimateLength() {
1057            return mValue.length();
1058        }
1059
1060        public void appendTo(StringBuffer JavaDoc buffer, Calendar JavaDoc calendar) {
1061            buffer.append(mValue);
1062        }
1063    }
1064
1065    /**
1066     * <p>Inner class to output one of a set of values.</p>
1067     */

1068    private static class TextField implements Rule {
1069        private final int mField;
1070        private final String JavaDoc[] mValues;
1071
1072        TextField(int field, String JavaDoc[] values) {
1073            mField = field;
1074            mValues = values;
1075        }
1076
1077        public int estimateLength() {
1078            int max = 0;
1079            for (int i=mValues.length; --i >= 0; ) {
1080                int len = mValues[i].length();
1081                if (len > max) {
1082                    max = len;
1083                }
1084            }
1085            return max;
1086        }
1087
1088        public void appendTo(StringBuffer JavaDoc buffer, Calendar JavaDoc calendar) {
1089            buffer.append(mValues[calendar.get(mField)]);
1090        }
1091    }
1092
1093    /**
1094     * <p>Inner class to output an unpadded number.</p>
1095     */

1096    private static class UnpaddedNumberField implements NumberRule {
1097        static final UnpaddedNumberField INSTANCE_YEAR = new UnpaddedNumberField(Calendar.YEAR);
1098        
1099        private final int mField;
1100
1101        UnpaddedNumberField(int field) {
1102            mField = field;
1103        }
1104
1105        public int estimateLength() {
1106            return 4;
1107        }
1108
1109        public void appendTo(StringBuffer JavaDoc buffer, Calendar JavaDoc calendar) {
1110            appendTo(buffer, calendar.get(mField));
1111        }
1112
1113        public final void appendTo(StringBuffer JavaDoc buffer, int value) {
1114            if (value < 10) {
1115                buffer.append((char)(value + '0'));
1116            } else if (value < 100) {
1117                buffer.append((char)(value / 10 + '0'));
1118                buffer.append((char)(value % 10 + '0'));
1119            } else {
1120                buffer.append(Integer.toString(value));
1121            }
1122        }
1123    }
1124
1125    /**
1126     * <p>Inner class to output an unpadded month.</p>
1127     */

1128    private static class UnpaddedMonthField implements NumberRule {
1129        static final UnpaddedMonthField INSTANCE = new UnpaddedMonthField();
1130        
1131        UnpaddedMonthField() {
1132        }
1133
1134        public int estimateLength() {
1135            return 2;
1136        }
1137
1138        public void appendTo(StringBuffer JavaDoc buffer, Calendar JavaDoc calendar) {
1139            appendTo(buffer, calendar.get(Calendar.MONTH) + 1);
1140        }
1141
1142        public final void appendTo(StringBuffer JavaDoc buffer, int value) {
1143            if (value < 10) {
1144                buffer.append((char)(value + '0'));
1145            } else {
1146                buffer.append((char)(value / 10 + '0'));
1147                buffer.append((char)(value % 10 + '0'));
1148            }
1149        }
1150    }
1151
1152    /**
1153     * <p>Inner class to output a padded number.</p>
1154     */

1155    private static class PaddedNumberField implements NumberRule {
1156        private final int mField;
1157        private final int mSize;
1158
1159        PaddedNumberField(int field, int size) {
1160            if (size < 3) {
1161                // Should use UnpaddedNumberField or TwoDigitNumberField.
1162
throw new IllegalArgumentException JavaDoc();
1163            }
1164            mField = field;
1165            mSize = size;
1166        }
1167
1168        public int estimateLength() {
1169            return 4;
1170        }
1171
1172        public void appendTo(StringBuffer JavaDoc buffer, Calendar JavaDoc calendar) {
1173            appendTo(buffer, calendar.get(mField));
1174        }
1175
1176        public final void appendTo(StringBuffer JavaDoc buffer, int value) {
1177            if (value < 100) {
1178                for (int i = mSize; --i >= 2; ) {
1179                    buffer.append('0');
1180                }
1181                buffer.append((char)(value / 10 + '0'));
1182                buffer.append((char)(value % 10 + '0'));
1183            } else {
1184                int digits;
1185                if (value < 1000) {
1186                    digits = 3;
1187                } else {
1188                    digits = (int)(Math.log(value) / LOG_10) + 1;
1189                }
1190                for (int i = mSize; --i >= digits; ) {
1191                    buffer.append('0');
1192                }
1193                buffer.append(Integer.toString(value));
1194            }
1195        }
1196    }
1197
1198    /**
1199     * <p>Inner class to output a two digit number.</p>
1200     */

1201    private static class TwoDigitNumberField implements NumberRule {
1202        private final int mField;
1203
1204        TwoDigitNumberField(int field) {
1205            mField = field;
1206        }
1207
1208        public int estimateLength() {
1209            return 2;
1210        }
1211
1212        public void appendTo(StringBuffer JavaDoc buffer, Calendar JavaDoc calendar) {
1213            appendTo(buffer, calendar.get(mField));
1214        }
1215
1216        public final void appendTo(StringBuffer JavaDoc buffer, int value) {
1217            if (value < 100) {
1218                buffer.append((char)(value / 10 + '0'));
1219                buffer.append((char)(value % 10 + '0'));
1220            } else {
1221                buffer.append(Integer.toString(value));
1222            }
1223        }
1224    }
1225
1226    /**
1227     * <p>Inner class to output a two digit year.</p>
1228     */

1229    private static class TwoDigitYearField implements NumberRule {
1230        static final TwoDigitYearField INSTANCE = new TwoDigitYearField();
1231        
1232        TwoDigitYearField() {
1233        }
1234
1235        public int estimateLength() {
1236            return 2;
1237        }
1238
1239        public void appendTo(StringBuffer JavaDoc buffer, Calendar JavaDoc calendar) {
1240            appendTo(buffer, calendar.get(Calendar.YEAR) % 100);
1241        }
1242
1243        public final void appendTo(StringBuffer JavaDoc buffer, int value) {
1244            buffer.append((char)(value / 10 + '0'));
1245            buffer.append((char)(value % 10 + '0'));
1246        }
1247    }
1248
1249    /**
1250     * <p>Inner class to output a two digit month.</p>
1251     */

1252    private static class TwoDigitMonthField implements NumberRule {
1253        static final TwoDigitMonthField INSTANCE = new TwoDigitMonthField();
1254        
1255        TwoDigitMonthField() {
1256        }
1257
1258        public int estimateLength() {
1259            return 2;
1260        }
1261
1262        public void appendTo(StringBuffer JavaDoc buffer, Calendar JavaDoc calendar) {
1263            appendTo(buffer, calendar.get(Calendar.MONTH) + 1);
1264        }
1265
1266        public final void appendTo(StringBuffer JavaDoc buffer, int value) {
1267            buffer.append((char)(value / 10 + '0'));
1268            buffer.append((char)(value % 10 + '0'));
1269        }
1270    }
1271
1272    /**
1273     * <p>Inner class to output the twelve hour field.</p>
1274     */

1275    private static class TwelveHourField implements NumberRule {
1276        private final NumberRule mRule;
1277
1278        TwelveHourField(NumberRule rule) {
1279            mRule = rule;
1280        }
1281
1282        public int estimateLength() {
1283            return mRule.estimateLength();
1284        }
1285
1286        public void appendTo(StringBuffer JavaDoc buffer, Calendar JavaDoc calendar) {
1287            int value = calendar.get(Calendar.HOUR);
1288            if (value == 0) {
1289                value = calendar.getLeastMaximum(Calendar.HOUR) + 1;
1290            }
1291            mRule.appendTo(buffer, value);
1292        }
1293
1294        public void appendTo(StringBuffer JavaDoc buffer, int value) {
1295            mRule.appendTo(buffer, value);
1296        }
1297    }
1298
1299    /**
1300     * <p>Inner class to output the twenty four hour field.</p>
1301     */

1302    private static class TwentyFourHourField implements NumberRule {
1303        private final NumberRule mRule;
1304
1305        TwentyFourHourField(NumberRule rule) {
1306            mRule = rule;
1307        }
1308
1309        public int estimateLength() {
1310            return mRule.estimateLength();
1311        }
1312
1313        public void appendTo(StringBuffer JavaDoc buffer, Calendar JavaDoc calendar) {
1314            int value = calendar.get(Calendar.HOUR_OF_DAY);
1315            if (value == 0) {
1316                value = calendar.getMaximum(Calendar.HOUR_OF_DAY) + 1;
1317            }
1318            mRule.appendTo(buffer, value);
1319        }
1320
1321        public void appendTo(StringBuffer JavaDoc buffer, int value) {
1322            mRule.appendTo(buffer, value);
1323        }
1324    }
1325
1326    /**
1327     * <p>Inner class to output a time zone name.</p>
1328     */

1329    private static class TimeZoneNameRule implements Rule {
1330        private final TimeZone JavaDoc mTimeZone;
1331        private final boolean mTimeZoneForced;
1332        private final Locale JavaDoc mLocale;
1333        private final int mStyle;
1334        private final String JavaDoc mStandard;
1335        private final String JavaDoc mDaylight;
1336
1337        TimeZoneNameRule(TimeZone JavaDoc timeZone, boolean timeZoneForced, Locale JavaDoc locale, int style) {
1338            mTimeZone = timeZone;
1339            mTimeZoneForced = timeZoneForced;
1340            mLocale = locale;
1341            mStyle = style;
1342
1343            if (timeZoneForced) {
1344                mStandard = getTimeZoneDisplay(timeZone, false, style, locale);
1345                mDaylight = getTimeZoneDisplay(timeZone, true, style, locale);
1346            } else {
1347                mStandard = null;
1348                mDaylight = null;
1349            }
1350        }
1351
1352        public int estimateLength() {
1353            if (mTimeZoneForced) {
1354                return Math.max(mStandard.length(), mDaylight.length());
1355            } else if (mStyle == TimeZone.SHORT) {
1356                return 4;
1357            } else {
1358                return 40;
1359            }
1360        }
1361
1362        public void appendTo(StringBuffer JavaDoc buffer, Calendar JavaDoc calendar) {
1363            if (mTimeZoneForced) {
1364                if (mTimeZone.useDaylightTime() && calendar.get(Calendar.DST_OFFSET) != 0) {
1365                    buffer.append(mDaylight);
1366                } else {
1367                    buffer.append(mStandard);
1368                }
1369            } else {
1370                TimeZone JavaDoc timeZone = calendar.getTimeZone();
1371                if (timeZone.useDaylightTime() && calendar.get(Calendar.DST_OFFSET) != 0) {
1372                    buffer.append(getTimeZoneDisplay(timeZone, true, mStyle, mLocale));
1373                } else {
1374                    buffer.append(getTimeZoneDisplay(timeZone, false, mStyle, mLocale));
1375                }
1376            }
1377        }
1378    }
1379
1380    /**
1381     * <p>Inner class to output a time zone as a number <code>+/-HHMM</code>
1382     * or <code>+/-HH:MM</code>.</p>
1383     */

1384    private static class TimeZoneNumberRule implements Rule {
1385        static final TimeZoneNumberRule INSTANCE_COLON = new TimeZoneNumberRule(true);
1386        static final TimeZoneNumberRule INSTANCE_NO_COLON = new TimeZoneNumberRule(false);
1387        
1388        final boolean mColon;
1389        
1390        TimeZoneNumberRule(boolean colon) {
1391            mColon = colon;
1392        }
1393
1394        public int estimateLength() {
1395            return 5;
1396        }
1397
1398        public void appendTo(StringBuffer JavaDoc buffer, Calendar JavaDoc calendar) {
1399            int offset = calendar.get(Calendar.ZONE_OFFSET) + calendar.get(Calendar.DST_OFFSET);
1400            
1401            if (offset < 0) {
1402                buffer.append('-');
1403                offset = -offset;
1404            } else {
1405                buffer.append('+');
1406            }
1407            
1408            int hours = offset / (60 * 60 * 1000);
1409            buffer.append((char)(hours / 10 + '0'));
1410            buffer.append((char)(hours % 10 + '0'));
1411            
1412            if (mColon) {
1413                buffer.append(':');
1414            }
1415            
1416            int minutes = offset / (60 * 1000) - 60 * hours;
1417            buffer.append((char)(minutes / 10 + '0'));
1418            buffer.append((char)(minutes % 10 + '0'));
1419        }
1420    }
1421
1422    // ----------------------------------------------------------------------
1423
/**
1424     * <p>Inner class that acts as a compound key for time zone names.</p>
1425     */

1426    private static class TimeZoneDisplayKey {
1427        private final TimeZone JavaDoc mTimeZone;
1428        private final int mStyle;
1429        private final Locale JavaDoc mLocale;
1430
1431        TimeZoneDisplayKey(TimeZone JavaDoc timeZone,
1432                           boolean daylight, int style, Locale JavaDoc locale) {
1433            mTimeZone = timeZone;
1434            if (daylight) {
1435                style |= 0x80000000;
1436            }
1437            mStyle = style;
1438            mLocale = locale;
1439        }
1440
1441        public int hashCode() {
1442            return mStyle * 31 + mLocale.hashCode();
1443        }
1444
1445        public boolean equals(Object JavaDoc obj) {
1446            if (this == obj) {
1447                return true;
1448            }
1449            if (obj instanceof TimeZoneDisplayKey) {
1450                TimeZoneDisplayKey other = (TimeZoneDisplayKey)obj;
1451                return
1452                    mTimeZone.equals(other.mTimeZone) &&
1453                    mStyle == other.mStyle &&
1454                    mLocale.equals(other.mLocale);
1455            }
1456            return false;
1457        }
1458    }
1459
1460    // ----------------------------------------------------------------------
1461
/**
1462     * <p>Helper class for creating compound objects.</p>
1463     *
1464     * <p>One use for this class is to create a hashtable key
1465     * out of multiple objects.</p>
1466     */

1467    private static class Pair {
1468        private final Object JavaDoc mObj1;
1469        private final Object JavaDoc mObj2;
1470
1471        public Pair(Object JavaDoc obj1, Object JavaDoc obj2) {
1472            mObj1 = obj1;
1473            mObj2 = obj2;
1474        }
1475
1476        public boolean equals(Object JavaDoc obj) {
1477            if (this == obj) {
1478                return true;
1479            }
1480
1481            if (!(obj instanceof Pair)) {
1482                return false;
1483            }
1484
1485            Pair key = (Pair)obj;
1486
1487            return
1488                (mObj1 == null ?
1489                 key.mObj1 == null : mObj1.equals(key.mObj1)) &&
1490                (mObj2 == null ?
1491                 key.mObj2 == null : mObj2.equals(key.mObj2));
1492        }
1493
1494        public int hashCode() {
1495            return
1496                (mObj1 == null ? 0 : mObj1.hashCode()) +
1497                (mObj2 == null ? 0 : mObj2.hashCode());
1498        }
1499
1500        public String JavaDoc toString() {
1501            return "[" + mObj1 + ':' + mObj2 + ']';
1502        }
1503    }
1504
1505}
1506
Popular Tags