Refactoring FastDateFormat per LANG-462 to use the FormatCache class created for an upcoming DateParser functionality. I've kept FormatCache package-private for now.

git-svn-id: https://svn.apache.org/repos/asf/commons/proper/lang/trunk@1095299 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Henri Yandell 2011-04-20 06:51:16 +00:00
parent 5a7a09256e
commit 9ef322c33c
3 changed files with 274 additions and 326 deletions

View File

@ -23,16 +23,14 @@ import java.text.DateFormatSymbols;
import java.text.FieldPosition; import java.text.FieldPosition;
import java.text.Format; import java.text.Format;
import java.text.ParsePosition; import java.text.ParsePosition;
import java.text.SimpleDateFormat;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Calendar; import java.util.Calendar;
import java.util.Date; import java.util.Date;
import java.util.GregorianCalendar;
import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Locale; import java.util.Locale;
import java.util.Map;
import java.util.TimeZone; import java.util.TimeZone;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import org.apache.commons.lang3.Validate; import org.apache.commons.lang3.Validate;
@ -99,15 +97,15 @@ public class FastDateFormat extends Format {
*/ */
public static final int SHORT = DateFormat.SHORT; public static final int SHORT = DateFormat.SHORT;
//@GuardedBy("this") private static final FormatCache<FastDateFormat> cache= new FormatCache<FastDateFormat>() {
private static String cDefaultPattern; // lazily initialised by getInstance() @Override
protected FastDateFormat createInstance(String pattern, TimeZone timeZone, Locale locale) {
return new FastDateFormat(pattern, timeZone, locale);
}
};
private static final Map<FastDateFormat, FastDateFormat> cInstanceCache = private static ConcurrentMap<TimeZoneDisplayKey, String> cTimeZoneDisplayCache =
new HashMap<FastDateFormat, FastDateFormat>(7); new ConcurrentHashMap<TimeZoneDisplayKey, String>(7);
private static final Map<Object, FastDateFormat> cDateInstanceCache = new HashMap<Object, FastDateFormat>(7);
private static final Map<Object, FastDateFormat> cTimeInstanceCache = new HashMap<Object, FastDateFormat>(7);
private static final Map<Object, FastDateFormat> cDateTimeInstanceCache = new HashMap<Object, FastDateFormat>(7);
private static final Map<Object, String> cTimeZoneDisplayCache = new HashMap<Object, String>(7);
/** /**
* The pattern. * The pattern.
@ -117,18 +115,10 @@ public class FastDateFormat extends Format {
* The time zone. * The time zone.
*/ */
private final TimeZone mTimeZone; private final TimeZone mTimeZone;
/**
* Whether the time zone overrides any on Calendars.
*/
private final boolean mTimeZoneForced;
/** /**
* The locale. * The locale.
*/ */
private final Locale mLocale; private final Locale mLocale;
/**
* Whether the locale overrides the default.
*/
private final boolean mLocaleForced;
/** /**
* The parsed rules. * The parsed rules.
*/ */
@ -146,7 +136,7 @@ public class FastDateFormat extends Format {
* @return a date/time formatter * @return a date/time formatter
*/ */
public static FastDateFormat getInstance() { public static FastDateFormat getInstance() {
return getInstance(getDefaultPattern(), null, null); return cache.getDateTimeInstance(SHORT, SHORT, null, null);
} }
/** /**
@ -159,7 +149,7 @@ public class FastDateFormat extends Format {
* @throws IllegalArgumentException if pattern is invalid * @throws IllegalArgumentException if pattern is invalid
*/ */
public static FastDateFormat getInstance(String pattern) { public static FastDateFormat getInstance(String pattern) {
return getInstance(pattern, null, null); return cache.getInstance(pattern, null, null);
} }
/** /**
@ -174,7 +164,7 @@ public class FastDateFormat extends Format {
* @throws IllegalArgumentException if pattern is invalid * @throws IllegalArgumentException if pattern is invalid
*/ */
public static FastDateFormat getInstance(String pattern, TimeZone timeZone) { public static FastDateFormat getInstance(String pattern, TimeZone timeZone) {
return getInstance(pattern, timeZone, null); return cache.getInstance(pattern, timeZone, null);
} }
/** /**
@ -188,7 +178,7 @@ public class FastDateFormat extends Format {
* @throws IllegalArgumentException if pattern is invalid * @throws IllegalArgumentException if pattern is invalid
*/ */
public static FastDateFormat getInstance(String pattern, Locale locale) { public static FastDateFormat getInstance(String pattern, Locale locale) {
return getInstance(pattern, null, locale); return cache.getInstance(pattern, null, locale);
} }
/** /**
@ -204,15 +194,8 @@ public class FastDateFormat extends Format {
* @throws IllegalArgumentException if pattern is invalid * @throws IllegalArgumentException if pattern is invalid
* or <code>null</code> * or <code>null</code>
*/ */
public static synchronized FastDateFormat getInstance(String pattern, TimeZone timeZone, Locale locale) { public static FastDateFormat getInstance(String pattern, TimeZone timeZone, Locale locale) {
FastDateFormat emptyFormat = new FastDateFormat(pattern, timeZone, locale); return cache.getInstance(pattern, timeZone, locale);
FastDateFormat format = cInstanceCache.get(emptyFormat);
if (format == null) {
format = emptyFormat;
format.init(); // convert shell format into usable one
cInstanceCache.put(format, format); // this is OK!
}
return format;
} }
//----------------------------------------------------------------------- //-----------------------------------------------------------------------
@ -227,7 +210,7 @@ public class FastDateFormat extends Format {
* @since 2.1 * @since 2.1
*/ */
public static FastDateFormat getDateInstance(int style) { public static FastDateFormat getDateInstance(int style) {
return getDateInstance(style, null, null); return cache.getDateTimeInstance(style, null, null, null);
} }
/** /**
@ -242,7 +225,7 @@ public class FastDateFormat extends Format {
* @since 2.1 * @since 2.1
*/ */
public static FastDateFormat getDateInstance(int style, Locale locale) { public static FastDateFormat getDateInstance(int style, Locale locale) {
return getDateInstance(style, null, locale); return cache.getDateTimeInstance(style, null, null, locale);
} }
/** /**
@ -258,8 +241,9 @@ public class FastDateFormat extends Format {
* @since 2.1 * @since 2.1
*/ */
public static FastDateFormat getDateInstance(int style, TimeZone timeZone) { public static FastDateFormat getDateInstance(int style, TimeZone timeZone) {
return getDateInstance(style, timeZone, null); return cache.getDateTimeInstance(style, null, timeZone, null);
} }
/** /**
* <p>Gets a date formatter instance using the specified style, time * <p>Gets a date formatter instance using the specified style, time
* zone and locale.</p> * zone and locale.</p>
@ -272,31 +256,8 @@ public class FastDateFormat extends Format {
* @throws IllegalArgumentException if the Locale has no date * @throws IllegalArgumentException if the Locale has no date
* pattern defined * pattern defined
*/ */
public static synchronized FastDateFormat getDateInstance(int style, TimeZone timeZone, Locale locale) { public static FastDateFormat getDateInstance(int style, TimeZone timeZone, Locale locale) {
Object key = Integer.valueOf(style); return cache.getDateTimeInstance(style, null, timeZone, locale);
if (timeZone != null) {
key = new Pair(key, timeZone);
}
if (locale == null) {
locale = Locale.getDefault();
}
key = new Pair(key, locale);
FastDateFormat format = cDateInstanceCache.get(key);
if (format == null) {
try {
SimpleDateFormat formatter = (SimpleDateFormat) DateFormat.getDateInstance(style, locale);
String pattern = formatter.toPattern();
format = getInstance(pattern, timeZone, locale);
cDateInstanceCache.put(key, format);
} catch (ClassCastException ex) {
throw new IllegalArgumentException("No date pattern for locale: " + locale);
}
}
return format;
} }
//----------------------------------------------------------------------- //-----------------------------------------------------------------------
@ -311,7 +272,7 @@ public class FastDateFormat extends Format {
* @since 2.1 * @since 2.1
*/ */
public static FastDateFormat getTimeInstance(int style) { public static FastDateFormat getTimeInstance(int style) {
return getTimeInstance(style, null, null); return cache.getDateTimeInstance(null, style, null, null);
} }
/** /**
@ -326,7 +287,7 @@ public class FastDateFormat extends Format {
* @since 2.1 * @since 2.1
*/ */
public static FastDateFormat getTimeInstance(int style, Locale locale) { public static FastDateFormat getTimeInstance(int style, Locale locale) {
return getTimeInstance(style, null, locale); return cache.getDateTimeInstance(null, style, null, locale);
} }
/** /**
@ -342,7 +303,7 @@ public class FastDateFormat extends Format {
* @since 2.1 * @since 2.1
*/ */
public static FastDateFormat getTimeInstance(int style, TimeZone timeZone) { public static FastDateFormat getTimeInstance(int style, TimeZone timeZone) {
return getTimeInstance(style, timeZone, null); return cache.getDateTimeInstance(null, style, timeZone, null);
} }
/** /**
@ -357,32 +318,8 @@ public class FastDateFormat extends Format {
* @throws IllegalArgumentException if the Locale has no time * @throws IllegalArgumentException if the Locale has no time
* pattern defined * pattern defined
*/ */
public static synchronized FastDateFormat getTimeInstance(int style, TimeZone timeZone, Locale locale) { public static FastDateFormat getTimeInstance(int style, TimeZone timeZone, Locale locale) {
Object key = Integer.valueOf(style); return cache.getDateTimeInstance(null, style, timeZone, locale);
if (timeZone != null) {
key = new Pair(key, timeZone);
}
if (locale != null) {
key = new Pair(key, locale);
}
FastDateFormat format = cTimeInstanceCache.get(key);
if (format == null) {
if (locale == null) {
locale = Locale.getDefault();
}
try {
SimpleDateFormat formatter = (SimpleDateFormat) DateFormat.getTimeInstance(style, locale);
String pattern = formatter.toPattern();
format = getInstance(pattern, timeZone, locale);
cTimeInstanceCache.put(key, format);
} catch (ClassCastException ex) {
throw new IllegalArgumentException("No date pattern for locale: " + locale);
}
}
return format;
} }
//----------------------------------------------------------------------- //-----------------------------------------------------------------------
@ -397,9 +334,8 @@ public class FastDateFormat extends Format {
* pattern defined * pattern defined
* @since 2.1 * @since 2.1
*/ */
public static FastDateFormat getDateTimeInstance( public static FastDateFormat getDateTimeInstance(int dateStyle, int timeStyle) {
int dateStyle, int timeStyle) { return cache.getDateTimeInstance(dateStyle, timeStyle, null, null);
return getDateTimeInstance(dateStyle, timeStyle, null, null);
} }
/** /**
@ -414,9 +350,8 @@ public class FastDateFormat extends Format {
* pattern defined * pattern defined
* @since 2.1 * @since 2.1
*/ */
public static FastDateFormat getDateTimeInstance( public static FastDateFormat getDateTimeInstance(int dateStyle, int timeStyle, Locale locale) {
int dateStyle, int timeStyle, Locale locale) { return cache.getDateTimeInstance(dateStyle, timeStyle, null, locale);
return getDateTimeInstance(dateStyle, timeStyle, null, locale);
} }
/** /**
@ -432,8 +367,7 @@ public class FastDateFormat extends Format {
* pattern defined * pattern defined
* @since 2.1 * @since 2.1
*/ */
public static FastDateFormat getDateTimeInstance( public static FastDateFormat getDateTimeInstance(int dateStyle, int timeStyle, TimeZone timeZone) {
int dateStyle, int timeStyle, TimeZone timeZone) {
return getDateTimeInstance(dateStyle, timeStyle, timeZone, null); return getDateTimeInstance(dateStyle, timeStyle, timeZone, null);
} }
/** /**
@ -449,32 +383,9 @@ public class FastDateFormat extends Format {
* @throws IllegalArgumentException if the Locale has no date/time * @throws IllegalArgumentException if the Locale has no date/time
* pattern defined * pattern defined
*/ */
public static synchronized FastDateFormat getDateTimeInstance(int dateStyle, int timeStyle, TimeZone timeZone, public static FastDateFormat getDateTimeInstance(
Locale locale) { int dateStyle, int timeStyle, TimeZone timeZone, Locale locale) {
return cache.getDateTimeInstance(dateStyle, timeStyle, timeZone, locale);
Object key = new Pair(Integer.valueOf(dateStyle), Integer.valueOf(timeStyle));
if (timeZone != null) {
key = new Pair(key, timeZone);
}
if (locale == null) {
locale = Locale.getDefault();
}
key = new Pair(key, locale);
FastDateFormat format = cDateTimeInstanceCache.get(key);
if (format == null) {
try {
SimpleDateFormat formatter = (SimpleDateFormat) DateFormat.getDateTimeInstance(dateStyle, timeStyle,
locale);
String pattern = formatter.toPattern();
format = getInstance(pattern, timeZone, locale);
cDateTimeInstanceCache.put(key, format);
} catch (ClassCastException ex) {
throw new IllegalArgumentException("No date time pattern for locale: " + locale);
}
}
return format;
} }
//----------------------------------------------------------------------- //-----------------------------------------------------------------------
@ -488,68 +399,42 @@ public class FastDateFormat extends Format {
* @param locale the locale to use * @param locale the locale to use
* @return the textual name of the time zone * @return the textual name of the time zone
*/ */
static synchronized String getTimeZoneDisplay(TimeZone tz, boolean daylight, int style, Locale locale) { static String getTimeZoneDisplay(TimeZone tz, boolean daylight, int style, Locale locale) {
Object key = new TimeZoneDisplayKey(tz, daylight, style, locale); TimeZoneDisplayKey key = new TimeZoneDisplayKey(tz, daylight, style, locale);
String value = cTimeZoneDisplayCache.get(key); String value = cTimeZoneDisplayCache.get(key);
if (value == null) { if (value == null) {
// This is a very slow call, so cache the results. // This is a very slow call, so cache the results.
value = tz.getDisplayName(daylight, style, locale); value = tz.getDisplayName(daylight, style, locale);
cTimeZoneDisplayCache.put(key, value); String prior = cTimeZoneDisplayCache.putIfAbsent(key, value);
if (prior != null) {
value= prior;
}
} }
return value; return value;
} }
/**
* <p>Gets the default pattern.</p>
*
* @return the default pattern
*/
private static synchronized String getDefaultPattern() {
if (cDefaultPattern == null) {
cDefaultPattern = new SimpleDateFormat().toPattern();
}
return cDefaultPattern;
}
// Constructor // Constructor
//----------------------------------------------------------------------- //-----------------------------------------------------------------------
/** /**
* <p>Constructs a new FastDateFormat.</p> * <p>Constructs a new FastDateFormat.</p>
* *
* @param pattern {@link java.text.SimpleDateFormat} compatible * @param pattern {@link java.text.SimpleDateFormat} compatible pattern
* pattern * @param timeZone non-null time zone to use
* @param timeZone time zone to use, <code>null</code> means use * @param locale non-null locale to use
* default for <code>Date</code> and value within for * @throws NullPointerException if pattern, timeZone, or locale is null.
* <code>Calendar</code>
* @param locale locale, <code>null</code> means use system
* default
* @throws IllegalArgumentException if pattern is invalid or
* <code>null</code>
*/ */
protected FastDateFormat(String pattern, TimeZone timeZone, Locale locale) { protected FastDateFormat(String pattern, TimeZone timeZone, Locale locale) {
super();
if (pattern == null) {
throw new IllegalArgumentException("The pattern must not be null");
}
mPattern = pattern; mPattern = pattern;
mTimeZoneForced = (timeZone != null);
if (timeZone == null) {
timeZone = TimeZone.getDefault();
}
mTimeZone = timeZone; mTimeZone = timeZone;
mLocaleForced = (locale != null);
if (locale == null) {
locale = Locale.getDefault();
}
mLocale = locale; mLocale = locale;
init();
} }
/** /**
* <p>Initializes the instance for first use.</p> * <p>Initializes the instance for first use.</p>
*/ */
protected void init() { private void init() {
List<Rule> rulesList = parsePattern(); List<Rule> rulesList = parsePattern();
mRules = rulesList.toArray(new Rule[rulesList.size()]); mRules = rulesList.toArray(new Rule[rulesList.size()]);
@ -662,9 +547,9 @@ public class FastDateFormat extends Format {
break; break;
case 'z': // time zone (text) case 'z': // time zone (text)
if (tokenLen >= 4) { if (tokenLen >= 4) {
rule = new TimeZoneNameRule(mTimeZone, mTimeZoneForced, mLocale, TimeZone.LONG); rule = new TimeZoneNameRule(mTimeZone, mLocale, TimeZone.LONG);
} else { } else {
rule = new TimeZoneNameRule(mTimeZone, mTimeZoneForced, mLocale, TimeZone.SHORT); rule = new TimeZoneNameRule(mTimeZone, mLocale, TimeZone.SHORT);
} }
break; break;
case 'Z': // time zone (value) case 'Z': // time zone (value)
@ -812,7 +697,7 @@ public class FastDateFormat extends Format {
* @return the formatted string * @return the formatted string
*/ */
public String format(Date date) { public String format(Date date) {
Calendar c = new GregorianCalendar(mTimeZone, mLocale); Calendar c = Calendar.getInstance(mTimeZone, mLocale);
c.setTime(date); c.setTime(date);
return applyRules(c, new StringBuffer(mMaxLengthEstimate)).toString(); return applyRules(c, new StringBuffer(mMaxLengthEstimate)).toString();
} }
@ -849,7 +734,7 @@ public class FastDateFormat extends Format {
* @return the specified string buffer * @return the specified string buffer
*/ */
public StringBuffer format(Date date, StringBuffer buf) { public StringBuffer format(Date date, StringBuffer buf) {
Calendar c = new GregorianCalendar(mTimeZone); Calendar c = Calendar.getInstance(mTimeZone, mLocale);
c.setTime(date); c.setTime(date);
return applyRules(c, buf); return applyRules(c, buf);
} }
@ -863,11 +748,6 @@ public class FastDateFormat extends Format {
* @return the specified string buffer * @return the specified string buffer
*/ */
public StringBuffer format(Calendar calendar, StringBuffer buf) { public StringBuffer format(Calendar calendar, StringBuffer buf) {
if (mTimeZoneForced) {
calendar.getTimeInMillis(); /// LANG-538
calendar = (Calendar) calendar.clone();
calendar.setTimeZone(mTimeZone);
}
return applyRules(calendar, buf); return applyRules(calendar, buf);
} }
@ -880,10 +760,8 @@ public class FastDateFormat extends Format {
* @return the specified string buffer * @return the specified string buffer
*/ */
protected StringBuffer applyRules(Calendar calendar, StringBuffer buf) { protected StringBuffer applyRules(Calendar calendar, StringBuffer buf) {
Rule[] rules = mRules; for (Rule rule : mRules) {
int len = mRules.length; rule.appendTo(buf, calendar);
for (int i = 0; i < len; i++) {
rules[i].appendTo(buf, calendar);
} }
return buf; return buf;
} }
@ -918,10 +796,7 @@ public class FastDateFormat extends Format {
/** /**
* <p>Gets the time zone used by this formatter.</p> * <p>Gets the time zone used by this formatter.</p>
* *
* <p>This zone is always used for <code>Date</code> formatting. * <p>This zone is always used for <code>Date</code> formatting. </p>
* If a <code>Calendar</code> is passed in to be formatted, the
* time zone on that may be used depending on
* {@link #getTimeZoneOverridesCalendar()}.</p>
* *
* @return the time zone * @return the time zone
*/ */
@ -929,17 +804,6 @@ public class FastDateFormat extends Format {
return mTimeZone; return mTimeZone;
} }
/**
* <p>Returns <code>true</code> if the time zone of the
* calendar overrides the time zone of the formatter.</p>
*
* @return <code>true</code> if time zone of formatter
* overridden for calendars
*/
public boolean getTimeZoneOverridesCalendar() {
return mTimeZoneForced;
}
/** /**
* <p>Gets the locale used by this formatter.</p> * <p>Gets the locale used by this formatter.</p>
* *
@ -976,16 +840,9 @@ public class FastDateFormat extends Format {
return false; return false;
} }
FastDateFormat other = (FastDateFormat) obj; FastDateFormat other = (FastDateFormat) obj;
if ( return mPattern.equals(other.mPattern)
(mPattern == other.mPattern || mPattern.equals(other.mPattern)) && && mTimeZone.equals(other.mTimeZone)
(mTimeZone == other.mTimeZone || mTimeZone.equals(other.mTimeZone)) && && mLocale.equals(other.mLocale);
(mLocale == other.mLocale || mLocale.equals(other.mLocale)) &&
(mTimeZoneForced == other.mTimeZoneForced) &&
(mLocaleForced == other.mLocaleForced)
) {
return true;
}
return false;
} }
/** /**
@ -995,13 +852,7 @@ public class FastDateFormat extends Format {
*/ */
@Override @Override
public int hashCode() { public int hashCode() {
int total = 0; return mPattern.hashCode() + 13 * (mTimeZone.hashCode() + 13 * mLocale.hashCode());
total += mPattern.hashCode();
total += mTimeZone.hashCode();
total += (mTimeZoneForced ? 1 : 0);
total += mLocale.hashCode();
total += (mLocaleForced ? 1 : 0);
return total;
} }
/** /**
@ -1517,9 +1368,6 @@ public class FastDateFormat extends Format {
*/ */
private static class TimeZoneNameRule implements Rule { private static class TimeZoneNameRule implements Rule {
private final TimeZone mTimeZone; private final TimeZone mTimeZone;
private final boolean mTimeZoneForced;
private final Locale mLocale;
private final int mStyle;
private final String mStandard; private final String mStandard;
private final String mDaylight; private final String mDaylight;
@ -1527,55 +1375,31 @@ public class FastDateFormat extends Format {
* Constructs an instance of <code>TimeZoneNameRule</code> with the specified properties. * Constructs an instance of <code>TimeZoneNameRule</code> with the specified properties.
* *
* @param timeZone the time zone * @param timeZone the time zone
* @param timeZoneForced if <code>true</code> the time zone is forced into standard and daylight
* @param locale the locale * @param locale the locale
* @param style the style * @param style the style
*/ */
TimeZoneNameRule(TimeZone timeZone, boolean timeZoneForced, Locale locale, int style) { TimeZoneNameRule(TimeZone timeZone, Locale locale, int style) {
mTimeZone = timeZone; mTimeZone = timeZone;
mTimeZoneForced = timeZoneForced;
mLocale = locale;
mStyle = style;
if (timeZoneForced) { mStandard = getTimeZoneDisplay(timeZone, false, style, locale);
mStandard = getTimeZoneDisplay(timeZone, false, style, locale); mDaylight = getTimeZoneDisplay(timeZone, true, style, locale);
mDaylight = getTimeZoneDisplay(timeZone, true, style, locale);
} else {
mStandard = null;
mDaylight = null;
}
} }
/** /**
* {@inheritDoc} * {@inheritDoc}
*/ */
public int estimateLength() { public int estimateLength() {
if (mTimeZoneForced) { return Math.max(mStandard.length(), mDaylight.length());
return Math.max(mStandard.length(), mDaylight.length());
} else if (mStyle == TimeZone.SHORT) {
return 4;
} else {
return 40;
}
} }
/** /**
* {@inheritDoc} * {@inheritDoc}
*/ */
public void appendTo(StringBuffer buffer, Calendar calendar) { public void appendTo(StringBuffer buffer, Calendar calendar) {
if (mTimeZoneForced) { if (mTimeZone.useDaylightTime() && calendar.get(Calendar.DST_OFFSET) != 0) {
if (mTimeZone.useDaylightTime() && calendar.get(Calendar.DST_OFFSET) != 0) { buffer.append(mDaylight);
buffer.append(mDaylight);
} else {
buffer.append(mStandard);
}
} else { } else {
TimeZone timeZone = calendar.getTimeZone(); buffer.append(mStandard);
if (timeZone.useDaylightTime() && calendar.get(Calendar.DST_OFFSET) != 0) {
buffer.append(getTimeZoneDisplay(timeZone, true, mStyle, mLocale));
} else {
buffer.append(getTimeZoneDisplay(timeZone, false, mStyle, mLocale));
}
} }
} }
} }
@ -1665,7 +1489,7 @@ public class FastDateFormat extends Format {
*/ */
@Override @Override
public int hashCode() { public int hashCode() {
return mStyle * 31 + mLocale.hashCode(); return (mStyle * 31 + mLocale.hashCode() ) * 31 + mTimeZone.hashCode();
} }
/** /**
@ -1686,67 +1510,4 @@ public class FastDateFormat extends Format {
return false; return false;
} }
} }
// ----------------------------------------------------------------------
/**
* <p>Helper class for creating compound objects.</p>
*
* <p>One use for this class is to create a hashtable key
* out of multiple objects.</p>
*/
private static class Pair {
private final Object mObj1;
private final Object mObj2;
/**
* Constructs an instance of <code>Pair</code> to hold the specified objects.
* @param obj1 one object in the pair
* @param obj2 second object in the pair
*/
public Pair(Object obj1, Object obj2) {
mObj1 = obj1;
mObj2 = obj2;
}
/**
* {@inheritDoc}
*/
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (!(obj instanceof Pair)) {
return false;
}
Pair key = (Pair)obj;
return
(mObj1 == null ?
key.mObj1 == null : mObj1.equals(key.mObj1)) &&
(mObj2 == null ?
key.mObj2 == null : mObj2.equals(key.mObj2));
}
/**
* {@inheritDoc}
*/
@Override
public int hashCode() {
return
(mObj1 == null ? 0 : mObj1.hashCode()) +
(mObj2 == null ? 0 : mObj2.hashCode());
}
/**
* {@inheritDoc}
*/
@Override
public String toString() {
return "[" + mObj1 + ':' + mObj2 + ']';
}
}
} }

View File

@ -0,0 +1,201 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.commons.lang3.time;
import java.text.DateFormat;
import java.text.Format;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Locale;
import java.util.TimeZone;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
/**
* <p>FormatCache is a cache and factory for {@link Format}s.</p>
*
* @since 3.0
* @version $Id: FormatCache 892161 2009-12-18 07:21:10Z $
*/
abstract class FormatCache<F extends Format> {
/**
* No date or no time. Used in same parameters as DateFormat.SHORT or DateFormat.LONG
*/
static final int NONE= -1;
private final ConcurrentMap<MultipartKey, F> cInstanceCache
= new ConcurrentHashMap<MultipartKey, F>(7);
private final ConcurrentMap<MultipartKey, String> cDateTimeInstanceCache
= new ConcurrentHashMap<MultipartKey, String>(7);
/**
* <p>Gets a formatter instance using the default pattern in the
* default timezone and locale.</p>
*
* @return a date/time formatter
*/
public F getInstance() {
return getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT, TimeZone.getDefault(), Locale.getDefault());
}
/**
* <p>Gets a formatter instance using the specified pattern, time zone
* and locale.</p>
*
* @param pattern {@link java.text.SimpleDateFormat} compatible
* pattern
* @param timeZone the non-null time zone
* @param locale the non-null locale
* @return a pattern based date/time formatter
* @throws IllegalArgumentException if pattern is invalid
* or <code>null</code>
*/
public F getInstance(String pattern, TimeZone timeZone, Locale locale) {
if (pattern == null) {
throw new NullPointerException("pattern must not be null");
}
if (timeZone == null) {
timeZone = TimeZone.getDefault();
}
if (locale == null) {
locale = Locale.getDefault();
}
MultipartKey key = new MultipartKey(pattern, timeZone, locale);
F format = cInstanceCache.get(key);
if (format == null) {
format = createInstance(pattern, timeZone, locale);
F previousValue= cInstanceCache.putIfAbsent(key, format);
if (previousValue != null) {
// another thread snuck in and did the same work
// we should return the instance that is in ConcurrentMap
format= previousValue;
}
}
return format;
}
/**
* <p>Create a format instance using the specified pattern, time zone
* and locale.</p>
*
* @param pattern {@link java.text.SimpleDateFormat} compatible pattern, this will not be null.
* @param timeZone time zone, this will not be null.
* @param locale locale, this will not be null.
* @return a pattern based date/time formatter
* @throws IllegalArgumentException if pattern is invalid
* or <code>null</code>
*/
abstract protected F createInstance(String pattern, TimeZone timeZone, Locale locale);
/**
* <p>Gets a date/time formatter instance using the specified style,
* time zone and locale.</p>
*
* @param dateStyle date style: FULL, LONG, MEDIUM, or SHORT
* @param timeStyle time style: FULL, LONG, MEDIUM, or SHORT
* @param timeZone optional time zone, overrides time zone of
* formatted date
* @param locale optional locale, overrides system locale
* @return a localized standard date/time formatter
* @throws IllegalArgumentException if the Locale has no date/time
* pattern defined
*/
public F getDateTimeInstance(Integer dateStyle, Integer timeStyle, TimeZone timeZone, Locale locale) {
if (locale == null) {
locale = Locale.getDefault();
}
MultipartKey key = new MultipartKey(dateStyle, timeStyle, locale);
String pattern = cDateTimeInstanceCache.get(key);
if (pattern == null) {
try {
DateFormat formatter;
if (dateStyle == null) {
formatter = DateFormat.getTimeInstance(timeStyle, locale);
}
else if (timeStyle == null) {
formatter = DateFormat.getDateInstance(dateStyle, locale);
}
else {
formatter = DateFormat.getDateTimeInstance(dateStyle, timeStyle, locale);
}
pattern = ((SimpleDateFormat)formatter).toPattern();
String previous = cDateTimeInstanceCache.putIfAbsent(key, pattern);
if (previous != null) {
// even though it doesn't matter if another thread put the pattern
// it's still good practice to return the String instance that is
// actually in the ConcurrentMap
pattern= previous;
}
} catch (ClassCastException ex) {
throw new IllegalArgumentException("No date time pattern for locale: " + locale);
}
}
return getInstance(pattern, timeZone, locale);
}
// ----------------------------------------------------------------------
/**
* <p>Helper class to hold multi-part Map keys</p>
*/
private static class MultipartKey {
private final Object[] keys;
private int hashCode;
/**
* Constructs an instance of <code>MultipartKey</code> to hold the specified objects.
* @param keys the set of objects that make up the key. Each key may be null.
*/
public MultipartKey(Object... keys) {
this.keys = keys;
}
/**
* {@inheritDoc}
*/
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if ( obj instanceof MultipartKey == false ) {
return false;
}
return Arrays.equals(keys, ((MultipartKey)obj).keys);
}
/**
* {@inheritDoc}
*/
@Override
public int hashCode() {
if(hashCode==0) {
int rc= 0;
for(Object key : keys) {
if(key!=null) {
rc= rc*7 + key.hashCode();
}
}
hashCode= rc;
}
return hashCode;
}
}
}

View File

@ -56,8 +56,6 @@ public class FastDateFormatTest extends TestCase {
assertEquals("MM/DD/yyyy", format1.getPattern()); assertEquals("MM/DD/yyyy", format1.getPattern());
assertEquals(TimeZone.getDefault(), format1.getTimeZone()); assertEquals(TimeZone.getDefault(), format1.getTimeZone());
assertEquals(TimeZone.getDefault(), format2.getTimeZone()); assertEquals(TimeZone.getDefault(), format2.getTimeZone());
assertEquals(false, format1.getTimeZoneOverridesCalendar());
assertEquals(false, format2.getTimeZoneOverridesCalendar());
} }
public void test_getInstance_String_TimeZone() { public void test_getInstance_String_TimeZone() {
@ -77,9 +75,7 @@ public class FastDateFormatTest extends TestCase {
assertTrue(format1 != format2); // -- junit 3.8 version -- assertFalse(format1 == format2); assertTrue(format1 != format2); // -- junit 3.8 version -- assertFalse(format1 == format2);
assertEquals(TimeZone.getTimeZone("Atlantic/Reykjavik"), format1.getTimeZone()); assertEquals(TimeZone.getTimeZone("Atlantic/Reykjavik"), format1.getTimeZone());
assertEquals(true, format1.getTimeZoneOverridesCalendar());
assertEquals(TimeZone.getDefault(), format2.getTimeZone()); assertEquals(TimeZone.getDefault(), format2.getTimeZone());
assertEquals(false, format2.getTimeZoneOverridesCalendar());
assertSame(format3, format4); assertSame(format3, format4);
assertTrue(format3 != format5); // -- junit 3.8 version -- assertFalse(format3 == format5); assertTrue(format3 != format5); // -- junit 3.8 version -- assertFalse(format3 == format5);
assertTrue(format4 != format6); // -- junit 3.8 version -- assertFalse(format3 == format5); assertTrue(format4 != format6); // -- junit 3.8 version -- assertFalse(format3 == format5);
@ -164,9 +160,6 @@ public class FastDateFormatTest extends TestCase {
assertEquals(TimeZone.getTimeZone("Atlantic/Reykjavik"), format1.getTimeZone()); assertEquals(TimeZone.getTimeZone("Atlantic/Reykjavik"), format1.getTimeZone());
assertEquals(TimeZone.getDefault(), format2.getTimeZone()); assertEquals(TimeZone.getDefault(), format2.getTimeZone());
assertEquals(TimeZone.getDefault(), format3.getTimeZone()); assertEquals(TimeZone.getDefault(), format3.getTimeZone());
assertEquals(true, format1.getTimeZoneOverridesCalendar());
assertEquals(false, format2.getTimeZoneOverridesCalendar());
assertEquals(true, format3.getTimeZoneOverridesCalendar());
assertEquals(Locale.GERMANY, format1.getLocale()); assertEquals(Locale.GERMANY, format1.getLocale());
assertEquals(Locale.GERMANY, format2.getLocale()); assertEquals(Locale.GERMANY, format2.getLocale());
assertEquals(Locale.GERMANY, format3.getLocale()); assertEquals(Locale.GERMANY, format3.getLocale());
@ -183,8 +176,6 @@ public class FastDateFormatTest extends TestCase {
try { try {
Locale.setDefault(Locale.US); Locale.setDefault(Locale.US);
TimeZone.setDefault(TimeZone.getTimeZone("America/New_York")); TimeZone.setDefault(TimeZone.getTimeZone("America/New_York"));
FastDateFormat fdf = null;
SimpleDateFormat sdf = null;
GregorianCalendar cal1 = new GregorianCalendar(2003, 0, 10, 15, 33, 20); GregorianCalendar cal1 = new GregorianCalendar(2003, 0, 10, 15, 33, 20);
GregorianCalendar cal2 = new GregorianCalendar(2003, 6, 10, 9, 00, 00); GregorianCalendar cal2 = new GregorianCalendar(2003, 6, 10, 9, 00, 00);
@ -193,8 +184,8 @@ public class FastDateFormatTest extends TestCase {
long millis1 = date1.getTime(); long millis1 = date1.getTime();
long millis2 = date2.getTime(); long millis2 = date2.getTime();
fdf = FastDateFormat.getInstance("yyyy-MM-dd'T'HH:mm:ss"); FastDateFormat fdf = FastDateFormat.getInstance("yyyy-MM-dd'T'HH:mm:ss");
sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss"); SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
assertEquals(sdf.format(date1), fdf.format(date1)); assertEquals(sdf.format(date1), fdf.format(date1));
assertEquals("2003-01-10T15:33:20", fdf.format(date1)); assertEquals("2003-01-10T15:33:20", fdf.format(date1));
assertEquals("2003-01-10T15:33:20", fdf.format(cal1)); assertEquals("2003-01-10T15:33:20", fdf.format(cal1));
@ -208,7 +199,6 @@ public class FastDateFormatTest extends TestCase {
assertEquals("-0500", fdf.format(cal1)); assertEquals("-0500", fdf.format(cal1));
assertEquals("-0500", fdf.format(millis1)); assertEquals("-0500", fdf.format(millis1));
fdf = FastDateFormat.getInstance("Z");
assertEquals("-0400", fdf.format(date2)); assertEquals("-0400", fdf.format(date2));
assertEquals("-0400", fdf.format(cal2)); assertEquals("-0400", fdf.format(cal2));
assertEquals("-0400", fdf.format(millis2)); assertEquals("-0400", fdf.format(millis2));
@ -218,7 +208,6 @@ public class FastDateFormatTest extends TestCase {
assertEquals("-05:00", fdf.format(cal1)); assertEquals("-05:00", fdf.format(cal1));
assertEquals("-05:00", fdf.format(millis1)); assertEquals("-05:00", fdf.format(millis1));
fdf = FastDateFormat.getInstance("ZZ");
assertEquals("-04:00", fdf.format(date2)); assertEquals("-04:00", fdf.format(date2));
assertEquals("-04:00", fdf.format(cal2)); assertEquals("-04:00", fdf.format(cal2));
assertEquals("-04:00", fdf.format(millis2)); assertEquals("-04:00", fdf.format(millis2));
@ -228,14 +217,13 @@ public class FastDateFormatTest extends TestCase {
fdf = FastDateFormat.getInstance(pattern); fdf = FastDateFormat.getInstance(pattern);
sdf = new SimpleDateFormat(pattern); sdf = new SimpleDateFormat(pattern);
assertEquals(sdf.format(date1), fdf.format(date1)); assertEquals(sdf.format(date1), fdf.format(date1));
assertEquals(sdf.format(date2), fdf.format(date2)); assertEquals(sdf.format(date2), fdf.format(date2));
} finally { } finally {
Locale.setDefault(realDefaultLocale); Locale.setDefault(realDefaultLocale);
TimeZone.setDefault(realDefaultZone); TimeZone.setDefault(realDefaultZone);
} }
} }
/** /**
* Test case for {@link FastDateFormat#getDateInstance(int, java.util.Locale)}. * Test case for {@link FastDateFormat#getDateInstance(int, java.util.Locale)}.
*/ */
@ -283,7 +271,6 @@ public class FastDateFormatTest extends TestCase {
* testLowYearPadding showed that the date was buggy * testLowYearPadding showed that the date was buggy
* This test confirms it, getting 366 back as a date * This test confirms it, getting 366 back as a date
*/ */
// TODO: Fix this problem
public void testSimpleDate() { public void testSimpleDate() {
Calendar cal = Calendar.getInstance(); Calendar cal = Calendar.getInstance();
FastDateFormat format = FastDateFormat.getInstance("yyyy/MM/dd"); FastDateFormat format = FastDateFormat.getInstance("yyyy/MM/dd");
@ -308,8 +295,6 @@ public class FastDateFormatTest extends TestCase {
} }
public void testLang538() { public void testLang538() {
final String dateTime = "2009-10-16T16:42:16.000Z";
// more commonly constructed with: cal = new GregorianCalendar(2009, 9, 16, 8, 42, 16) // more commonly constructed with: cal = new GregorianCalendar(2009, 9, 16, 8, 42, 16)
// for the unit test to work in any time zone, constructing with GMT-8 rather than default locale time zone // for the unit test to work in any time zone, constructing with GMT-8 rather than default locale time zone
GregorianCalendar cal = new GregorianCalendar(TimeZone.getTimeZone("GMT-8")); GregorianCalendar cal = new GregorianCalendar(TimeZone.getTimeZone("GMT-8"));
@ -317,7 +302,8 @@ public class FastDateFormatTest extends TestCase {
cal.set(2009, 9, 16, 8, 42, 16); cal.set(2009, 9, 16, 8, 42, 16);
FastDateFormat format = FastDateFormat.getInstance("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", TimeZone.getTimeZone("GMT")); FastDateFormat format = FastDateFormat.getInstance("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", TimeZone.getTimeZone("GMT"));
assertEquals("dateTime", dateTime, format.format(cal)); assertEquals("dateTime", "2009-10-16T16:42:16.000Z", format.format(cal.getTime()));
assertEquals("dateTime", "2009-10-16T08:42:16.000Z", format.format(cal));
} }
public void testLang645() { public void testLang645() {