0001: /*
0002: * Licensed to the Apache Software Foundation (ASF) under one or more
0003: * contributor license agreements. See the NOTICE file distributed with
0004: * this work for additional information regarding copyright ownership.
0005: * The ASF licenses this file to You under the Apache License, Version 2.0
0006: * (the "License"); you may not use this file except in compliance with
0007: * the License. You may obtain a copy of the License at
0008: *
0009: * http://www.apache.org/licenses/LICENSE-2.0
0010: *
0011: * Unless required by applicable law or agreed to in writing, software
0012: * distributed under the License is distributed on an "AS IS" BASIS,
0013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
0014: * See the License for the specific language governing permissions and
0015: * limitations under the License.
0016: */
0017: package org.apache.commons.lang.time;
0018:
0019: import java.io.IOException;
0020: import java.io.ObjectInputStream;
0021:
0022: import java.text.DateFormat;
0023: import java.text.DateFormatSymbols;
0024: import java.text.FieldPosition;
0025: import java.text.Format;
0026: import java.text.ParsePosition;
0027: import java.text.SimpleDateFormat;
0028: import java.util.ArrayList;
0029: import java.util.Calendar;
0030: import java.util.Date;
0031: import java.util.GregorianCalendar;
0032: import java.util.HashMap;
0033: import java.util.List;
0034: import java.util.Locale;
0035: import java.util.Map;
0036: import java.util.TimeZone;
0037:
0038: import org.apache.commons.lang.Validate;
0039:
0040: /**
0041: * <p>FastDateFormat is a fast and thread-safe version of
0042: * {@link java.text.SimpleDateFormat}.</p>
0043: *
0044: * <p>This class can be used as a direct replacement to
0045: * <code>SimpleDateFormat</code> in most formatting situations.
0046: * This class is especially useful in multi-threaded server environments.
0047: * <code>SimpleDateFormat</code> is not thread-safe in any JDK version,
0048: * nor will it be as Sun have closed the bug/RFE.
0049: * </p>
0050: *
0051: * <p>Only formatting is supported, but all patterns are compatible with
0052: * SimpleDateFormat (except time zones - see below).</p>
0053: *
0054: * <p>Java 1.4 introduced a new pattern letter, <code>'Z'</code>, to represent
0055: * time zones in RFC822 format (eg. <code>+0800</code> or <code>-1100</code>).
0056: * This pattern letter can be used here (on all JDK versions).</p>
0057: *
0058: * <p>In addition, the pattern <code>'ZZ'</code> has been made to represent
0059: * ISO8601 full format time zones (eg. <code>+08:00</code> or <code>-11:00</code>).
0060: * This introduces a minor incompatibility with Java 1.4, but at a gain of
0061: * useful functionality.</p>
0062: *
0063: * @author TeaTrove project
0064: * @author Brian S O'Neill
0065: * @author Sean Schofield
0066: * @author Gary Gregory
0067: * @author Stephen Colebourne
0068: * @author Nikolay Metchev
0069: * @since 2.0
0070: * @version $Id: FastDateFormat.java 504349 2007-02-06 22:44:33Z bayard $
0071: */
0072: public class FastDateFormat extends Format {
0073: // A lot of the speed in this class comes from caching, but some comes
0074: // from the special int to StringBuffer conversion.
0075: //
0076: // The following produces a padded 2 digit number:
0077: // buffer.append((char)(value / 10 + '0'));
0078: // buffer.append((char)(value % 10 + '0'));
0079: //
0080: // Note that the fastest append to StringBuffer is a single char (used here).
0081: // Note that Integer.toString() is not called, the conversion is simply
0082: // taking the value and adding (mathematically) the ASCII value for '0'.
0083: // So, don't change this code! It works and is very fast.
0084:
0085: /**
0086: * Required for serialization support.
0087: *
0088: * @see java.io.Serializable
0089: */
0090: private static final long serialVersionUID = 1L;
0091:
0092: /**
0093: * FULL locale dependent date or time style.
0094: */
0095: public static final int FULL = DateFormat.FULL;
0096: /**
0097: * LONG locale dependent date or time style.
0098: */
0099: public static final int LONG = DateFormat.LONG;
0100: /**
0101: * MEDIUM locale dependent date or time style.
0102: */
0103: public static final int MEDIUM = DateFormat.MEDIUM;
0104: /**
0105: * SHORT locale dependent date or time style.
0106: */
0107: public static final int SHORT = DateFormat.SHORT;
0108:
0109: private static String cDefaultPattern;
0110:
0111: private static Map cInstanceCache = new HashMap(7);
0112: private static Map cDateInstanceCache = new HashMap(7);
0113: private static Map cTimeInstanceCache = new HashMap(7);
0114: private static Map cDateTimeInstanceCache = new HashMap(7);
0115: private static Map cTimeZoneDisplayCache = new HashMap(7);
0116:
0117: /**
0118: * The pattern.
0119: */
0120: private final String mPattern;
0121: /**
0122: * The time zone.
0123: */
0124: private final TimeZone mTimeZone;
0125: /**
0126: * Whether the time zone overrides any on Calendars.
0127: */
0128: private final boolean mTimeZoneForced;
0129: /**
0130: * The locale.
0131: */
0132: private final Locale mLocale;
0133: /**
0134: * Whether the locale overrides the default.
0135: */
0136: private final boolean mLocaleForced;
0137: /**
0138: * The parsed rules.
0139: */
0140: private transient Rule[] mRules;
0141: /**
0142: * The estimated maximum length.
0143: */
0144: private transient int mMaxLengthEstimate;
0145:
0146: //-----------------------------------------------------------------------
0147: /**
0148: * <p>Gets a formatter instance using the default pattern in the
0149: * default locale.</p>
0150: *
0151: * @return a date/time formatter
0152: */
0153: public static FastDateFormat getInstance() {
0154: return getInstance(getDefaultPattern(), null, null);
0155: }
0156:
0157: /**
0158: * <p>Gets a formatter instance using the specified pattern in the
0159: * default locale.</p>
0160: *
0161: * @param pattern {@link java.text.SimpleDateFormat} compatible
0162: * pattern
0163: * @return a pattern based date/time formatter
0164: * @throws IllegalArgumentException if pattern is invalid
0165: */
0166: public static FastDateFormat getInstance(String pattern) {
0167: return getInstance(pattern, null, null);
0168: }
0169:
0170: /**
0171: * <p>Gets a formatter instance using the specified pattern and
0172: * time zone.</p>
0173: *
0174: * @param pattern {@link java.text.SimpleDateFormat} compatible
0175: * pattern
0176: * @param timeZone optional time zone, overrides time zone of
0177: * formatted date
0178: * @return a pattern based date/time formatter
0179: * @throws IllegalArgumentException if pattern is invalid
0180: */
0181: public static FastDateFormat getInstance(String pattern,
0182: TimeZone timeZone) {
0183: return getInstance(pattern, timeZone, null);
0184: }
0185:
0186: /**
0187: * <p>Gets a formatter instance using the specified pattern and
0188: * locale.</p>
0189: *
0190: * @param pattern {@link java.text.SimpleDateFormat} compatible
0191: * pattern
0192: * @param locale optional locale, overrides system locale
0193: * @return a pattern based date/time formatter
0194: * @throws IllegalArgumentException if pattern is invalid
0195: */
0196: public static FastDateFormat getInstance(String pattern,
0197: Locale locale) {
0198: return getInstance(pattern, null, locale);
0199: }
0200:
0201: /**
0202: * <p>Gets a formatter instance using the specified pattern, time zone
0203: * and locale.</p>
0204: *
0205: * @param pattern {@link java.text.SimpleDateFormat} compatible
0206: * pattern
0207: * @param timeZone optional time zone, overrides time zone of
0208: * formatted date
0209: * @param locale optional locale, overrides system locale
0210: * @return a pattern based date/time formatter
0211: * @throws IllegalArgumentException if pattern is invalid
0212: * or <code>null</code>
0213: */
0214: public static synchronized FastDateFormat getInstance(
0215: String pattern, TimeZone timeZone, Locale locale) {
0216: FastDateFormat emptyFormat = new FastDateFormat(pattern,
0217: timeZone, locale);
0218: FastDateFormat format = (FastDateFormat) cInstanceCache
0219: .get(emptyFormat);
0220: if (format == null) {
0221: format = emptyFormat;
0222: format.init(); // convert shell format into usable one
0223: cInstanceCache.put(format, format); // this is OK!
0224: }
0225: return format;
0226: }
0227:
0228: //-----------------------------------------------------------------------
0229: /**
0230: * <p>Gets a date formatter instance using the specified style in the
0231: * default time zone and locale.</p>
0232: *
0233: * @param style date style: FULL, LONG, MEDIUM, or SHORT
0234: * @return a localized standard date formatter
0235: * @throws IllegalArgumentException if the Locale has no date
0236: * pattern defined
0237: * @since 2.1
0238: */
0239: public static FastDateFormat getDateInstance(int style) {
0240: return getDateInstance(style, null, null);
0241: }
0242:
0243: /**
0244: * <p>Gets a date formatter instance using the specified style and
0245: * locale in the default time zone.</p>
0246: *
0247: * @param style date style: FULL, LONG, MEDIUM, or SHORT
0248: * @param locale optional locale, overrides system locale
0249: * @return a localized standard date formatter
0250: * @throws IllegalArgumentException if the Locale has no date
0251: * pattern defined
0252: * @since 2.1
0253: */
0254: public static FastDateFormat getDateInstance(int style,
0255: Locale locale) {
0256: return getDateInstance(style, null, locale);
0257: }
0258:
0259: /**
0260: * <p>Gets a date formatter instance using the specified style and
0261: * time zone in the default locale.</p>
0262: *
0263: * @param style date style: FULL, LONG, MEDIUM, or SHORT
0264: * @param timeZone optional time zone, overrides time zone of
0265: * formatted date
0266: * @return a localized standard date formatter
0267: * @throws IllegalArgumentException if the Locale has no date
0268: * pattern defined
0269: * @since 2.1
0270: */
0271: public static FastDateFormat getDateInstance(int style,
0272: TimeZone timeZone) {
0273: return getDateInstance(style, timeZone, null);
0274: }
0275:
0276: /**
0277: * <p>Gets a date formatter instance using the specified style, time
0278: * zone and locale.</p>
0279: *
0280: * @param style date style: FULL, LONG, MEDIUM, or SHORT
0281: * @param timeZone optional time zone, overrides time zone of
0282: * formatted date
0283: * @param locale optional locale, overrides system locale
0284: * @return a localized standard date formatter
0285: * @throws IllegalArgumentException if the Locale has no date
0286: * pattern defined
0287: */
0288: public static synchronized FastDateFormat getDateInstance(
0289: int style, TimeZone timeZone, Locale locale) {
0290: Object key = new Integer(style);
0291: if (timeZone != null) {
0292: key = new Pair(key, timeZone);
0293: }
0294: if (locale != null) {
0295: key = new Pair(key, locale);
0296: }
0297:
0298: FastDateFormat format = (FastDateFormat) cDateInstanceCache
0299: .get(key);
0300: if (format == null) {
0301: if (locale == null) {
0302: locale = Locale.getDefault();
0303: }
0304:
0305: try {
0306: SimpleDateFormat formatter = (SimpleDateFormat) DateFormat
0307: .getDateInstance(style, locale);
0308: String pattern = formatter.toPattern();
0309: format = getInstance(pattern, timeZone, locale);
0310: cDateInstanceCache.put(key, format);
0311:
0312: } catch (ClassCastException ex) {
0313: throw new IllegalArgumentException(
0314: "No date pattern for locale: " + locale);
0315: }
0316: }
0317: return format;
0318: }
0319:
0320: //-----------------------------------------------------------------------
0321: /**
0322: * <p>Gets a time formatter instance using the specified style in the
0323: * default time zone and locale.</p>
0324: *
0325: * @param style time style: FULL, LONG, MEDIUM, or SHORT
0326: * @return a localized standard time formatter
0327: * @throws IllegalArgumentException if the Locale has no time
0328: * pattern defined
0329: * @since 2.1
0330: */
0331: public static FastDateFormat getTimeInstance(int style) {
0332: return getTimeInstance(style, null, null);
0333: }
0334:
0335: /**
0336: * <p>Gets a time formatter instance using the specified style and
0337: * locale in the default time zone.</p>
0338: *
0339: * @param style time style: FULL, LONG, MEDIUM, or SHORT
0340: * @param locale optional locale, overrides system locale
0341: * @return a localized standard time formatter
0342: * @throws IllegalArgumentException if the Locale has no time
0343: * pattern defined
0344: * @since 2.1
0345: */
0346: public static FastDateFormat getTimeInstance(int style,
0347: Locale locale) {
0348: return getTimeInstance(style, null, locale);
0349: }
0350:
0351: /**
0352: * <p>Gets a time formatter instance using the specified style and
0353: * time zone in the default locale.</p>
0354: *
0355: * @param style time style: FULL, LONG, MEDIUM, or SHORT
0356: * @param timeZone optional time zone, overrides time zone of
0357: * formatted time
0358: * @return a localized standard time formatter
0359: * @throws IllegalArgumentException if the Locale has no time
0360: * pattern defined
0361: * @since 2.1
0362: */
0363: public static FastDateFormat getTimeInstance(int style,
0364: TimeZone timeZone) {
0365: return getTimeInstance(style, timeZone, null);
0366: }
0367:
0368: /**
0369: * <p>Gets a time formatter instance using the specified style, time
0370: * zone and locale.</p>
0371: *
0372: * @param style time style: FULL, LONG, MEDIUM, or SHORT
0373: * @param timeZone optional time zone, overrides time zone of
0374: * formatted time
0375: * @param locale optional locale, overrides system locale
0376: * @return a localized standard time formatter
0377: * @throws IllegalArgumentException if the Locale has no time
0378: * pattern defined
0379: */
0380: public static synchronized FastDateFormat getTimeInstance(
0381: int style, TimeZone timeZone, Locale locale) {
0382: Object key = new Integer(style);
0383: if (timeZone != null) {
0384: key = new Pair(key, timeZone);
0385: }
0386: if (locale != null) {
0387: key = new Pair(key, locale);
0388: }
0389:
0390: FastDateFormat format = (FastDateFormat) cTimeInstanceCache
0391: .get(key);
0392: if (format == null) {
0393: if (locale == null) {
0394: locale = Locale.getDefault();
0395: }
0396:
0397: try {
0398: SimpleDateFormat formatter = (SimpleDateFormat) DateFormat
0399: .getTimeInstance(style, locale);
0400: String pattern = formatter.toPattern();
0401: format = getInstance(pattern, timeZone, locale);
0402: cTimeInstanceCache.put(key, format);
0403:
0404: } catch (ClassCastException ex) {
0405: throw new IllegalArgumentException(
0406: "No date pattern for locale: " + locale);
0407: }
0408: }
0409: return format;
0410: }
0411:
0412: //-----------------------------------------------------------------------
0413: /**
0414: * <p>Gets a date/time formatter instance using the specified style
0415: * in the default time zone and locale.</p>
0416: *
0417: * @param dateStyle date style: FULL, LONG, MEDIUM, or SHORT
0418: * @param timeStyle time style: FULL, LONG, MEDIUM, or SHORT
0419: * @return a localized standard date/time formatter
0420: * @throws IllegalArgumentException if the Locale has no date/time
0421: * pattern defined
0422: * @since 2.1
0423: */
0424: public static FastDateFormat getDateTimeInstance(int dateStyle,
0425: int timeStyle) {
0426: return getDateTimeInstance(dateStyle, timeStyle, null, null);
0427: }
0428:
0429: /**
0430: * <p>Gets a date/time formatter instance using the specified style and
0431: * locale in the default time zone.</p>
0432: *
0433: * @param dateStyle date style: FULL, LONG, MEDIUM, or SHORT
0434: * @param timeStyle time style: FULL, LONG, MEDIUM, or SHORT
0435: * @param locale optional locale, overrides system locale
0436: * @return a localized standard date/time formatter
0437: * @throws IllegalArgumentException if the Locale has no date/time
0438: * pattern defined
0439: * @since 2.1
0440: */
0441: public static FastDateFormat getDateTimeInstance(int dateStyle,
0442: int timeStyle, Locale locale) {
0443: return getDateTimeInstance(dateStyle, timeStyle, null, locale);
0444: }
0445:
0446: /**
0447: * <p>Gets a date/time formatter instance using the specified style and
0448: * time zone in the default locale.</p>
0449: *
0450: * @param dateStyle date style: FULL, LONG, MEDIUM, or SHORT
0451: * @param timeStyle time style: FULL, LONG, MEDIUM, or SHORT
0452: * @param timeZone optional time zone, overrides time zone of
0453: * formatted date
0454: * @return a localized standard date/time formatter
0455: * @throws IllegalArgumentException if the Locale has no date/time
0456: * pattern defined
0457: * @since 2.1
0458: */
0459: public static FastDateFormat getDateTimeInstance(int dateStyle,
0460: int timeStyle, TimeZone timeZone) {
0461: return getDateTimeInstance(dateStyle, timeStyle, timeZone, null);
0462: }
0463:
0464: /**
0465: * <p>Gets a date/time formatter instance using the specified style,
0466: * time zone and locale.</p>
0467: *
0468: * @param dateStyle date style: FULL, LONG, MEDIUM, or SHORT
0469: * @param timeStyle time style: FULL, LONG, MEDIUM, or SHORT
0470: * @param timeZone optional time zone, overrides time zone of
0471: * formatted date
0472: * @param locale optional locale, overrides system locale
0473: * @return a localized standard date/time formatter
0474: * @throws IllegalArgumentException if the Locale has no date/time
0475: * pattern defined
0476: */
0477: public static synchronized FastDateFormat getDateTimeInstance(
0478: int dateStyle, int timeStyle, TimeZone timeZone,
0479: Locale locale) {
0480:
0481: Object key = new Pair(new Integer(dateStyle), new Integer(
0482: timeStyle));
0483: if (timeZone != null) {
0484: key = new Pair(key, timeZone);
0485: }
0486: if (locale != null) {
0487: key = new Pair(key, locale);
0488: }
0489:
0490: FastDateFormat format = (FastDateFormat) cDateTimeInstanceCache
0491: .get(key);
0492: if (format == null) {
0493: if (locale == null) {
0494: locale = Locale.getDefault();
0495: }
0496:
0497: try {
0498: SimpleDateFormat formatter = (SimpleDateFormat) DateFormat
0499: .getDateTimeInstance(dateStyle, timeStyle,
0500: locale);
0501: String pattern = formatter.toPattern();
0502: format = getInstance(pattern, timeZone, locale);
0503: cDateTimeInstanceCache.put(key, format);
0504:
0505: } catch (ClassCastException ex) {
0506: throw new IllegalArgumentException(
0507: "No date time pattern for locale: " + locale);
0508: }
0509: }
0510: return format;
0511: }
0512:
0513: //-----------------------------------------------------------------------
0514: /**
0515: * <p>Gets the time zone display name, using a cache for performance.</p>
0516: *
0517: * @param tz the zone to query
0518: * @param daylight true if daylight savings
0519: * @param style the style to use <code>TimeZone.LONG</code>
0520: * or <code>TimeZone.SHORT</code>
0521: * @param locale the locale to use
0522: * @return the textual name of the time zone
0523: */
0524: static synchronized String getTimeZoneDisplay(TimeZone tz,
0525: boolean daylight, int style, Locale locale) {
0526: Object key = new TimeZoneDisplayKey(tz, daylight, style, locale);
0527: String value = (String) cTimeZoneDisplayCache.get(key);
0528: if (value == null) {
0529: // This is a very slow call, so cache the results.
0530: value = tz.getDisplayName(daylight, style, locale);
0531: cTimeZoneDisplayCache.put(key, value);
0532: }
0533: return value;
0534: }
0535:
0536: /**
0537: * <p>Gets the default pattern.</p>
0538: *
0539: * @return the default pattern
0540: */
0541: private static synchronized String getDefaultPattern() {
0542: if (cDefaultPattern == null) {
0543: cDefaultPattern = new SimpleDateFormat().toPattern();
0544: }
0545: return cDefaultPattern;
0546: }
0547:
0548: // Constructor
0549: //-----------------------------------------------------------------------
0550: /**
0551: * <p>Constructs a new FastDateFormat.</p>
0552: *
0553: * @param pattern {@link java.text.SimpleDateFormat} compatible
0554: * pattern
0555: * @param timeZone time zone to use, <code>null</code> means use
0556: * default for <code>Date</code> and value within for
0557: * <code>Calendar</code>
0558: * @param locale locale, <code>null</code> means use system
0559: * default
0560: * @throws IllegalArgumentException if pattern is invalid or
0561: * <code>null</code>
0562: */
0563: protected FastDateFormat(String pattern, TimeZone timeZone,
0564: Locale locale) {
0565: super ();
0566: if (pattern == null) {
0567: throw new IllegalArgumentException(
0568: "The pattern must not be null");
0569: }
0570: mPattern = pattern;
0571:
0572: mTimeZoneForced = (timeZone != null);
0573: if (timeZone == null) {
0574: timeZone = TimeZone.getDefault();
0575: }
0576: mTimeZone = timeZone;
0577:
0578: mLocaleForced = (locale != null);
0579: if (locale == null) {
0580: locale = Locale.getDefault();
0581: }
0582: mLocale = locale;
0583: }
0584:
0585: /**
0586: * <p>Initializes the instance for first use.</p>
0587: */
0588: protected void init() {
0589: List rulesList = parsePattern();
0590: mRules = (Rule[]) rulesList.toArray(new Rule[rulesList.size()]);
0591:
0592: int len = 0;
0593: for (int i = mRules.length; --i >= 0;) {
0594: len += mRules[i].estimateLength();
0595: }
0596:
0597: mMaxLengthEstimate = len;
0598: }
0599:
0600: // Parse the pattern
0601: //-----------------------------------------------------------------------
0602: /**
0603: * <p>Returns a list of Rules given a pattern.</p>
0604: *
0605: * @return a <code>List</code> of Rule objects
0606: * @throws IllegalArgumentException if pattern is invalid
0607: */
0608: protected List parsePattern() {
0609: DateFormatSymbols symbols = new DateFormatSymbols(mLocale);
0610: List rules = new ArrayList();
0611:
0612: String[] ERAs = symbols.getEras();
0613: String[] months = symbols.getMonths();
0614: String[] shortMonths = symbols.getShortMonths();
0615: String[] weekdays = symbols.getWeekdays();
0616: String[] shortWeekdays = symbols.getShortWeekdays();
0617: String[] AmPmStrings = symbols.getAmPmStrings();
0618:
0619: int length = mPattern.length();
0620: int[] indexRef = new int[1];
0621:
0622: for (int i = 0; i < length; i++) {
0623: indexRef[0] = i;
0624: String token = parseToken(mPattern, indexRef);
0625: i = indexRef[0];
0626:
0627: int tokenLen = token.length();
0628: if (tokenLen == 0) {
0629: break;
0630: }
0631:
0632: Rule rule;
0633: char c = token.charAt(0);
0634:
0635: switch (c) {
0636: case 'G': // era designator (text)
0637: rule = new TextField(Calendar.ERA, ERAs);
0638: break;
0639: case 'y': // year (number)
0640: if (tokenLen >= 4) {
0641: rule = selectNumberRule(Calendar.YEAR, tokenLen);
0642: } else {
0643: rule = TwoDigitYearField.INSTANCE;
0644: }
0645: break;
0646: case 'M': // month in year (text and number)
0647: if (tokenLen >= 4) {
0648: rule = new TextField(Calendar.MONTH, months);
0649: } else if (tokenLen == 3) {
0650: rule = new TextField(Calendar.MONTH, shortMonths);
0651: } else if (tokenLen == 2) {
0652: rule = TwoDigitMonthField.INSTANCE;
0653: } else {
0654: rule = UnpaddedMonthField.INSTANCE;
0655: }
0656: break;
0657: case 'd': // day in month (number)
0658: rule = selectNumberRule(Calendar.DAY_OF_MONTH, tokenLen);
0659: break;
0660: case 'h': // hour in am/pm (number, 1..12)
0661: rule = new TwelveHourField(selectNumberRule(
0662: Calendar.HOUR, tokenLen));
0663: break;
0664: case 'H': // hour in day (number, 0..23)
0665: rule = selectNumberRule(Calendar.HOUR_OF_DAY, tokenLen);
0666: break;
0667: case 'm': // minute in hour (number)
0668: rule = selectNumberRule(Calendar.MINUTE, tokenLen);
0669: break;
0670: case 's': // second in minute (number)
0671: rule = selectNumberRule(Calendar.SECOND, tokenLen);
0672: break;
0673: case 'S': // millisecond (number)
0674: rule = selectNumberRule(Calendar.MILLISECOND, tokenLen);
0675: break;
0676: case 'E': // day in week (text)
0677: rule = new TextField(Calendar.DAY_OF_WEEK,
0678: tokenLen < 4 ? shortWeekdays : weekdays);
0679: break;
0680: case 'D': // day in year (number)
0681: rule = selectNumberRule(Calendar.DAY_OF_YEAR, tokenLen);
0682: break;
0683: case 'F': // day of week in month (number)
0684: rule = selectNumberRule(Calendar.DAY_OF_WEEK_IN_MONTH,
0685: tokenLen);
0686: break;
0687: case 'w': // week in year (number)
0688: rule = selectNumberRule(Calendar.WEEK_OF_YEAR, tokenLen);
0689: break;
0690: case 'W': // week in month (number)
0691: rule = selectNumberRule(Calendar.WEEK_OF_MONTH,
0692: tokenLen);
0693: break;
0694: case 'a': // am/pm marker (text)
0695: rule = new TextField(Calendar.AM_PM, AmPmStrings);
0696: break;
0697: case 'k': // hour in day (1..24)
0698: rule = new TwentyFourHourField(selectNumberRule(
0699: Calendar.HOUR_OF_DAY, tokenLen));
0700: break;
0701: case 'K': // hour in am/pm (0..11)
0702: rule = selectNumberRule(Calendar.HOUR, tokenLen);
0703: break;
0704: case 'z': // time zone (text)
0705: if (tokenLen >= 4) {
0706: rule = new TimeZoneNameRule(mTimeZone,
0707: mTimeZoneForced, mLocale, TimeZone.LONG);
0708: } else {
0709: rule = new TimeZoneNameRule(mTimeZone,
0710: mTimeZoneForced, mLocale, TimeZone.SHORT);
0711: }
0712: break;
0713: case 'Z': // time zone (value)
0714: if (tokenLen == 1) {
0715: rule = TimeZoneNumberRule.INSTANCE_NO_COLON;
0716: } else {
0717: rule = TimeZoneNumberRule.INSTANCE_COLON;
0718: }
0719: break;
0720: case '\'': // literal text
0721: String sub = token.substring(1);
0722: if (sub.length() == 1) {
0723: rule = new CharacterLiteral(sub.charAt(0));
0724: } else {
0725: rule = new StringLiteral(sub);
0726: }
0727: break;
0728: default:
0729: throw new IllegalArgumentException(
0730: "Illegal pattern component: " + token);
0731: }
0732:
0733: rules.add(rule);
0734: }
0735:
0736: return rules;
0737: }
0738:
0739: /**
0740: * <p>Performs the parsing of tokens.</p>
0741: *
0742: * @param pattern the pattern
0743: * @param indexRef index references
0744: * @return parsed token
0745: */
0746: protected String parseToken(String pattern, int[] indexRef) {
0747: StringBuffer buf = new StringBuffer();
0748:
0749: int i = indexRef[0];
0750: int length = pattern.length();
0751:
0752: char c = pattern.charAt(i);
0753: if (c >= 'A' && c <= 'Z' || c >= 'a' && c <= 'z') {
0754: // Scan a run of the same character, which indicates a time
0755: // pattern.
0756: buf.append(c);
0757:
0758: while (i + 1 < length) {
0759: char peek = pattern.charAt(i + 1);
0760: if (peek == c) {
0761: buf.append(c);
0762: i++;
0763: } else {
0764: break;
0765: }
0766: }
0767: } else {
0768: // This will identify token as text.
0769: buf.append('\'');
0770:
0771: boolean inLiteral = false;
0772:
0773: for (; i < length; i++) {
0774: c = pattern.charAt(i);
0775:
0776: if (c == '\'') {
0777: if (i + 1 < length && pattern.charAt(i + 1) == '\'') {
0778: // '' is treated as escaped '
0779: i++;
0780: buf.append(c);
0781: } else {
0782: inLiteral = !inLiteral;
0783: }
0784: } else if (!inLiteral
0785: && (c >= 'A' && c <= 'Z' || c >= 'a'
0786: && c <= 'z')) {
0787: i--;
0788: break;
0789: } else {
0790: buf.append(c);
0791: }
0792: }
0793: }
0794:
0795: indexRef[0] = i;
0796: return buf.toString();
0797: }
0798:
0799: /**
0800: * <p>Gets an appropriate rule for the padding required.</p>
0801: *
0802: * @param field the field to get a rule for
0803: * @param padding the padding required
0804: * @return a new rule with the correct padding
0805: */
0806: protected NumberRule selectNumberRule(int field, int padding) {
0807: switch (padding) {
0808: case 1:
0809: return new UnpaddedNumberField(field);
0810: case 2:
0811: return new TwoDigitNumberField(field);
0812: default:
0813: return new PaddedNumberField(field, padding);
0814: }
0815: }
0816:
0817: // Format methods
0818: //-----------------------------------------------------------------------
0819: /**
0820: * <p>Formats a <code>Date</code>, <code>Calendar</code> or
0821: * <code>Long</code> (milliseconds) object.</p>
0822: *
0823: * @param obj the object to format
0824: * @param toAppendTo the buffer to append to
0825: * @param pos the position - ignored
0826: * @return the buffer passed in
0827: */
0828: public StringBuffer format(Object obj, StringBuffer toAppendTo,
0829: FieldPosition pos) {
0830: if (obj instanceof Date) {
0831: return format((Date) obj, toAppendTo);
0832: } else if (obj instanceof Calendar) {
0833: return format((Calendar) obj, toAppendTo);
0834: } else if (obj instanceof Long) {
0835: return format(((Long) obj).longValue(), toAppendTo);
0836: } else {
0837: throw new IllegalArgumentException("Unknown class: "
0838: + (obj == null ? "<null>" : obj.getClass()
0839: .getName()));
0840: }
0841: }
0842:
0843: /**
0844: * <p>Formats a millisecond <code>long</code> value.</p>
0845: *
0846: * @param millis the millisecond value to format
0847: * @return the formatted string
0848: * @since 2.1
0849: */
0850: public String format(long millis) {
0851: return format(new Date(millis));
0852: }
0853:
0854: /**
0855: * <p>Formats a <code>Date</code> object.</p>
0856: *
0857: * @param date the date to format
0858: * @return the formatted string
0859: */
0860: public String format(Date date) {
0861: Calendar c = new GregorianCalendar(mTimeZone);
0862: c.setTime(date);
0863: return applyRules(c, new StringBuffer(mMaxLengthEstimate))
0864: .toString();
0865: }
0866:
0867: /**
0868: * <p>Formats a <code>Calendar</code> object.</p>
0869: *
0870: * @param calendar the calendar to format
0871: * @return the formatted string
0872: */
0873: public String format(Calendar calendar) {
0874: return format(calendar, new StringBuffer(mMaxLengthEstimate))
0875: .toString();
0876: }
0877:
0878: /**
0879: * <p>Formats a milliseond <code>long</code> value into the
0880: * supplied <code>StringBuffer</code>.</p>
0881: *
0882: * @param millis the millisecond value to format
0883: * @param buf the buffer to format into
0884: * @return the specified string buffer
0885: * @since 2.1
0886: */
0887: public StringBuffer format(long millis, StringBuffer buf) {
0888: return format(new Date(millis), buf);
0889: }
0890:
0891: /**
0892: * <p>Formats a <code>Date</code> object into the
0893: * supplied <code>StringBuffer</code>.</p>
0894: *
0895: * @param date the date to format
0896: * @param buf the buffer to format into
0897: * @return the specified string buffer
0898: */
0899: public StringBuffer format(Date date, StringBuffer buf) {
0900: Calendar c = new GregorianCalendar(mTimeZone);
0901: c.setTime(date);
0902: return applyRules(c, buf);
0903: }
0904:
0905: /**
0906: * <p>Formats a <code>Calendar</code> object into the
0907: * supplied <code>StringBuffer</code>.</p>
0908: *
0909: * @param calendar the calendar to format
0910: * @param buf the buffer to format into
0911: * @return the specified string buffer
0912: */
0913: public StringBuffer format(Calendar calendar, StringBuffer buf) {
0914: if (mTimeZoneForced) {
0915: calendar = (Calendar) calendar.clone();
0916: calendar.setTimeZone(mTimeZone);
0917: }
0918: return applyRules(calendar, buf);
0919: }
0920:
0921: /**
0922: * <p>Performs the formatting by applying the rules to the
0923: * specified calendar.</p>
0924: *
0925: * @param calendar the calendar to format
0926: * @param buf the buffer to format into
0927: * @return the specified string buffer
0928: */
0929: protected StringBuffer applyRules(Calendar calendar,
0930: StringBuffer buf) {
0931: Rule[] rules = mRules;
0932: int len = mRules.length;
0933: for (int i = 0; i < len; i++) {
0934: rules[i].appendTo(buf, calendar);
0935: }
0936: return buf;
0937: }
0938:
0939: // Parsing
0940: //-----------------------------------------------------------------------
0941: /**
0942: * <p>Parsing is not supported.</p>
0943: *
0944: * @param source the string to parse
0945: * @param pos the parsing position
0946: * @return <code>null</code> as not supported
0947: */
0948: public Object parseObject(String source, ParsePosition pos) {
0949: pos.setIndex(0);
0950: pos.setErrorIndex(0);
0951: return null;
0952: }
0953:
0954: // Accessors
0955: //-----------------------------------------------------------------------
0956: /**
0957: * <p>Gets the pattern used by this formatter.</p>
0958: *
0959: * @return the pattern, {@link java.text.SimpleDateFormat} compatible
0960: */
0961: public String getPattern() {
0962: return mPattern;
0963: }
0964:
0965: /**
0966: * <p>Gets the time zone used by this formatter.</p>
0967: *
0968: * <p>This zone is always used for <code>Date</code> formatting.
0969: * If a <code>Calendar</code> is passed in to be formatted, the
0970: * time zone on that may be used depending on
0971: * {@link #getTimeZoneOverridesCalendar()}.</p>
0972: *
0973: * @return the time zone
0974: */
0975: public TimeZone getTimeZone() {
0976: return mTimeZone;
0977: }
0978:
0979: /**
0980: * <p>Returns <code>true</code> if the time zone of the
0981: * calendar overrides the time zone of the formatter.</p>
0982: *
0983: * @return <code>true</code> if time zone of formatter
0984: * overridden for calendars
0985: */
0986: public boolean getTimeZoneOverridesCalendar() {
0987: return mTimeZoneForced;
0988: }
0989:
0990: /**
0991: * <p>Gets the locale used by this formatter.</p>
0992: *
0993: * @return the locale
0994: */
0995: public Locale getLocale() {
0996: return mLocale;
0997: }
0998:
0999: /**
1000: * <p>Gets an estimate for the maximum string length that the
1001: * formatter will produce.</p>
1002: *
1003: * <p>The actual formatted length will almost always be less than or
1004: * equal to this amount.</p>
1005: *
1006: * @return the maximum formatted length
1007: */
1008: public int getMaxLengthEstimate() {
1009: return mMaxLengthEstimate;
1010: }
1011:
1012: // Basics
1013: //-----------------------------------------------------------------------
1014: /**
1015: * <p>Compares two objects for equality.</p>
1016: *
1017: * @param obj the object to compare to
1018: * @return <code>true</code> if equal
1019: */
1020: public boolean equals(Object obj) {
1021: if (obj instanceof FastDateFormat == false) {
1022: return false;
1023: }
1024: FastDateFormat other = (FastDateFormat) obj;
1025: if ((mPattern == other.mPattern || mPattern
1026: .equals(other.mPattern))
1027: && (mTimeZone == other.mTimeZone || mTimeZone
1028: .equals(other.mTimeZone))
1029: && (mLocale == other.mLocale || mLocale
1030: .equals(other.mLocale))
1031: && (mTimeZoneForced == other.mTimeZoneForced)
1032: && (mLocaleForced == other.mLocaleForced)) {
1033: return true;
1034: }
1035: return false;
1036: }
1037:
1038: /**
1039: * <p>Returns a hashcode compatible with equals.</p>
1040: *
1041: * @return a hashcode compatible with equals
1042: */
1043: public int hashCode() {
1044: int total = 0;
1045: total += mPattern.hashCode();
1046: total += mTimeZone.hashCode();
1047: total += (mTimeZoneForced ? 1 : 0);
1048: total += mLocale.hashCode();
1049: total += (mLocaleForced ? 1 : 0);
1050: return total;
1051: }
1052:
1053: /**
1054: * <p>Gets a debugging string version of this formatter.</p>
1055: *
1056: * @return a debugging string
1057: */
1058: public String toString() {
1059: return "FastDateFormat[" + mPattern + "]";
1060: }
1061:
1062: // Serializing
1063: //-----------------------------------------------------------------------
1064: /**
1065: * Create the object after serialization. This implementation reinitializes the
1066: * transient properties.
1067: *
1068: * @param in ObjectInputStream from which the object is being deserialized.
1069: * @throws IOException if there is an IO issue.
1070: * @throws ClassNotFoundException if a class cannot be found.
1071: */
1072: private void readObject(ObjectInputStream in) throws IOException,
1073: ClassNotFoundException {
1074: in.defaultReadObject();
1075: init();
1076: }
1077:
1078: // Rules
1079: //-----------------------------------------------------------------------
1080: /**
1081: * <p>Inner class defining a rule.</p>
1082: */
1083: private interface Rule {
1084: /**
1085: * Returns the estimated lentgh of the result.
1086: *
1087: * @return the estimated length
1088: */
1089: int estimateLength();
1090:
1091: /**
1092: * Appends the value of the specified calendar to the output buffer based on the rule implementation.
1093: *
1094: * @param buffer the output buffer
1095: * @param calendar calendar to be appended
1096: */
1097: void appendTo(StringBuffer buffer, Calendar calendar);
1098: }
1099:
1100: /**
1101: * <p>Inner class defining a numeric rule.</p>
1102: */
1103: private interface NumberRule extends Rule {
1104: /**
1105: * Appends the specified value to the output buffer based on the rule implementation.
1106: *
1107: * @param buffer the output buffer
1108: * @param value the value to be appended
1109: */
1110: void appendTo(StringBuffer buffer, int value);
1111: }
1112:
1113: /**
1114: * <p>Inner class to output a constant single character.</p>
1115: */
1116: private static class CharacterLiteral implements Rule {
1117: private final char mValue;
1118:
1119: /**
1120: * Constructs a new instance of <code>CharacterLiteral</code>
1121: * to hold the specified value.
1122: *
1123: * @param value the character literal
1124: */
1125: CharacterLiteral(char value) {
1126: mValue = value;
1127: }
1128:
1129: /**
1130: * {@inheritDoc}
1131: */
1132: public int estimateLength() {
1133: return 1;
1134: }
1135:
1136: /**
1137: * {@inheritDoc}
1138: */
1139: public void appendTo(StringBuffer buffer, Calendar calendar) {
1140: buffer.append(mValue);
1141: }
1142: }
1143:
1144: /**
1145: * <p>Inner class to output a constant string.</p>
1146: */
1147: private static class StringLiteral implements Rule {
1148: private final String mValue;
1149:
1150: /**
1151: * Constructs a new instance of <code>StringLiteral</code>
1152: * to hold the specified value.
1153: *
1154: * @param value the string literal
1155: */
1156: StringLiteral(String value) {
1157: mValue = value;
1158: }
1159:
1160: /**
1161: * {@inheritDoc}
1162: */
1163: public int estimateLength() {
1164: return mValue.length();
1165: }
1166:
1167: /**
1168: * {@inheritDoc}
1169: */
1170: public void appendTo(StringBuffer buffer, Calendar calendar) {
1171: buffer.append(mValue);
1172: }
1173: }
1174:
1175: /**
1176: * <p>Inner class to output one of a set of values.</p>
1177: */
1178: private static class TextField implements Rule {
1179: private final int mField;
1180: private final String[] mValues;
1181:
1182: /**
1183: * Constructs an instance of <code>TextField</code>
1184: * with the specified field and values.
1185: *
1186: * @param field the field
1187: * @param values the field values
1188: */
1189: TextField(int field, String[] values) {
1190: mField = field;
1191: mValues = values;
1192: }
1193:
1194: /**
1195: * {@inheritDoc}
1196: */
1197: public int estimateLength() {
1198: int max = 0;
1199: for (int i = mValues.length; --i >= 0;) {
1200: int len = mValues[i].length();
1201: if (len > max) {
1202: max = len;
1203: }
1204: }
1205: return max;
1206: }
1207:
1208: /**
1209: * {@inheritDoc}
1210: */
1211: public void appendTo(StringBuffer buffer, Calendar calendar) {
1212: buffer.append(mValues[calendar.get(mField)]);
1213: }
1214: }
1215:
1216: /**
1217: * <p>Inner class to output an unpadded number.</p>
1218: */
1219: private static class UnpaddedNumberField implements NumberRule {
1220: static final UnpaddedNumberField INSTANCE_YEAR = new UnpaddedNumberField(
1221: Calendar.YEAR);
1222:
1223: private final int mField;
1224:
1225: /**
1226: * Constructs an instance of <code>UnpadedNumberField</code> with the specified field.
1227: *
1228: * @param field the field
1229: */
1230: UnpaddedNumberField(int field) {
1231: mField = field;
1232: }
1233:
1234: /**
1235: * {@inheritDoc}
1236: */
1237: public int estimateLength() {
1238: return 4;
1239: }
1240:
1241: /**
1242: * {@inheritDoc}
1243: */
1244: public void appendTo(StringBuffer buffer, Calendar calendar) {
1245: appendTo(buffer, calendar.get(mField));
1246: }
1247:
1248: /**
1249: * {@inheritDoc}
1250: */
1251: public final void appendTo(StringBuffer buffer, int value) {
1252: if (value < 10) {
1253: buffer.append((char) (value + '0'));
1254: } else if (value < 100) {
1255: buffer.append((char) (value / 10 + '0'));
1256: buffer.append((char) (value % 10 + '0'));
1257: } else {
1258: buffer.append(Integer.toString(value));
1259: }
1260: }
1261: }
1262:
1263: /**
1264: * <p>Inner class to output an unpadded month.</p>
1265: */
1266: private static class UnpaddedMonthField implements NumberRule {
1267: static final UnpaddedMonthField INSTANCE = new UnpaddedMonthField();
1268:
1269: /**
1270: * Constructs an instance of <code>UnpaddedMonthField</code>.
1271: *
1272: */
1273: UnpaddedMonthField() {
1274: super ();
1275: }
1276:
1277: /**
1278: * {@inheritDoc}
1279: */
1280: public int estimateLength() {
1281: return 2;
1282: }
1283:
1284: /**
1285: * {@inheritDoc}
1286: */
1287: public void appendTo(StringBuffer buffer, Calendar calendar) {
1288: appendTo(buffer, calendar.get(Calendar.MONTH) + 1);
1289: }
1290:
1291: /**
1292: * {@inheritDoc}
1293: */
1294: public final void appendTo(StringBuffer buffer, int value) {
1295: if (value < 10) {
1296: buffer.append((char) (value + '0'));
1297: } else {
1298: buffer.append((char) (value / 10 + '0'));
1299: buffer.append((char) (value % 10 + '0'));
1300: }
1301: }
1302: }
1303:
1304: /**
1305: * <p>Inner class to output a padded number.</p>
1306: */
1307: private static class PaddedNumberField implements NumberRule {
1308: private final int mField;
1309: private final int mSize;
1310:
1311: /**
1312: * Constructs an instance of <code>PaddedNumberField</code>.
1313: *
1314: * @param field the field
1315: * @param size size of the output field
1316: */
1317: PaddedNumberField(int field, int size) {
1318: if (size < 3) {
1319: // Should use UnpaddedNumberField or TwoDigitNumberField.
1320: throw new IllegalArgumentException();
1321: }
1322: mField = field;
1323: mSize = size;
1324: }
1325:
1326: /**
1327: * {@inheritDoc}
1328: */
1329: public int estimateLength() {
1330: return 4;
1331: }
1332:
1333: /**
1334: * {@inheritDoc}
1335: */
1336: public void appendTo(StringBuffer buffer, Calendar calendar) {
1337: appendTo(buffer, calendar.get(mField));
1338: }
1339:
1340: /**
1341: * {@inheritDoc}
1342: */
1343: public final void appendTo(StringBuffer buffer, int value) {
1344: if (value < 100) {
1345: for (int i = mSize; --i >= 2;) {
1346: buffer.append('0');
1347: }
1348: buffer.append((char) (value / 10 + '0'));
1349: buffer.append((char) (value % 10 + '0'));
1350: } else {
1351: int digits;
1352: if (value < 1000) {
1353: digits = 3;
1354: } else {
1355: Validate.isTrue(value > -1,
1356: "Negative values should not be possible",
1357: value);
1358: digits = Integer.toString(value).length();
1359: }
1360: for (int i = mSize; --i >= digits;) {
1361: buffer.append('0');
1362: }
1363: buffer.append(Integer.toString(value));
1364: }
1365: }
1366: }
1367:
1368: /**
1369: * <p>Inner class to output a two digit number.</p>
1370: */
1371: private static class TwoDigitNumberField implements NumberRule {
1372: private final int mField;
1373:
1374: /**
1375: * Constructs an instance of <code>TwoDigitNumberField</code> with the specified field.
1376: *
1377: * @param field the field
1378: */
1379: TwoDigitNumberField(int field) {
1380: mField = field;
1381: }
1382:
1383: /**
1384: * {@inheritDoc}
1385: */
1386: public int estimateLength() {
1387: return 2;
1388: }
1389:
1390: /**
1391: * {@inheritDoc}
1392: */
1393: public void appendTo(StringBuffer buffer, Calendar calendar) {
1394: appendTo(buffer, calendar.get(mField));
1395: }
1396:
1397: /**
1398: * {@inheritDoc}
1399: */
1400: public final void appendTo(StringBuffer buffer, int value) {
1401: if (value < 100) {
1402: buffer.append((char) (value / 10 + '0'));
1403: buffer.append((char) (value % 10 + '0'));
1404: } else {
1405: buffer.append(Integer.toString(value));
1406: }
1407: }
1408: }
1409:
1410: /**
1411: * <p>Inner class to output a two digit year.</p>
1412: */
1413: private static class TwoDigitYearField implements NumberRule {
1414: static final TwoDigitYearField INSTANCE = new TwoDigitYearField();
1415:
1416: /**
1417: * Constructs an instance of <code>TwoDigitYearField</code>.
1418: */
1419: TwoDigitYearField() {
1420: super ();
1421: }
1422:
1423: /**
1424: * {@inheritDoc}
1425: */
1426: public int estimateLength() {
1427: return 2;
1428: }
1429:
1430: /**
1431: * {@inheritDoc}
1432: */
1433: public void appendTo(StringBuffer buffer, Calendar calendar) {
1434: appendTo(buffer, calendar.get(Calendar.YEAR) % 100);
1435: }
1436:
1437: /**
1438: * {@inheritDoc}
1439: */
1440: public final void appendTo(StringBuffer buffer, int value) {
1441: buffer.append((char) (value / 10 + '0'));
1442: buffer.append((char) (value % 10 + '0'));
1443: }
1444: }
1445:
1446: /**
1447: * <p>Inner class to output a two digit month.</p>
1448: */
1449: private static class TwoDigitMonthField implements NumberRule {
1450: static final TwoDigitMonthField INSTANCE = new TwoDigitMonthField();
1451:
1452: /**
1453: * Constructs an instance of <code>TwoDigitMonthField</code>.
1454: */
1455: TwoDigitMonthField() {
1456: super ();
1457: }
1458:
1459: /**
1460: * {@inheritDoc}
1461: */
1462: public int estimateLength() {
1463: return 2;
1464: }
1465:
1466: /**
1467: * {@inheritDoc}
1468: */
1469: public void appendTo(StringBuffer buffer, Calendar calendar) {
1470: appendTo(buffer, calendar.get(Calendar.MONTH) + 1);
1471: }
1472:
1473: /**
1474: * {@inheritDoc}
1475: */
1476: public final void appendTo(StringBuffer buffer, int value) {
1477: buffer.append((char) (value / 10 + '0'));
1478: buffer.append((char) (value % 10 + '0'));
1479: }
1480: }
1481:
1482: /**
1483: * <p>Inner class to output the twelve hour field.</p>
1484: */
1485: private static class TwelveHourField implements NumberRule {
1486: private final NumberRule mRule;
1487:
1488: /**
1489: * Constructs an instance of <code>TwelveHourField</code> with the specified
1490: * <code>NumberRule</code>.
1491: *
1492: * @param rule the rule
1493: */
1494: TwelveHourField(NumberRule rule) {
1495: mRule = rule;
1496: }
1497:
1498: /**
1499: * {@inheritDoc}
1500: */
1501: public int estimateLength() {
1502: return mRule.estimateLength();
1503: }
1504:
1505: /**
1506: * {@inheritDoc}
1507: */
1508: public void appendTo(StringBuffer buffer, Calendar calendar) {
1509: int value = calendar.get(Calendar.HOUR);
1510: if (value == 0) {
1511: value = calendar.getLeastMaximum(Calendar.HOUR) + 1;
1512: }
1513: mRule.appendTo(buffer, value);
1514: }
1515:
1516: /**
1517: * {@inheritDoc}
1518: */
1519: public void appendTo(StringBuffer buffer, int value) {
1520: mRule.appendTo(buffer, value);
1521: }
1522: }
1523:
1524: /**
1525: * <p>Inner class to output the twenty four hour field.</p>
1526: */
1527: private static class TwentyFourHourField implements NumberRule {
1528: private final NumberRule mRule;
1529:
1530: /**
1531: * Constructs an instance of <code>TwentyFourHourField</code> with the specified
1532: * <code>NumberRule</code>.
1533: *
1534: * @param rule the rule
1535: */
1536: TwentyFourHourField(NumberRule rule) {
1537: mRule = rule;
1538: }
1539:
1540: /**
1541: * {@inheritDoc}
1542: */
1543: public int estimateLength() {
1544: return mRule.estimateLength();
1545: }
1546:
1547: /**
1548: * {@inheritDoc}
1549: */
1550: public void appendTo(StringBuffer buffer, Calendar calendar) {
1551: int value = calendar.get(Calendar.HOUR_OF_DAY);
1552: if (value == 0) {
1553: value = calendar.getMaximum(Calendar.HOUR_OF_DAY) + 1;
1554: }
1555: mRule.appendTo(buffer, value);
1556: }
1557:
1558: /**
1559: * {@inheritDoc}
1560: */
1561: public void appendTo(StringBuffer buffer, int value) {
1562: mRule.appendTo(buffer, value);
1563: }
1564: }
1565:
1566: /**
1567: * <p>Inner class to output a time zone name.</p>
1568: */
1569: private static class TimeZoneNameRule implements Rule {
1570: private final TimeZone mTimeZone;
1571: private final boolean mTimeZoneForced;
1572: private final Locale mLocale;
1573: private final int mStyle;
1574: private final String mStandard;
1575: private final String mDaylight;
1576:
1577: /**
1578: * Constructs an instance of <code>TimeZoneNameRule</code> with the specified properties.
1579: *
1580: * @param timeZone the time zone
1581: * @param timeZoneForced if <code>true</code> the time zone is forced into standard and daylight
1582: * @param locale the locale
1583: * @param style the style
1584: */
1585: TimeZoneNameRule(TimeZone timeZone, boolean timeZoneForced,
1586: Locale locale, int style) {
1587: mTimeZone = timeZone;
1588: mTimeZoneForced = timeZoneForced;
1589: mLocale = locale;
1590: mStyle = style;
1591:
1592: if (timeZoneForced) {
1593: mStandard = getTimeZoneDisplay(timeZone, false, style,
1594: locale);
1595: mDaylight = getTimeZoneDisplay(timeZone, true, style,
1596: locale);
1597: } else {
1598: mStandard = null;
1599: mDaylight = null;
1600: }
1601: }
1602:
1603: /**
1604: * {@inheritDoc}
1605: */
1606: public int estimateLength() {
1607: if (mTimeZoneForced) {
1608: return Math.max(mStandard.length(), mDaylight.length());
1609: } else if (mStyle == TimeZone.SHORT) {
1610: return 4;
1611: } else {
1612: return 40;
1613: }
1614: }
1615:
1616: /**
1617: * {@inheritDoc}
1618: */
1619: public void appendTo(StringBuffer buffer, Calendar calendar) {
1620: if (mTimeZoneForced) {
1621: if (mTimeZone.useDaylightTime()
1622: && calendar.get(Calendar.DST_OFFSET) != 0) {
1623: buffer.append(mDaylight);
1624: } else {
1625: buffer.append(mStandard);
1626: }
1627: } else {
1628: TimeZone timeZone = calendar.getTimeZone();
1629: if (timeZone.useDaylightTime()
1630: && calendar.get(Calendar.DST_OFFSET) != 0) {
1631: buffer.append(getTimeZoneDisplay(timeZone, true,
1632: mStyle, mLocale));
1633: } else {
1634: buffer.append(getTimeZoneDisplay(timeZone, false,
1635: mStyle, mLocale));
1636: }
1637: }
1638: }
1639: }
1640:
1641: /**
1642: * <p>Inner class to output a time zone as a number <code>+/-HHMM</code>
1643: * or <code>+/-HH:MM</code>.</p>
1644: */
1645: private static class TimeZoneNumberRule implements Rule {
1646: static final TimeZoneNumberRule INSTANCE_COLON = new TimeZoneNumberRule(
1647: true);
1648: static final TimeZoneNumberRule INSTANCE_NO_COLON = new TimeZoneNumberRule(
1649: false);
1650:
1651: final boolean mColon;
1652:
1653: /**
1654: * Constructs an instance of <code>TimeZoneNumberRule</code> with the specified properties.
1655: *
1656: * @param colon add colon between HH and MM in the output if <code>true</code>
1657: */
1658: TimeZoneNumberRule(boolean colon) {
1659: mColon = colon;
1660: }
1661:
1662: /**
1663: * {@inheritDoc}
1664: */
1665: public int estimateLength() {
1666: return 5;
1667: }
1668:
1669: /**
1670: * {@inheritDoc}
1671: */
1672: public void appendTo(StringBuffer buffer, Calendar calendar) {
1673: int offset = calendar.get(Calendar.ZONE_OFFSET)
1674: + calendar.get(Calendar.DST_OFFSET);
1675:
1676: if (offset < 0) {
1677: buffer.append('-');
1678: offset = -offset;
1679: } else {
1680: buffer.append('+');
1681: }
1682:
1683: int hours = offset / (60 * 60 * 1000);
1684: buffer.append((char) (hours / 10 + '0'));
1685: buffer.append((char) (hours % 10 + '0'));
1686:
1687: if (mColon) {
1688: buffer.append(':');
1689: }
1690:
1691: int minutes = offset / (60 * 1000) - 60 * hours;
1692: buffer.append((char) (minutes / 10 + '0'));
1693: buffer.append((char) (minutes % 10 + '0'));
1694: }
1695: }
1696:
1697: // ----------------------------------------------------------------------
1698: /**
1699: * <p>Inner class that acts as a compound key for time zone names.</p>
1700: */
1701: private static class TimeZoneDisplayKey {
1702: private final TimeZone mTimeZone;
1703: private final int mStyle;
1704: private final Locale mLocale;
1705:
1706: /**
1707: * Constructs an instance of <code>TimeZoneDisplayKey</code> with the specified properties.
1708: *
1709: * @param timeZone the time zone
1710: * @param daylight adjust the style for daylight saving time if <code>true</code>
1711: * @param style the timezone style
1712: * @param locale the timezone locale
1713: */
1714: TimeZoneDisplayKey(TimeZone timeZone, boolean daylight,
1715: int style, Locale locale) {
1716: mTimeZone = timeZone;
1717: if (daylight) {
1718: style |= 0x80000000;
1719: }
1720: mStyle = style;
1721: mLocale = locale;
1722: }
1723:
1724: /**
1725: * {@inheritDoc}
1726: */
1727: public int hashCode() {
1728: return mStyle * 31 + mLocale.hashCode();
1729: }
1730:
1731: /**
1732: * {@inheritDoc}
1733: */
1734: public boolean equals(Object obj) {
1735: if (this == obj) {
1736: return true;
1737: }
1738: if (obj instanceof TimeZoneDisplayKey) {
1739: TimeZoneDisplayKey other = (TimeZoneDisplayKey) obj;
1740: return mTimeZone.equals(other.mTimeZone)
1741: && mStyle == other.mStyle
1742: && mLocale.equals(other.mLocale);
1743: }
1744: return false;
1745: }
1746: }
1747:
1748: // ----------------------------------------------------------------------
1749: /**
1750: * <p>Helper class for creating compound objects.</p>
1751: *
1752: * <p>One use for this class is to create a hashtable key
1753: * out of multiple objects.</p>
1754: */
1755: private static class Pair {
1756: private final Object mObj1;
1757: private final Object mObj2;
1758:
1759: /**
1760: * Constructs an instance of <code>Pair</code> to hold the specified objects.
1761: * @param obj1 one object in the pair
1762: * @param obj2 second object in the pair
1763: */
1764: public Pair(Object obj1, Object obj2) {
1765: mObj1 = obj1;
1766: mObj2 = obj2;
1767: }
1768:
1769: /**
1770: * {@inheritDoc}
1771: */
1772: public boolean equals(Object obj) {
1773: if (this == obj) {
1774: return true;
1775: }
1776:
1777: if (!(obj instanceof Pair)) {
1778: return false;
1779: }
1780:
1781: Pair key = (Pair) obj;
1782:
1783: return (mObj1 == null ? key.mObj1 == null : mObj1
1784: .equals(key.mObj1))
1785: && (mObj2 == null ? key.mObj2 == null : mObj2
1786: .equals(key.mObj2));
1787: }
1788:
1789: /**
1790: * {@inheritDoc}
1791: */
1792: public int hashCode() {
1793: return (mObj1 == null ? 0 : mObj1.hashCode())
1794: + (mObj2 == null ? 0 : mObj2.hashCode());
1795: }
1796:
1797: /**
1798: * {@inheritDoc}
1799: */
1800: public String toString() {
1801: return "[" + mObj1 + ':' + mObj2 + ']';
1802: }
1803: }
1804:
1805: }
|