diff --git a/src/main/java/org/apache/commons/lang3/time/FastDateFormat.java b/src/main/java/org/apache/commons/lang3/time/FastDateFormat.java index 666db6ee4..c59558b73 100644 --- a/src/main/java/org/apache/commons/lang3/time/FastDateFormat.java +++ b/src/main/java/org/apache/commons/lang3/time/FastDateFormat.java @@ -23,16 +23,14 @@ import java.text.DateFormatSymbols; import java.text.FieldPosition; import java.text.Format; import java.text.ParsePosition; -import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Calendar; import java.util.Date; -import java.util.GregorianCalendar; -import java.util.HashMap; import java.util.List; import java.util.Locale; -import java.util.Map; import java.util.TimeZone; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; import org.apache.commons.lang3.Validate; @@ -99,15 +97,15 @@ public class FastDateFormat extends Format { */ public static final int SHORT = DateFormat.SHORT; - //@GuardedBy("this") - private static String cDefaultPattern; // lazily initialised by getInstance() + private static final FormatCache cache= new FormatCache() { + @Override + protected FastDateFormat createInstance(String pattern, TimeZone timeZone, Locale locale) { + return new FastDateFormat(pattern, timeZone, locale); + } + }; - private static final Map cInstanceCache = - new HashMap(7); - private static final Map cDateInstanceCache = new HashMap(7); - private static final Map cTimeInstanceCache = new HashMap(7); - private static final Map cDateTimeInstanceCache = new HashMap(7); - private static final Map cTimeZoneDisplayCache = new HashMap(7); + private static ConcurrentMap cTimeZoneDisplayCache = + new ConcurrentHashMap(7); /** * The pattern. @@ -117,18 +115,10 @@ public class FastDateFormat extends Format { * The time zone. */ private final TimeZone mTimeZone; - /** - * Whether the time zone overrides any on Calendars. - */ - private final boolean mTimeZoneForced; /** * The locale. */ private final Locale mLocale; - /** - * Whether the locale overrides the default. - */ - private final boolean mLocaleForced; /** * The parsed rules. */ @@ -146,7 +136,7 @@ public class FastDateFormat extends Format { * @return a date/time formatter */ 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 */ 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 */ 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 */ 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 * or null */ - public static synchronized FastDateFormat getInstance(String pattern, TimeZone timeZone, Locale locale) { - FastDateFormat emptyFormat = new FastDateFormat(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; + public static FastDateFormat getInstance(String pattern, TimeZone timeZone, Locale locale) { + return cache.getInstance(pattern, timeZone, locale); } //----------------------------------------------------------------------- @@ -227,7 +210,7 @@ public class FastDateFormat extends Format { * @since 2.1 */ 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 */ 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 */ public static FastDateFormat getDateInstance(int style, TimeZone timeZone) { - return getDateInstance(style, timeZone, null); + return cache.getDateTimeInstance(style, null, timeZone, null); } + /** *

Gets a date formatter instance using the specified style, time * zone and locale.

@@ -272,31 +256,8 @@ public class FastDateFormat extends Format { * @throws IllegalArgumentException if the Locale has no date * pattern defined */ - public static synchronized FastDateFormat getDateInstance(int style, TimeZone timeZone, Locale locale) { - Object key = Integer.valueOf(style); - 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; + public static FastDateFormat getDateInstance(int style, TimeZone timeZone, Locale locale) { + return cache.getDateTimeInstance(style, null, timeZone, locale); } //----------------------------------------------------------------------- @@ -311,7 +272,7 @@ public class FastDateFormat extends Format { * @since 2.1 */ 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 */ 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 */ 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 * pattern defined */ - public static synchronized FastDateFormat getTimeInstance(int style, TimeZone timeZone, Locale locale) { - Object key = Integer.valueOf(style); - 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; + public static FastDateFormat getTimeInstance(int style, TimeZone timeZone, Locale locale) { + return cache.getDateTimeInstance(null, style, timeZone, locale); } //----------------------------------------------------------------------- @@ -397,9 +334,8 @@ public class FastDateFormat extends Format { * pattern defined * @since 2.1 */ - public static FastDateFormat getDateTimeInstance( - int dateStyle, int timeStyle) { - return getDateTimeInstance(dateStyle, timeStyle, null, null); + public static FastDateFormat getDateTimeInstance(int dateStyle, int timeStyle) { + return cache.getDateTimeInstance(dateStyle, timeStyle, null, null); } /** @@ -414,9 +350,8 @@ public class FastDateFormat extends Format { * pattern defined * @since 2.1 */ - public static FastDateFormat getDateTimeInstance( - int dateStyle, int timeStyle, Locale locale) { - return getDateTimeInstance(dateStyle, timeStyle, null, locale); + public static FastDateFormat getDateTimeInstance(int dateStyle, int timeStyle, Locale locale) { + return cache.getDateTimeInstance(dateStyle, timeStyle, null, locale); } /** @@ -432,8 +367,7 @@ public class FastDateFormat extends Format { * pattern defined * @since 2.1 */ - public static FastDateFormat getDateTimeInstance( - int dateStyle, int timeStyle, TimeZone timeZone) { + public static FastDateFormat getDateTimeInstance(int dateStyle, int timeStyle, TimeZone timeZone) { return getDateTimeInstance(dateStyle, timeStyle, timeZone, null); } /** @@ -449,32 +383,9 @@ public class FastDateFormat extends Format { * @throws IllegalArgumentException if the Locale has no date/time * pattern defined */ - public static synchronized FastDateFormat getDateTimeInstance(int dateStyle, int timeStyle, TimeZone timeZone, - Locale 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; + public static FastDateFormat getDateTimeInstance( + int dateStyle, int timeStyle, TimeZone timeZone, Locale locale) { + return cache.getDateTimeInstance(dateStyle, timeStyle, timeZone, locale); } //----------------------------------------------------------------------- @@ -488,68 +399,42 @@ public class FastDateFormat extends Format { * @param locale the locale to use * @return the textual name of the time zone */ - static synchronized String getTimeZoneDisplay(TimeZone tz, boolean daylight, int style, Locale locale) { - Object key = new TimeZoneDisplayKey(tz, daylight, style, locale); + static String getTimeZoneDisplay(TimeZone tz, boolean daylight, int style, Locale locale) { + TimeZoneDisplayKey key = new TimeZoneDisplayKey(tz, daylight, style, locale); String value = cTimeZoneDisplayCache.get(key); if (value == null) { // This is a very slow call, so cache the results. value = tz.getDisplayName(daylight, style, locale); - cTimeZoneDisplayCache.put(key, value); + String prior = cTimeZoneDisplayCache.putIfAbsent(key, value); + if (prior != null) { + value= prior; + } } return value; } - /** - *

Gets the default pattern.

- * - * @return the default pattern - */ - private static synchronized String getDefaultPattern() { - if (cDefaultPattern == null) { - cDefaultPattern = new SimpleDateFormat().toPattern(); - } - return cDefaultPattern; - } - // Constructor //----------------------------------------------------------------------- /** *

Constructs a new FastDateFormat.

* - * @param pattern {@link java.text.SimpleDateFormat} compatible - * pattern - * @param timeZone time zone to use, null means use - * default for Date and value within for - * Calendar - * @param locale locale, null means use system - * default - * @throws IllegalArgumentException if pattern is invalid or - * null + * @param pattern {@link java.text.SimpleDateFormat} compatible pattern + * @param timeZone non-null time zone to use + * @param locale non-null locale to use + * @throws NullPointerException if pattern, timeZone, or locale is null. */ protected FastDateFormat(String pattern, TimeZone timeZone, Locale locale) { - super(); - if (pattern == null) { - throw new IllegalArgumentException("The pattern must not be null"); - } mPattern = pattern; - - mTimeZoneForced = (timeZone != null); - if (timeZone == null) { - timeZone = TimeZone.getDefault(); - } mTimeZone = timeZone; - - mLocaleForced = (locale != null); - if (locale == null) { - locale = Locale.getDefault(); - } mLocale = locale; + + init(); } /** *

Initializes the instance for first use.

*/ - protected void init() { + private void init() { List rulesList = parsePattern(); mRules = rulesList.toArray(new Rule[rulesList.size()]); @@ -662,9 +547,9 @@ public class FastDateFormat extends Format { break; case 'z': // time zone (text) if (tokenLen >= 4) { - rule = new TimeZoneNameRule(mTimeZone, mTimeZoneForced, mLocale, TimeZone.LONG); + rule = new TimeZoneNameRule(mTimeZone, mLocale, TimeZone.LONG); } else { - rule = new TimeZoneNameRule(mTimeZone, mTimeZoneForced, mLocale, TimeZone.SHORT); + rule = new TimeZoneNameRule(mTimeZone, mLocale, TimeZone.SHORT); } break; case 'Z': // time zone (value) @@ -812,7 +697,7 @@ public class FastDateFormat extends Format { * @return the formatted string */ public String format(Date date) { - Calendar c = new GregorianCalendar(mTimeZone, mLocale); + Calendar c = Calendar.getInstance(mTimeZone, mLocale); c.setTime(date); return applyRules(c, new StringBuffer(mMaxLengthEstimate)).toString(); } @@ -849,7 +734,7 @@ public class FastDateFormat extends Format { * @return the specified string buffer */ public StringBuffer format(Date date, StringBuffer buf) { - Calendar c = new GregorianCalendar(mTimeZone); + Calendar c = Calendar.getInstance(mTimeZone, mLocale); c.setTime(date); return applyRules(c, buf); } @@ -863,11 +748,6 @@ public class FastDateFormat extends Format { * @return the specified string buffer */ public StringBuffer format(Calendar calendar, StringBuffer buf) { - if (mTimeZoneForced) { - calendar.getTimeInMillis(); /// LANG-538 - calendar = (Calendar) calendar.clone(); - calendar.setTimeZone(mTimeZone); - } return applyRules(calendar, buf); } @@ -880,10 +760,8 @@ public class FastDateFormat extends Format { * @return the specified string buffer */ protected StringBuffer applyRules(Calendar calendar, StringBuffer buf) { - Rule[] rules = mRules; - int len = mRules.length; - for (int i = 0; i < len; i++) { - rules[i].appendTo(buf, calendar); + for (Rule rule : mRules) { + rule.appendTo(buf, calendar); } return buf; } @@ -918,10 +796,7 @@ public class FastDateFormat extends Format { /** *

Gets the time zone used by this formatter.

* - *

This zone is always used for Date formatting. - * If a Calendar is passed in to be formatted, the - * time zone on that may be used depending on - * {@link #getTimeZoneOverridesCalendar()}.

+ *

This zone is always used for Date formatting.

* * @return the time zone */ @@ -929,17 +804,6 @@ public class FastDateFormat extends Format { return mTimeZone; } - /** - *

Returns true if the time zone of the - * calendar overrides the time zone of the formatter.

- * - * @return true if time zone of formatter - * overridden for calendars - */ - public boolean getTimeZoneOverridesCalendar() { - return mTimeZoneForced; - } - /** *

Gets the locale used by this formatter.

* @@ -976,16 +840,9 @@ public class FastDateFormat extends Format { return false; } FastDateFormat other = (FastDateFormat) obj; - if ( - (mPattern == other.mPattern || mPattern.equals(other.mPattern)) && - (mTimeZone == other.mTimeZone || mTimeZone.equals(other.mTimeZone)) && - (mLocale == other.mLocale || mLocale.equals(other.mLocale)) && - (mTimeZoneForced == other.mTimeZoneForced) && - (mLocaleForced == other.mLocaleForced) - ) { - return true; - } - return false; + return mPattern.equals(other.mPattern) + && mTimeZone.equals(other.mTimeZone) + && mLocale.equals(other.mLocale); } /** @@ -995,13 +852,7 @@ public class FastDateFormat extends Format { */ @Override public int hashCode() { - int total = 0; - total += mPattern.hashCode(); - total += mTimeZone.hashCode(); - total += (mTimeZoneForced ? 1 : 0); - total += mLocale.hashCode(); - total += (mLocaleForced ? 1 : 0); - return total; + return mPattern.hashCode() + 13 * (mTimeZone.hashCode() + 13 * mLocale.hashCode()); } /** @@ -1517,9 +1368,6 @@ public class FastDateFormat extends Format { */ private static class TimeZoneNameRule implements Rule { private final TimeZone mTimeZone; - private final boolean mTimeZoneForced; - private final Locale mLocale; - private final int mStyle; private final String mStandard; private final String mDaylight; @@ -1527,55 +1375,31 @@ public class FastDateFormat extends Format { * Constructs an instance of TimeZoneNameRule with the specified properties. * * @param timeZone the time zone - * @param timeZoneForced if true the time zone is forced into standard and daylight * @param locale the locale * @param style the style */ - TimeZoneNameRule(TimeZone timeZone, boolean timeZoneForced, Locale locale, int style) { + TimeZoneNameRule(TimeZone timeZone, Locale locale, int style) { mTimeZone = timeZone; - mTimeZoneForced = timeZoneForced; - mLocale = locale; - mStyle = style; - if (timeZoneForced) { - mStandard = getTimeZoneDisplay(timeZone, false, style, locale); - mDaylight = getTimeZoneDisplay(timeZone, true, style, locale); - } else { - mStandard = null; - mDaylight = null; - } + mStandard = getTimeZoneDisplay(timeZone, false, style, locale); + mDaylight = getTimeZoneDisplay(timeZone, true, style, locale); } /** * {@inheritDoc} */ public int estimateLength() { - if (mTimeZoneForced) { - return Math.max(mStandard.length(), mDaylight.length()); - } else if (mStyle == TimeZone.SHORT) { - return 4; - } else { - return 40; - } + return Math.max(mStandard.length(), mDaylight.length()); } /** * {@inheritDoc} */ public void appendTo(StringBuffer buffer, Calendar calendar) { - if (mTimeZoneForced) { - if (mTimeZone.useDaylightTime() && calendar.get(Calendar.DST_OFFSET) != 0) { - buffer.append(mDaylight); - } else { - buffer.append(mStandard); - } + if (mTimeZone.useDaylightTime() && calendar.get(Calendar.DST_OFFSET) != 0) { + buffer.append(mDaylight); } else { - TimeZone timeZone = calendar.getTimeZone(); - if (timeZone.useDaylightTime() && calendar.get(Calendar.DST_OFFSET) != 0) { - buffer.append(getTimeZoneDisplay(timeZone, true, mStyle, mLocale)); - } else { - buffer.append(getTimeZoneDisplay(timeZone, false, mStyle, mLocale)); - } + buffer.append(mStandard); } } } @@ -1665,7 +1489,7 @@ public class FastDateFormat extends Format { */ @Override 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; } } - - // ---------------------------------------------------------------------- - /** - *

Helper class for creating compound objects.

- * - *

One use for this class is to create a hashtable key - * out of multiple objects.

- */ - private static class Pair { - private final Object mObj1; - private final Object mObj2; - - /** - * Constructs an instance of Pair 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 + ']'; - } - } - } diff --git a/src/main/java/org/apache/commons/lang3/time/FormatCache.java b/src/main/java/org/apache/commons/lang3/time/FormatCache.java new file mode 100644 index 000000000..a2d8690c8 --- /dev/null +++ b/src/main/java/org/apache/commons/lang3/time/FormatCache.java @@ -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; + +/** + *

FormatCache is a cache and factory for {@link Format}s.

+ * + * @since 3.0 + * @version $Id: FormatCache 892161 2009-12-18 07:21:10Z $ + */ +abstract class FormatCache { + /** + * No date or no time. Used in same parameters as DateFormat.SHORT or DateFormat.LONG + */ + static final int NONE= -1; + + private final ConcurrentMap cInstanceCache + = new ConcurrentHashMap(7); + + private final ConcurrentMap cDateTimeInstanceCache + = new ConcurrentHashMap(7); + + /** + *

Gets a formatter instance using the default pattern in the + * default timezone and locale.

+ * + * @return a date/time formatter + */ + public F getInstance() { + return getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT, TimeZone.getDefault(), Locale.getDefault()); + } + + /** + *

Gets a formatter instance using the specified pattern, time zone + * and locale.

+ * + * @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 null + */ + 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; + } + + /** + *

Create a format instance using the specified pattern, time zone + * and locale.

+ * + * @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 null + */ + abstract protected F createInstance(String pattern, TimeZone timeZone, Locale locale); + + /** + *

Gets a date/time formatter instance using the specified style, + * time zone and locale.

+ * + * @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); + } + + // ---------------------------------------------------------------------- + /** + *

Helper class to hold multi-part Map keys

+ */ + private static class MultipartKey { + private final Object[] keys; + private int hashCode; + + /** + * Constructs an instance of MultipartKey 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; + } + } + +} diff --git a/src/test/java/org/apache/commons/lang3/time/FastDateFormatTest.java b/src/test/java/org/apache/commons/lang3/time/FastDateFormatTest.java index 6a7b9b629..8645bf682 100644 --- a/src/test/java/org/apache/commons/lang3/time/FastDateFormatTest.java +++ b/src/test/java/org/apache/commons/lang3/time/FastDateFormatTest.java @@ -56,8 +56,6 @@ public class FastDateFormatTest extends TestCase { assertEquals("MM/DD/yyyy", format1.getPattern()); assertEquals(TimeZone.getDefault(), format1.getTimeZone()); assertEquals(TimeZone.getDefault(), format2.getTimeZone()); - assertEquals(false, format1.getTimeZoneOverridesCalendar()); - assertEquals(false, format2.getTimeZoneOverridesCalendar()); } 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); assertEquals(TimeZone.getTimeZone("Atlantic/Reykjavik"), format1.getTimeZone()); - assertEquals(true, format1.getTimeZoneOverridesCalendar()); assertEquals(TimeZone.getDefault(), format2.getTimeZone()); - assertEquals(false, format2.getTimeZoneOverridesCalendar()); assertSame(format3, format4); assertTrue(format3 != format5); // -- 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.getDefault(), format2.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, format2.getLocale()); assertEquals(Locale.GERMANY, format3.getLocale()); @@ -183,8 +176,6 @@ public class FastDateFormatTest extends TestCase { try { Locale.setDefault(Locale.US); TimeZone.setDefault(TimeZone.getTimeZone("America/New_York")); - FastDateFormat fdf = null; - SimpleDateFormat sdf = null; GregorianCalendar cal1 = new GregorianCalendar(2003, 0, 10, 15, 33, 20); GregorianCalendar cal2 = new GregorianCalendar(2003, 6, 10, 9, 00, 00); @@ -193,8 +184,8 @@ public class FastDateFormatTest extends TestCase { long millis1 = date1.getTime(); long millis2 = date2.getTime(); - fdf = FastDateFormat.getInstance("yyyy-MM-dd'T'HH:mm:ss"); - sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss"); + FastDateFormat fdf = FastDateFormat.getInstance("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("2003-01-10T15:33:20", fdf.format(date1)); 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(millis1)); - fdf = FastDateFormat.getInstance("Z"); assertEquals("-0400", fdf.format(date2)); assertEquals("-0400", fdf.format(cal2)); 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(millis1)); - fdf = FastDateFormat.getInstance("ZZ"); assertEquals("-04:00", fdf.format(date2)); assertEquals("-04:00", fdf.format(cal2)); assertEquals("-04:00", fdf.format(millis2)); @@ -228,14 +217,13 @@ public class FastDateFormatTest extends TestCase { fdf = FastDateFormat.getInstance(pattern); sdf = new SimpleDateFormat(pattern); assertEquals(sdf.format(date1), fdf.format(date1)); - assertEquals(sdf.format(date2), fdf.format(date2)); - + assertEquals(sdf.format(date2), fdf.format(date2)); } finally { Locale.setDefault(realDefaultLocale); TimeZone.setDefault(realDefaultZone); } } - + /** * 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 * This test confirms it, getting 366 back as a date */ - // TODO: Fix this problem public void testSimpleDate() { Calendar cal = Calendar.getInstance(); FastDateFormat format = FastDateFormat.getInstance("yyyy/MM/dd"); @@ -308,8 +295,6 @@ public class FastDateFormatTest extends TestCase { } 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) // 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")); @@ -317,7 +302,8 @@ public class FastDateFormatTest extends TestCase { cal.set(2009, 9, 16, 8, 42, 16); 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() {