0001: /* ====================================================================
0002: * Trove - Copyright (c) 1997-2001 Walt Disney Internet Group
0003: * ====================================================================
0004: * The Tea Software License, Version 1.1
0005: *
0006: * Copyright (c) 2000 Walt Disney Internet Group. All rights reserved.
0007: *
0008: * Redistribution and use in source and binary forms, with or without
0009: * modification, are permitted provided that the following conditions
0010: * are met:
0011: *
0012: * 1. Redistributions of source code must retain the above copyright
0013: * notice, this list of conditions and the following disclaimer.
0014: *
0015: * 2. Redistributions in binary form must reproduce the above copyright
0016: * notice, this list of conditions and the following disclaimer in
0017: * the documentation and/or other materials provided with the
0018: * distribution.
0019: *
0020: * 3. The end-user documentation included with the redistribution,
0021: * if any, must include the following acknowledgment:
0022: * "This product includes software developed by the
0023: * Walt Disney Internet Group (http://opensource.go.com/)."
0024: * Alternately, this acknowledgment may appear in the software itself,
0025: * if and wherever such third-party acknowledgments normally appear.
0026: *
0027: * 4. The names "Tea", "TeaServlet", "Kettle", "Trove" and "BeanDoc" must
0028: * not be used to endorse or promote products derived from this
0029: * software without prior written permission. For written
0030: * permission, please contact opensource@dig.com.
0031: *
0032: * 5. Products derived from this software may not be called "Tea",
0033: * "TeaServlet", "Kettle" or "Trove", nor may "Tea", "TeaServlet",
0034: * "Kettle", "Trove" or "BeanDoc" appear in their name, without prior
0035: * written permission of the Walt Disney Internet Group.
0036: *
0037: * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
0038: * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
0039: * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
0040: * DISCLAIMED. IN NO EVENT SHALL THE WALT DISNEY INTERNET GROUP OR ITS
0041: * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
0042: * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
0043: * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
0044: * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
0045: * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
0046: * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
0047: * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
0048: * ====================================================================
0049: *
0050: * For more information about Tea, please see http://opensource.go.com/.
0051: */
0052:
0053: package org.jivesoftware.util;
0054:
0055: import java.util.Date;
0056: import java.util.Calendar;
0057: import java.util.GregorianCalendar;
0058: import java.util.Locale;
0059: import java.util.TimeZone;
0060: import java.util.List;
0061: import java.util.ArrayList;
0062: import java.util.Map;
0063: import java.util.HashMap;
0064: import java.text.DateFormatSymbols;
0065: import java.text.DateFormat;
0066: import java.text.SimpleDateFormat;
0067:
0068: /**
0069: * <p>Similar to {@link java.text.SimpleDateFormat}, but faster and thread-safe.
0070: * Only formatting is supported, but all patterns are compatible with
0071: * SimpleDateFormat.</p>
0072: *
0073: * <p>Note, this class is from the open source Tea project (http://sourceforge.net/projects/teatrove/).</p>
0074: *
0075: * @author Brian S O'Neill
0076: */
0077: public class FastDateFormat {
0078: /** Style pattern */
0079: public static final Object FULL = new Integer(SimpleDateFormat.FULL),
0080: LONG = new Integer(SimpleDateFormat.LONG),
0081: MEDIUM = new Integer(SimpleDateFormat.MEDIUM),
0082: SHORT = new Integer(SimpleDateFormat.SHORT);
0083:
0084: private static final double LOG_10 = Math.log(10);
0085:
0086: private static String cDefaultPattern;
0087: private static TimeZone cDefaultTimeZone = TimeZone.getDefault();
0088:
0089: private static Map cTimeZoneDisplayCache = new HashMap();
0090:
0091: private static Map cInstanceCache = new HashMap(7);
0092: private static Map cDateInstanceCache = new HashMap(7);
0093: private static Map cTimeInstanceCache = new HashMap(7);
0094: private static Map cDateTimeInstanceCache = new HashMap(7);
0095:
0096: public static FastDateFormat getInstance() {
0097: return getInstance(getDefaultPattern(), null, null, null);
0098: }
0099:
0100: /**
0101: * @param pattern {@link java.text.SimpleDateFormat} compatible pattern
0102: */
0103: public static FastDateFormat getInstance(String pattern)
0104: throws IllegalArgumentException {
0105: return getInstance(pattern, null, null, null);
0106: }
0107:
0108: /**
0109: * @param pattern {@link java.text.SimpleDateFormat} compatible pattern
0110: * @param timeZone optional time zone, overrides time zone of formatted
0111: * date
0112: */
0113: public static FastDateFormat getInstance(String pattern,
0114: TimeZone timeZone) throws IllegalArgumentException {
0115: return getInstance(pattern, timeZone, null, null);
0116: }
0117:
0118: /**
0119: * @param pattern {@link java.text.SimpleDateFormat} compatible pattern
0120: * @param locale optional locale, overrides system locale
0121: */
0122: public static FastDateFormat getInstance(String pattern,
0123: Locale locale) throws IllegalArgumentException {
0124: return getInstance(pattern, null, locale, null);
0125: }
0126:
0127: /**
0128: * @param pattern {@link java.text.SimpleDateFormat} compatible pattern
0129: * @param symbols optional date format symbols, overrides symbols for
0130: * system locale
0131: */
0132: public static FastDateFormat getInstance(String pattern,
0133: DateFormatSymbols symbols) throws IllegalArgumentException {
0134: return getInstance(pattern, null, null, symbols);
0135: }
0136:
0137: /**
0138: * @param pattern {@link java.text.SimpleDateFormat} compatible pattern
0139: * @param timeZone optional time zone, overrides time zone of formatted
0140: * date
0141: * @param locale optional locale, overrides system locale
0142: */
0143: public static FastDateFormat getInstance(String pattern,
0144: TimeZone timeZone, Locale locale)
0145: throws IllegalArgumentException {
0146: return getInstance(pattern, timeZone, locale, null);
0147: }
0148:
0149: /**
0150: * @param pattern {@link java.text.SimpleDateFormat} compatible pattern
0151: * @param timeZone optional time zone, overrides time zone of formatted
0152: * date
0153: * @param locale optional locale, overrides system locale
0154: * @param symbols optional date format symbols, overrides symbols for
0155: * provided locale
0156: */
0157: public static synchronized FastDateFormat getInstance(
0158: String pattern, TimeZone timeZone, Locale locale,
0159: DateFormatSymbols symbols) throws IllegalArgumentException {
0160: Object key = pattern;
0161:
0162: if (timeZone != null) {
0163: key = new Pair(key, timeZone);
0164: }
0165: if (locale != null) {
0166: key = new Pair(key, locale);
0167: }
0168: if (symbols != null) {
0169: key = new Pair(key, symbols);
0170: }
0171:
0172: FastDateFormat format = (FastDateFormat) cInstanceCache
0173: .get(key);
0174: if (format == null) {
0175: if (locale == null) {
0176: locale = Locale.getDefault();
0177: }
0178: if (symbols == null) {
0179: symbols = new DateFormatSymbols(locale);
0180: }
0181: format = new FastDateFormat(pattern, timeZone, locale,
0182: symbols);
0183: cInstanceCache.put(key, format);
0184: }
0185: return format;
0186: }
0187:
0188: /**
0189: * @param style date style: FULL, LONG, MEDIUM, or SHORT
0190: * @param timeZone optional time zone, overrides time zone of formatted
0191: * date
0192: * @param locale optional locale, overrides system locale
0193: */
0194: public static synchronized FastDateFormat getDateInstance(
0195: Object style, TimeZone timeZone, Locale locale)
0196: throws IllegalArgumentException {
0197: Object key = style;
0198:
0199: if (timeZone != null) {
0200: key = new Pair(key, timeZone);
0201: }
0202: if (locale == null) {
0203: key = new Pair(key, locale);
0204: }
0205:
0206: FastDateFormat format = (FastDateFormat) cDateInstanceCache
0207: .get(key);
0208:
0209: if (format == null) {
0210: int ds;
0211: try {
0212: ds = ((Integer) style).intValue();
0213: } catch (ClassCastException e) {
0214: throw new IllegalArgumentException(
0215: "Illegal date style: " + style);
0216: }
0217:
0218: if (locale == null) {
0219: locale = Locale.getDefault();
0220: }
0221:
0222: try {
0223: String pattern = ((SimpleDateFormat) DateFormat
0224: .getDateInstance(ds, locale)).toPattern();
0225: format = getInstance(pattern, timeZone, locale);
0226: cDateInstanceCache.put(key, format);
0227: } catch (ClassCastException e) {
0228: throw new IllegalArgumentException(
0229: "No date pattern for locale: " + locale);
0230: }
0231: }
0232:
0233: return format;
0234: }
0235:
0236: /**
0237: * @param style time style: FULL, LONG, MEDIUM, or SHORT
0238: * @param timeZone optional time zone, overrides time zone of formatted
0239: * date
0240: * @param locale optional locale, overrides system locale
0241: */
0242: public static synchronized FastDateFormat getTimeInstance(
0243: Object style, TimeZone timeZone, Locale locale)
0244: throws IllegalArgumentException {
0245: Object key = style;
0246:
0247: if (timeZone != null) {
0248: key = new Pair(key, timeZone);
0249: }
0250: if (locale != null) {
0251: key = new Pair(key, locale);
0252: }
0253:
0254: FastDateFormat format = (FastDateFormat) cTimeInstanceCache
0255: .get(key);
0256:
0257: if (format == null) {
0258: int ts;
0259: try {
0260: ts = ((Integer) style).intValue();
0261: } catch (ClassCastException e) {
0262: throw new IllegalArgumentException(
0263: "Illegal time style: " + style);
0264: }
0265:
0266: if (locale == null) {
0267: locale = Locale.getDefault();
0268: }
0269:
0270: try {
0271: String pattern = ((SimpleDateFormat) DateFormat
0272: .getTimeInstance(ts, locale)).toPattern();
0273: format = getInstance(pattern, timeZone, locale);
0274: cTimeInstanceCache.put(key, format);
0275: } catch (ClassCastException e) {
0276: throw new IllegalArgumentException(
0277: "No date pattern for locale: " + locale);
0278: }
0279: }
0280:
0281: return format;
0282: }
0283:
0284: /**
0285: * @param dateStyle date style: FULL, LONG, MEDIUM, or SHORT
0286: * @param timeStyle time style: FULL, LONG, MEDIUM, or SHORT
0287: * @param timeZone optional time zone, overrides time zone of formatted
0288: * date
0289: * @param locale optional locale, overrides system locale
0290: */
0291: public static synchronized FastDateFormat getDateTimeInstance(
0292: Object dateStyle, Object timeStyle, TimeZone timeZone,
0293: Locale locale) throws IllegalArgumentException {
0294: Object key = new Pair(dateStyle, timeStyle);
0295:
0296: if (timeZone != null) {
0297: key = new Pair(key, timeZone);
0298: }
0299: if (locale != null) {
0300: key = new Pair(key, locale);
0301: }
0302:
0303: FastDateFormat format = (FastDateFormat) cDateTimeInstanceCache
0304: .get(key);
0305:
0306: if (format == null) {
0307: int ds;
0308: try {
0309: ds = ((Integer) dateStyle).intValue();
0310: } catch (ClassCastException e) {
0311: throw new IllegalArgumentException(
0312: "Illegal date style: " + dateStyle);
0313: }
0314:
0315: int ts;
0316: try {
0317: ts = ((Integer) timeStyle).intValue();
0318: } catch (ClassCastException e) {
0319: throw new IllegalArgumentException(
0320: "Illegal time style: " + timeStyle);
0321: }
0322:
0323: if (locale == null) {
0324: locale = Locale.getDefault();
0325: }
0326:
0327: try {
0328: String pattern = ((SimpleDateFormat) DateFormat
0329: .getDateTimeInstance(ds, ts, locale))
0330: .toPattern();
0331: format = getInstance(pattern, timeZone, locale);
0332: cDateTimeInstanceCache.put(key, format);
0333: } catch (ClassCastException e) {
0334: throw new IllegalArgumentException(
0335: "No date time pattern for locale: " + locale);
0336: }
0337: }
0338:
0339: return format;
0340: }
0341:
0342: static synchronized String getTimeZoneDisplay(TimeZone tz,
0343: boolean daylight, int style, Locale locale) {
0344: Object key = new TimeZoneDisplayKey(tz, daylight, style, locale);
0345: String value = (String) cTimeZoneDisplayCache.get(key);
0346: if (value == null) {
0347: // This is a very slow call, so cache the results.
0348: value = tz.getDisplayName(daylight, style, locale);
0349: cTimeZoneDisplayCache.put(key, value);
0350: }
0351: return value;
0352: }
0353:
0354: private static synchronized String getDefaultPattern() {
0355: if (cDefaultPattern == null) {
0356: cDefaultPattern = new SimpleDateFormat().toPattern();
0357: }
0358: return cDefaultPattern;
0359: }
0360:
0361: /**
0362: * Returns a list of Rules.
0363: */
0364: private static List parse(String pattern, TimeZone timeZone,
0365: Locale locale, DateFormatSymbols symbols) {
0366: List rules = new ArrayList();
0367:
0368: String[] ERAs = symbols.getEras();
0369: String[] months = symbols.getMonths();
0370: String[] shortMonths = symbols.getShortMonths();
0371: String[] weekdays = symbols.getWeekdays();
0372: String[] shortWeekdays = symbols.getShortWeekdays();
0373: String[] AmPmStrings = symbols.getAmPmStrings();
0374:
0375: int length = pattern.length();
0376: int[] indexRef = new int[1];
0377:
0378: for (int i = 0; i < length; i++) {
0379: indexRef[0] = i;
0380: String token = parseToken(pattern, indexRef);
0381: i = indexRef[0];
0382:
0383: int tokenLen = token.length();
0384: if (tokenLen == 0) {
0385: break;
0386: }
0387:
0388: Rule rule;
0389: char c = token.charAt(0);
0390:
0391: switch (c) {
0392: case 'G': // era designator (text)
0393: rule = new TextField(Calendar.ERA, ERAs);
0394: break;
0395: case 'y': // year (number)
0396: if (tokenLen >= 4) {
0397: rule = new UnpaddedNumberField(Calendar.YEAR);
0398: } else {
0399: rule = new TwoDigitYearField();
0400: }
0401: break;
0402: case 'M': // month in year (text and number)
0403: if (tokenLen >= 4) {
0404: rule = new TextField(Calendar.MONTH, months);
0405: } else if (tokenLen == 3) {
0406: rule = new TextField(Calendar.MONTH, shortMonths);
0407: } else if (tokenLen == 2) {
0408: rule = new TwoDigitMonthField();
0409: } else {
0410: rule = new UnpaddedMonthField();
0411: }
0412: break;
0413: case 'd': // day in month (number)
0414: rule = selectNumberRule(Calendar.DAY_OF_MONTH, tokenLen);
0415: break;
0416: case 'h': // hour in am/pm (number, 1..12)
0417: rule = new TwelveHourField(selectNumberRule(
0418: Calendar.HOUR, tokenLen));
0419: break;
0420: case 'H': // hour in day (number, 0..23)
0421: rule = selectNumberRule(Calendar.HOUR_OF_DAY, tokenLen);
0422: break;
0423: case 'm': // minute in hour (number)
0424: rule = selectNumberRule(Calendar.MINUTE, tokenLen);
0425: break;
0426: case 's': // second in minute (number)
0427: rule = selectNumberRule(Calendar.SECOND, tokenLen);
0428: break;
0429: case 'S': // millisecond (number)
0430: rule = selectNumberRule(Calendar.MILLISECOND, tokenLen);
0431: break;
0432: case 'E': // day in week (text)
0433: rule = new TextField(Calendar.DAY_OF_WEEK,
0434: tokenLen < 4 ? shortWeekdays : weekdays);
0435: break;
0436: case 'D': // day in year (number)
0437: rule = selectNumberRule(Calendar.DAY_OF_YEAR, tokenLen);
0438: break;
0439: case 'F': // day of week in month (number)
0440: rule = selectNumberRule(Calendar.DAY_OF_WEEK_IN_MONTH,
0441: tokenLen);
0442: break;
0443: case 'w': // week in year (number)
0444: rule = selectNumberRule(Calendar.WEEK_OF_YEAR, tokenLen);
0445: break;
0446: case 'W': // week in month (number)
0447: rule = selectNumberRule(Calendar.WEEK_OF_MONTH,
0448: tokenLen);
0449: break;
0450: case 'a': // am/pm marker (text)
0451: rule = new TextField(Calendar.AM_PM, AmPmStrings);
0452: break;
0453: case 'k': // hour in day (1..24)
0454: rule = new TwentyFourHourField(selectNumberRule(
0455: Calendar.HOUR_OF_DAY, tokenLen));
0456: break;
0457: case 'K': // hour in am/pm (0..11)
0458: rule = selectNumberRule(Calendar.HOUR, tokenLen);
0459: break;
0460: case 'z': // time zone (text)
0461: if (tokenLen >= 4) {
0462: rule = new TimeZoneRule(timeZone, locale,
0463: TimeZone.LONG);
0464: } else {
0465: rule = new TimeZoneRule(timeZone, locale,
0466: TimeZone.SHORT);
0467: }
0468: break;
0469: case '\'': // literal text
0470: String sub = token.substring(1);
0471: if (sub.length() == 1) {
0472: rule = new CharacterLiteral(sub.charAt(0));
0473: } else {
0474: rule = new StringLiteral(new String(sub));
0475: }
0476: break;
0477: default:
0478: throw new IllegalArgumentException(
0479: "Illegal pattern component: " + token);
0480: }
0481:
0482: rules.add(rule);
0483: }
0484:
0485: return rules;
0486: }
0487:
0488: private static String parseToken(String pattern, int[] indexRef) {
0489: StringBuffer buf = new StringBuffer();
0490:
0491: int i = indexRef[0];
0492: int length = pattern.length();
0493:
0494: char c = pattern.charAt(i);
0495: if (c >= 'A' && c <= 'Z' || c >= 'a' && c <= 'z') {
0496: // Scan a run of the same character, which indicates a time
0497: // pattern.
0498: buf.append(c);
0499:
0500: while (i + 1 < length) {
0501: char peek = pattern.charAt(i + 1);
0502: if (peek == c) {
0503: buf.append(c);
0504: i++;
0505: } else {
0506: break;
0507: }
0508: }
0509: } else {
0510: // This will identify token as text.
0511: buf.append('\'');
0512:
0513: boolean inLiteral = false;
0514:
0515: for (; i < length; i++) {
0516: c = pattern.charAt(i);
0517:
0518: if (c == '\'') {
0519: if (i + 1 < length && pattern.charAt(i + 1) == '\'') {
0520: // '' is treated as escaped '
0521: i++;
0522: buf.append(c);
0523: } else {
0524: inLiteral = !inLiteral;
0525: }
0526: } else if (!inLiteral
0527: && (c >= 'A' && c <= 'Z' || c >= 'a'
0528: && c <= 'z')) {
0529: i--;
0530: break;
0531: } else {
0532: buf.append(c);
0533: }
0534: }
0535: }
0536:
0537: indexRef[0] = i;
0538: return buf.toString();
0539: }
0540:
0541: private static NumberRule selectNumberRule(int field, int padding) {
0542: switch (padding) {
0543: case 1:
0544: return new UnpaddedNumberField(field);
0545: case 2:
0546: return new TwoDigitNumberField(field);
0547: default:
0548: return new PaddedNumberField(field, padding);
0549: }
0550: }
0551:
0552: private final String mPattern;
0553: private final TimeZone mTimeZone;
0554: private final Locale mLocale;
0555: private final Rule[] mRules;
0556: private final int mMaxLengthEstimate;
0557:
0558: private FastDateFormat() {
0559: this (getDefaultPattern(), null, null, null);
0560: }
0561:
0562: /**
0563: * @param pattern {@link java.text.SimpleDateFormat} compatible pattern
0564: */
0565: private FastDateFormat(String pattern)
0566: throws IllegalArgumentException {
0567: this (pattern, null, null, null);
0568: }
0569:
0570: /**
0571: * @param pattern {@link java.text.SimpleDateFormat} compatible pattern
0572: * @param timeZone optional time zone, overrides time zone of formatted
0573: * date
0574: */
0575: private FastDateFormat(String pattern, TimeZone timeZone)
0576: throws IllegalArgumentException {
0577: this (pattern, timeZone, null, null);
0578: }
0579:
0580: /**
0581: * @param pattern {@link java.text.SimpleDateFormat} compatible pattern
0582: * @param locale optional locale, overrides system locale
0583: */
0584: private FastDateFormat(String pattern, Locale locale)
0585: throws IllegalArgumentException {
0586: this (pattern, null, locale, null);
0587: }
0588:
0589: /**
0590: * @param pattern {@link java.text.SimpleDateFormat} compatible pattern
0591: * @param symbols optional date format symbols, overrides symbols for
0592: * system locale
0593: */
0594: private FastDateFormat(String pattern, DateFormatSymbols symbols)
0595: throws IllegalArgumentException {
0596: this (pattern, null, null, symbols);
0597: }
0598:
0599: /**
0600: * @param pattern {@link java.text.SimpleDateFormat} compatible pattern
0601: * @param timeZone optional time zone, overrides time zone of formatted
0602: * date
0603: * @param locale optional locale, overrides system locale
0604: */
0605: private FastDateFormat(String pattern, TimeZone timeZone,
0606: Locale locale) throws IllegalArgumentException {
0607: this (pattern, timeZone, locale, null);
0608: }
0609:
0610: /**
0611: * @param pattern {@link java.text.SimpleDateFormat} compatible pattern
0612: * @param timeZone optional time zone, overrides time zone of formatted
0613: * date
0614: * @param locale optional locale, overrides system locale
0615: * @param symbols optional date format symbols, overrides symbols for
0616: * provided locale
0617: */
0618: private FastDateFormat(String pattern, TimeZone timeZone,
0619: Locale locale, DateFormatSymbols symbols)
0620: throws IllegalArgumentException {
0621: if (locale == null) {
0622: locale = Locale.getDefault();
0623: }
0624:
0625: mPattern = pattern;
0626: mTimeZone = timeZone;
0627: mLocale = locale;
0628:
0629: if (symbols == null) {
0630: symbols = new DateFormatSymbols(locale);
0631: }
0632:
0633: List rulesList = parse(pattern, timeZone, locale, symbols);
0634: mRules = (Rule[]) rulesList.toArray(new Rule[rulesList.size()]);
0635:
0636: int len = 0;
0637: for (int i = mRules.length; --i >= 0;) {
0638: len += mRules[i].estimateLength();
0639: }
0640:
0641: mMaxLengthEstimate = len;
0642: }
0643:
0644: public String format(Date date) {
0645: Calendar c = new GregorianCalendar(cDefaultTimeZone);
0646: c.setTime(date);
0647: if (mTimeZone != null) {
0648: c.setTimeZone(mTimeZone);
0649: }
0650: return applyRules(c, new StringBuffer(mMaxLengthEstimate))
0651: .toString();
0652: }
0653:
0654: public String format(Calendar calendar) {
0655: return format(calendar, new StringBuffer(mMaxLengthEstimate))
0656: .toString();
0657: }
0658:
0659: public StringBuffer format(Date date, StringBuffer buf) {
0660: Calendar c = new GregorianCalendar(cDefaultTimeZone);
0661: c.setTime(date);
0662: if (mTimeZone != null) {
0663: c.setTimeZone(mTimeZone);
0664: }
0665: return applyRules(c, buf);
0666: }
0667:
0668: public StringBuffer format(Calendar calendar, StringBuffer buf) {
0669: if (mTimeZone != null) {
0670: calendar = (Calendar) calendar.clone();
0671: calendar.setTimeZone(mTimeZone);
0672: }
0673: return applyRules(calendar, buf);
0674: }
0675:
0676: private StringBuffer applyRules(Calendar calendar, StringBuffer buf) {
0677: Rule[] rules = mRules;
0678: int len = mRules.length;
0679: for (int i = 0; i < len; i++) {
0680: rules[i].appendTo(buf, calendar);
0681: }
0682: return buf;
0683: }
0684:
0685: public String getPattern() {
0686: return mPattern;
0687: }
0688:
0689: /**
0690: * Returns the time zone used by this formatter, or null if time zone of
0691: * formatted dates is used instead.
0692: */
0693: public TimeZone getTimeZone() {
0694: return mTimeZone;
0695: }
0696:
0697: public Locale getLocale() {
0698: return mLocale;
0699: }
0700:
0701: /**
0702: * Returns an estimate for the maximum length date that this date
0703: * formatter will produce. The actual formatted length will almost always
0704: * be less than or equal to this amount.
0705: */
0706: public int getMaxLengthEstimate() {
0707: return mMaxLengthEstimate;
0708: }
0709:
0710: private interface Rule {
0711: int estimateLength();
0712:
0713: void appendTo(StringBuffer buffer, Calendar calendar);
0714: }
0715:
0716: private interface NumberRule extends Rule {
0717: void appendTo(StringBuffer buffer, int value);
0718: }
0719:
0720: private static class CharacterLiteral implements Rule {
0721: private final char mValue;
0722:
0723: CharacterLiteral(char value) {
0724: mValue = value;
0725: }
0726:
0727: public int estimateLength() {
0728: return 1;
0729: }
0730:
0731: public void appendTo(StringBuffer buffer, Calendar calendar) {
0732: buffer.append(mValue);
0733: }
0734: }
0735:
0736: private static class StringLiteral implements Rule {
0737: private final String mValue;
0738:
0739: StringLiteral(String value) {
0740: mValue = value;
0741: }
0742:
0743: public int estimateLength() {
0744: return mValue.length();
0745: }
0746:
0747: public void appendTo(StringBuffer buffer, Calendar calendar) {
0748: buffer.append(mValue);
0749: }
0750: }
0751:
0752: private static class TextField implements Rule {
0753: private final int mField;
0754: private final String[] mValues;
0755:
0756: TextField(int field, String[] values) {
0757: mField = field;
0758: mValues = values;
0759: }
0760:
0761: public int estimateLength() {
0762: int max = 0;
0763: for (int i = mValues.length; --i >= 0;) {
0764: int len = mValues[i].length();
0765: if (len > max) {
0766: max = len;
0767: }
0768: }
0769: return max;
0770: }
0771:
0772: public void appendTo(StringBuffer buffer, Calendar calendar) {
0773: buffer.append(mValues[calendar.get(mField)]);
0774: }
0775: }
0776:
0777: private static class UnpaddedNumberField implements NumberRule {
0778: private final int mField;
0779:
0780: UnpaddedNumberField(int field) {
0781: mField = field;
0782: }
0783:
0784: public int estimateLength() {
0785: return 4;
0786: }
0787:
0788: public void appendTo(StringBuffer buffer, Calendar calendar) {
0789: appendTo(buffer, calendar.get(mField));
0790: }
0791:
0792: public final void appendTo(StringBuffer buffer, int value) {
0793: if (value < 10) {
0794: buffer.append((char) (value + '0'));
0795: } else if (value < 100) {
0796: buffer.append((char) (value / 10 + '0'));
0797: buffer.append((char) (value % 10 + '0'));
0798: } else {
0799: buffer.append(Integer.toString(value));
0800: }
0801: }
0802: }
0803:
0804: private static class UnpaddedMonthField implements NumberRule {
0805: UnpaddedMonthField() {
0806: }
0807:
0808: public int estimateLength() {
0809: return 2;
0810: }
0811:
0812: public void appendTo(StringBuffer buffer, Calendar calendar) {
0813: appendTo(buffer, calendar.get(Calendar.MONTH) + 1);
0814: }
0815:
0816: public final void appendTo(StringBuffer buffer, int value) {
0817: if (value < 10) {
0818: buffer.append((char) (value + '0'));
0819: } else {
0820: buffer.append((char) (value / 10 + '0'));
0821: buffer.append((char) (value % 10 + '0'));
0822: }
0823: }
0824: }
0825:
0826: private static class PaddedNumberField implements NumberRule {
0827: private final int mField;
0828: private final int mSize;
0829:
0830: PaddedNumberField(int field, int size) {
0831: if (size < 3) {
0832: // Should use UnpaddedNumberField or TwoDigitNumberField.
0833: throw new IllegalArgumentException();
0834: }
0835: mField = field;
0836: mSize = size;
0837: }
0838:
0839: public int estimateLength() {
0840: return 4;
0841: }
0842:
0843: public void appendTo(StringBuffer buffer, Calendar calendar) {
0844: appendTo(buffer, calendar.get(mField));
0845: }
0846:
0847: public final void appendTo(StringBuffer buffer, int value) {
0848: if (value < 100) {
0849: for (int i = mSize; --i >= 2;) {
0850: buffer.append('0');
0851: }
0852: buffer.append((char) (value / 10 + '0'));
0853: buffer.append((char) (value % 10 + '0'));
0854: } else {
0855: int digits;
0856: if (value < 1000) {
0857: digits = 3;
0858: } else {
0859: digits = (int) (Math.log(value) / LOG_10) + 1;
0860: }
0861: for (int i = mSize; --i >= digits;) {
0862: buffer.append('0');
0863: }
0864: buffer.append(Integer.toString(value));
0865: }
0866: }
0867: }
0868:
0869: private static class TwoDigitNumberField implements NumberRule {
0870: private final int mField;
0871:
0872: TwoDigitNumberField(int field) {
0873: mField = field;
0874: }
0875:
0876: public int estimateLength() {
0877: return 2;
0878: }
0879:
0880: public void appendTo(StringBuffer buffer, Calendar calendar) {
0881: appendTo(buffer, calendar.get(mField));
0882: }
0883:
0884: public final void appendTo(StringBuffer buffer, int value) {
0885: if (value < 100) {
0886: buffer.append((char) (value / 10 + '0'));
0887: buffer.append((char) (value % 10 + '0'));
0888: } else {
0889: buffer.append(Integer.toString(value));
0890: }
0891: }
0892: }
0893:
0894: private static class TwoDigitYearField implements NumberRule {
0895: TwoDigitYearField() {
0896: }
0897:
0898: public int estimateLength() {
0899: return 2;
0900: }
0901:
0902: public void appendTo(StringBuffer buffer, Calendar calendar) {
0903: appendTo(buffer, calendar.get(Calendar.YEAR) % 100);
0904: }
0905:
0906: public final void appendTo(StringBuffer buffer, int value) {
0907: buffer.append((char) (value / 10 + '0'));
0908: buffer.append((char) (value % 10 + '0'));
0909: }
0910: }
0911:
0912: private static class TwoDigitMonthField implements NumberRule {
0913: TwoDigitMonthField() {
0914: }
0915:
0916: public int estimateLength() {
0917: return 2;
0918: }
0919:
0920: public void appendTo(StringBuffer buffer, Calendar calendar) {
0921: appendTo(buffer, calendar.get(Calendar.MONTH) + 1);
0922: }
0923:
0924: public final void appendTo(StringBuffer buffer, int value) {
0925: buffer.append((char) (value / 10 + '0'));
0926: buffer.append((char) (value % 10 + '0'));
0927: }
0928: }
0929:
0930: private static class TwelveHourField implements NumberRule {
0931: private final NumberRule mRule;
0932:
0933: TwelveHourField(NumberRule rule) {
0934: mRule = rule;
0935: }
0936:
0937: public int estimateLength() {
0938: return mRule.estimateLength();
0939: }
0940:
0941: public void appendTo(StringBuffer buffer, Calendar calendar) {
0942: int value = calendar.get(Calendar.HOUR);
0943: if (value == 0) {
0944: value = calendar.getLeastMaximum(Calendar.HOUR) + 1;
0945: }
0946: mRule.appendTo(buffer, value);
0947: }
0948:
0949: public void appendTo(StringBuffer buffer, int value) {
0950: mRule.appendTo(buffer, value);
0951: }
0952: }
0953:
0954: private static class TwentyFourHourField implements NumberRule {
0955: private final NumberRule mRule;
0956:
0957: TwentyFourHourField(NumberRule rule) {
0958: mRule = rule;
0959: }
0960:
0961: public int estimateLength() {
0962: return mRule.estimateLength();
0963: }
0964:
0965: public void appendTo(StringBuffer buffer, Calendar calendar) {
0966: int value = calendar.get(Calendar.HOUR_OF_DAY);
0967: if (value == 0) {
0968: value = calendar.getMaximum(Calendar.HOUR_OF_DAY) + 1;
0969: }
0970: mRule.appendTo(buffer, value);
0971: }
0972:
0973: public void appendTo(StringBuffer buffer, int value) {
0974: mRule.appendTo(buffer, value);
0975: }
0976: }
0977:
0978: private static class TimeZoneRule implements Rule {
0979: private final TimeZone mTimeZone;
0980: private final Locale mLocale;
0981: private final int mStyle;
0982: private final String mStandard;
0983: private final String mDaylight;
0984:
0985: TimeZoneRule(TimeZone timeZone, Locale locale, int style) {
0986: mTimeZone = timeZone;
0987: mLocale = locale;
0988: mStyle = style;
0989:
0990: if (timeZone != null) {
0991: mStandard = getTimeZoneDisplay(timeZone, false, style,
0992: locale);
0993: mDaylight = getTimeZoneDisplay(timeZone, true, style,
0994: locale);
0995: } else {
0996: mStandard = null;
0997: mDaylight = null;
0998: }
0999: }
1000:
1001: public int estimateLength() {
1002: if (mTimeZone != null) {
1003: return Math.max(mStandard.length(), mDaylight.length());
1004: } else if (mStyle == TimeZone.SHORT) {
1005: return 4;
1006: } else {
1007: return 40;
1008: }
1009: }
1010:
1011: public void appendTo(StringBuffer buffer, Calendar calendar) {
1012: TimeZone timeZone;
1013: if ((timeZone = mTimeZone) != null) {
1014: if (timeZone.useDaylightTime()
1015: && calendar.get(Calendar.DST_OFFSET) != 0) {
1016:
1017: buffer.append(mDaylight);
1018: } else {
1019: buffer.append(mStandard);
1020: }
1021: } else {
1022: timeZone = calendar.getTimeZone();
1023: if (timeZone.useDaylightTime()
1024: && calendar.get(Calendar.DST_OFFSET) != 0) {
1025:
1026: buffer.append(getTimeZoneDisplay(timeZone, true,
1027: mStyle, mLocale));
1028: } else {
1029: buffer.append(getTimeZoneDisplay(timeZone, false,
1030: mStyle, mLocale));
1031: }
1032: }
1033: }
1034: }
1035:
1036: private static class TimeZoneDisplayKey {
1037: private final TimeZone mTimeZone;
1038: private final int mStyle;
1039: private final Locale mLocale;
1040:
1041: TimeZoneDisplayKey(TimeZone timeZone, boolean daylight,
1042: int style, Locale locale) {
1043: mTimeZone = timeZone;
1044: if (daylight) {
1045: style |= 0x80000000;
1046: }
1047: mStyle = style;
1048: mLocale = locale;
1049: }
1050:
1051: public int hashCode() {
1052: return mStyle * 31 + mLocale.hashCode();
1053: }
1054:
1055: public boolean equals(Object obj) {
1056: if (this == obj) {
1057: return true;
1058: }
1059: if (obj instanceof TimeZoneDisplayKey) {
1060: TimeZoneDisplayKey other = (TimeZoneDisplayKey) obj;
1061: return mTimeZone.equals(other.mTimeZone)
1062: && mStyle == other.mStyle
1063: && mLocale.equals(other.mLocale);
1064: }
1065: return false;
1066: }
1067: }
1068:
1069: private static class Pair implements Comparable,
1070: java.io.Serializable {
1071: private final Object mObj1;
1072: private final Object mObj2;
1073:
1074: public Pair(Object obj1, Object obj2) {
1075: mObj1 = obj1;
1076: mObj2 = obj2;
1077: }
1078:
1079: public int compareTo(Object obj) {
1080: if (this == obj) {
1081: return 0;
1082: }
1083:
1084: Pair other = (Pair) obj;
1085:
1086: Object a = mObj1;
1087: Object b = other.mObj1;
1088:
1089: firstTest: {
1090: if (a == null) {
1091: if (b != null) {
1092: return 1;
1093: }
1094: // Both a and b are null.
1095: break firstTest;
1096: } else {
1097: if (b == null) {
1098: return -1;
1099: }
1100: }
1101:
1102: int result = ((Comparable) a).compareTo(b);
1103:
1104: if (result != 0) {
1105: return result;
1106: }
1107: }
1108:
1109: a = mObj2;
1110: b = other.mObj2;
1111:
1112: if (a == null) {
1113: if (b != null) {
1114: return 1;
1115: }
1116: // Both a and b are null.
1117: return 0;
1118: } else {
1119: if (b == null) {
1120: return -1;
1121: }
1122: }
1123:
1124: return ((Comparable) a).compareTo(b);
1125: }
1126:
1127: public boolean equals(Object obj) {
1128: if (this == obj) {
1129: return true;
1130: }
1131:
1132: if (!(obj instanceof Pair)) {
1133: return false;
1134: }
1135:
1136: Pair key = (Pair) obj;
1137:
1138: return (mObj1 == null ? key.mObj1 == null : mObj1
1139: .equals(key.mObj1))
1140: && (mObj2 == null ? key.mObj2 == null : mObj2
1141: .equals(key.mObj2));
1142: }
1143:
1144: public int hashCode() {
1145: return (mObj1 == null ? 0 : mObj1.hashCode())
1146: + (mObj2 == null ? 0 : mObj2.hashCode());
1147: }
1148:
1149: public String toString() {
1150: return "[" + mObj1 + ':' + mObj2 + ']';
1151: }
1152: }
1153: }
|