LANG-829: FastDateParser could use Calendar.getDisplayNames for all text fields
git-svn-id: https://svn.apache.org/repos/asf/commons/proper/lang/trunk@1390839 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
parent
9960dd4204
commit
8c87749695
|
@ -19,7 +19,6 @@ package org.apache.commons.lang3.time;
|
|||
import java.io.IOException;
|
||||
import java.io.ObjectInputStream;
|
||||
import java.io.Serializable;
|
||||
import java.text.DateFormatSymbols;
|
||||
import java.text.ParseException;
|
||||
import java.text.ParsePosition;
|
||||
import java.util.ArrayList;
|
||||
|
@ -73,9 +72,6 @@ public class FastDateParser implements DateParser, Serializable {
|
|||
*/
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private static final ConcurrentMap<Locale,TimeZoneStrategy> tzsCache=
|
||||
new ConcurrentHashMap<Locale,TimeZoneStrategy>(3);
|
||||
|
||||
static final Locale JAPANESE_IMPERIAL = new Locale("ja","JP","JP");
|
||||
|
||||
// defining fields
|
||||
|
@ -87,7 +83,6 @@ public class FastDateParser implements DateParser, Serializable {
|
|||
private transient Pattern parsePattern;
|
||||
private transient Strategy[] strategies;
|
||||
private transient int thisYear;
|
||||
private transient ConcurrentMap<Integer, KeyValue[]> nameValues;
|
||||
|
||||
// dynamic fields to communicate with Strategy
|
||||
private transient String currentFormatField;
|
||||
|
@ -113,9 +108,8 @@ public class FastDateParser implements DateParser, Serializable {
|
|||
* This is called from constructor and from readObject (de-serialization)
|
||||
*/
|
||||
private void init() {
|
||||
thisYear= Calendar.getInstance(timeZone, locale).get(Calendar.YEAR);
|
||||
|
||||
nameValues= new ConcurrentHashMap<Integer, KeyValue[]>();
|
||||
Calendar definingCalendar = Calendar.getInstance(timeZone, locale);
|
||||
thisYear= definingCalendar.get(Calendar.YEAR);
|
||||
|
||||
StringBuilder regex= new StringBuilder();
|
||||
List<Strategy> collector = new ArrayList<Strategy>();
|
||||
|
@ -127,7 +121,7 @@ public class FastDateParser implements DateParser, Serializable {
|
|||
}
|
||||
|
||||
currentFormatField= patternMatcher.group();
|
||||
Strategy currentStrategy= getStrategy(currentFormatField);
|
||||
Strategy currentStrategy= getStrategy(currentFormatField, definingCalendar);
|
||||
for(;;) {
|
||||
patternMatcher.region(patternMatcher.end(), patternMatcher.regionEnd());
|
||||
if(!patternMatcher.lookingAt()) {
|
||||
|
@ -135,7 +129,7 @@ public class FastDateParser implements DateParser, Serializable {
|
|||
break;
|
||||
}
|
||||
String nextFormatField= patternMatcher.group();
|
||||
nextStrategy = getStrategy(nextFormatField);
|
||||
nextStrategy = getStrategy(nextFormatField, definingCalendar);
|
||||
if(currentStrategy.addRegex(this, regex)) {
|
||||
collector.add(currentStrategy);
|
||||
}
|
||||
|
@ -368,111 +362,28 @@ public class FastDateParser implements DateParser, Serializable {
|
|||
/**
|
||||
* Get the short and long values displayed for a field
|
||||
* @param field The field of interest
|
||||
* @param definingCalendar The calendar to obtain the short and long values
|
||||
* @param locale The locale of dislay names
|
||||
* @return A sorted array of the field key / value pairs
|
||||
*/
|
||||
KeyValue[] getDisplayNames(int field) {
|
||||
Integer fieldInt = Integer.valueOf(field);
|
||||
KeyValue[] fieldKeyValues= nameValues.get(fieldInt);
|
||||
if(fieldKeyValues==null) {
|
||||
DateFormatSymbols symbols= DateFormatSymbols.getInstance(locale);
|
||||
switch(field) {
|
||||
case Calendar.ERA:
|
||||
// DateFormatSymbols#getEras() only returns AD/BC or translations
|
||||
// It does not work for the Thai Buddhist or Japanese Imperial calendars.
|
||||
// see: https://issues.apache.org/jira/browse/TRINIDAD-2126
|
||||
Calendar c = Calendar.getInstance(locale);
|
||||
// N.B. Some calendars have different short and long symbols, e.g. ja_JP_JP
|
||||
// TODO Seems to be only that locale; if that is guaranteed, could skip some work here
|
||||
String[] shortEras = toArray(c.getDisplayNames(Calendar.ERA, Calendar.SHORT, locale));
|
||||
String[] longEras = toArray(c.getDisplayNames(Calendar.ERA, Calendar.LONG, locale));
|
||||
if (Arrays.equals(shortEras, longEras)) {
|
||||
fieldKeyValues = createKeyValues(longEras, null); // save memory
|
||||
} else {
|
||||
fieldKeyValues = createKeyValues(longEras, shortEras);
|
||||
}
|
||||
break;
|
||||
case Calendar.DAY_OF_WEEK:
|
||||
fieldKeyValues= createKeyValues(symbols.getWeekdays(), symbols.getShortWeekdays());
|
||||
break;
|
||||
case Calendar.AM_PM:
|
||||
fieldKeyValues= createKeyValues(symbols.getAmPmStrings(), null);
|
||||
break;
|
||||
case Calendar.MONTH:
|
||||
fieldKeyValues= createKeyValues(symbols.getMonths(), symbols.getShortMonths());
|
||||
break;
|
||||
default:
|
||||
throw new IllegalArgumentException("Invalid field value "+field);
|
||||
}
|
||||
KeyValue[] prior = nameValues.putIfAbsent(fieldInt, fieldKeyValues);
|
||||
if(prior!=null) {
|
||||
fieldKeyValues= prior;
|
||||
}
|
||||
}
|
||||
return fieldKeyValues;
|
||||
private static KeyValue[] getDisplayNames(int field, Calendar definingCalendar, Locale locale) {
|
||||
List<KeyValue> keyValues = new ArrayList<KeyValue>(24);
|
||||
addNamesToKeyValues(keyValues, definingCalendar.getDisplayNames(field, Calendar.ALL_STYLES, locale));
|
||||
return createKeyValues(keyValues);
|
||||
}
|
||||
|
||||
private String[] toArray(Map<String, Integer> era) {
|
||||
String[] eras = new String[era.size()]; // assume no gaps in entry values
|
||||
for(Map.Entry<String, Integer> me : era.entrySet()) {
|
||||
int idx = me.getValue().intValue();
|
||||
final String key = me.getKey();
|
||||
if (key == null) {
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
eras[idx] = key;
|
||||
private static void addNamesToKeyValues(List<KeyValue> keyValues, Map<String, Integer> displayNames) {
|
||||
for(Map.Entry<String, Integer> me : displayNames.entrySet()) {
|
||||
keyValues.add(new KeyValue(me.getKey(), me.getValue()));
|
||||
}
|
||||
return eras;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create key / value pairs from keys
|
||||
* @param longValues The allowable long names for a field
|
||||
* @param shortValues The optional allowable short names for a field
|
||||
* @return The sorted name / value pairs for the field
|
||||
*/
|
||||
private static KeyValue[] createKeyValues(String[] longValues, String[] shortValues) {
|
||||
KeyValue[] fieldKeyValues= new KeyValue[count(longValues)+count(shortValues)];
|
||||
copy(fieldKeyValues, copy(fieldKeyValues, 0, longValues), shortValues);
|
||||
private static KeyValue[] createKeyValues(List<KeyValue> keyValues) {
|
||||
KeyValue[] fieldKeyValues= keyValues.toArray(new KeyValue[keyValues.size()]);
|
||||
Arrays.sort(fieldKeyValues, IGNORE_CASE_COMPARATOR);
|
||||
return fieldKeyValues;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a count of valid values in array. A valid value is of non-zero length.
|
||||
* @param values The values to check. This parameter may be null
|
||||
* @return The number of valid values
|
||||
*/
|
||||
private static int count(String[] values) {
|
||||
int count= 0;
|
||||
if(values!=null) {
|
||||
for(String value : values) {
|
||||
if(value.length()>0) {
|
||||
++count;
|
||||
}
|
||||
}
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create key / value pairs from values
|
||||
* @param fieldKeyValues The destination array
|
||||
* @param offset The offset into the destination array
|
||||
* @param values The values to use to create key / value pairs. This parameter may be null.
|
||||
* @return The offset into the destination array
|
||||
*/
|
||||
private static int copy(KeyValue[] fieldKeyValues, int offset, String[] values) {
|
||||
if(values!=null) {
|
||||
for(int i= 0; i<values.length; ++i) {
|
||||
String value= values[i];
|
||||
if(value.length()>0) {
|
||||
fieldKeyValues[offset++]= new KeyValue(value, i);
|
||||
}
|
||||
}
|
||||
}
|
||||
return offset;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adjust dates to be within 80 years before and 20 years after instantiation
|
||||
* @param twoDigitYear The year to adjust
|
||||
|
@ -523,6 +434,7 @@ public class FastDateParser implements DateParser, Serializable {
|
|||
* which will accept this field
|
||||
* @param parser The parser calling this strategy
|
||||
* @param regex The <code>StringBuilder</code> to append to
|
||||
* @param Calendar The calendar this strategy must parse
|
||||
* @return true, if this field will set the calendar;
|
||||
* false, if this field is a constant value
|
||||
*/
|
||||
|
@ -538,9 +450,10 @@ public class FastDateParser implements DateParser, Serializable {
|
|||
/**
|
||||
* Obtain a Strategy given a field from a SimpleDateFormat pattern
|
||||
* @param formatField A sub-sequence of the SimpleDateFormat pattern
|
||||
* @param definingCalendar The calendar to obtain the short and long values
|
||||
* @return The Strategy that will handle parsing for the field
|
||||
*/
|
||||
private Strategy getStrategy(String formatField) {
|
||||
private Strategy getStrategy(String formatField, Calendar definingCalendar) {
|
||||
switch(formatField.charAt(0)) {
|
||||
case '\'':
|
||||
if(formatField.length()>2) {
|
||||
|
@ -552,25 +465,23 @@ public class FastDateParser implements DateParser, Serializable {
|
|||
case 'D':
|
||||
return DAY_OF_YEAR_STRATEGY;
|
||||
case 'E':
|
||||
return DAY_OF_WEEK_STRATEGY;
|
||||
return getLocaleSpecificStrategy(Calendar.DAY_OF_WEEK, definingCalendar);
|
||||
case 'F':
|
||||
return DAY_OF_WEEK_IN_MONTH_STRATEGY;
|
||||
case 'G':
|
||||
return ERA_STRATEGY;
|
||||
return getLocaleSpecificStrategy(Calendar.ERA, definingCalendar);
|
||||
case 'H':
|
||||
return MODULO_HOUR_OF_DAY_STRATEGY;
|
||||
case 'K':
|
||||
return HOUR_STRATEGY;
|
||||
case 'M':
|
||||
return formatField.length()>=3 ?TEXT_MONTH_STRATEGY :NUMBER_MONTH_STRATEGY;
|
||||
return formatField.length()>=3 ?getLocaleSpecificStrategy(Calendar.MONTH, definingCalendar) :NUMBER_MONTH_STRATEGY;
|
||||
case 'S':
|
||||
return MILLISECOND_STRATEGY;
|
||||
case 'W':
|
||||
return WEEK_OF_MONTH_STRATEGY;
|
||||
case 'Z':
|
||||
break;
|
||||
case 'a':
|
||||
return AM_PM_STRATEGY;
|
||||
return getLocaleSpecificStrategy(Calendar.AM_PM, definingCalendar);
|
||||
case 'd':
|
||||
return DAY_OF_MONTH_STRATEGY;
|
||||
case 'h':
|
||||
|
@ -585,21 +496,52 @@ public class FastDateParser implements DateParser, Serializable {
|
|||
return WEEK_OF_YEAR_STRATEGY;
|
||||
case 'y':
|
||||
return formatField.length()>2 ?LITERAL_YEAR_STRATEGY :ABBREVIATED_YEAR_STRATEGY;
|
||||
case 'Z':
|
||||
case 'z':
|
||||
break;
|
||||
return getLocaleSpecificStrategy(Calendar.ZONE_OFFSET, definingCalendar);
|
||||
}
|
||||
TimeZoneStrategy tzs= tzsCache.get(locale);
|
||||
if(tzs==null) {
|
||||
tzs= new TimeZoneStrategy(locale);
|
||||
TimeZoneStrategy inCache= tzsCache.putIfAbsent(locale, tzs);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private static ConcurrentMap<Locale, Strategy>[] caches = new ConcurrentMap[Calendar.FIELD_COUNT];
|
||||
|
||||
/**
|
||||
* Get a cache of Strategies for a particular field
|
||||
* @param field The Calendar field
|
||||
* @return a cache of Locale to Strategy
|
||||
*/
|
||||
private static ConcurrentMap<Locale, Strategy> getCache(int field) {
|
||||
synchronized(caches) {
|
||||
if(caches[field]==null) {
|
||||
caches[field]= new ConcurrentHashMap<Locale,Strategy>(3);
|
||||
}
|
||||
return caches[field];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a Strategy that parses a Text field
|
||||
* @param locale The Locale of the TimeZone to parse
|
||||
* @param field The Calendar field
|
||||
* @param definingCalendar The calendar to obtain the short and long values
|
||||
* @return a TextStrategy for the field and Locale
|
||||
*/
|
||||
private Strategy getLocaleSpecificStrategy(int field, Calendar definingCalendar) {
|
||||
ConcurrentMap<Locale,Strategy> cache = getCache(field);
|
||||
Strategy strategy= cache.get(field);
|
||||
if(strategy==null) {
|
||||
strategy= field==Calendar.ZONE_OFFSET
|
||||
? new TimeZoneStrategy(locale)
|
||||
: new TextStrategy(field, definingCalendar, locale);
|
||||
Strategy inCache= cache.putIfAbsent(locale, strategy);
|
||||
if(inCache!=null) {
|
||||
return inCache;
|
||||
}
|
||||
}
|
||||
return tzs;
|
||||
return strategy;
|
||||
}
|
||||
|
||||
/**
|
||||
/**
|
||||
* A strategy that copies the static or quoted field in the parsing pattern
|
||||
*/
|
||||
private static class CopyQuotedStrategy implements Strategy {
|
||||
|
@ -642,18 +584,20 @@ public class FastDateParser implements DateParser, Serializable {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
/**
|
||||
* A strategy that handles a text field in the parsing pattern
|
||||
*/
|
||||
private static class TextStrategy implements Strategy {
|
||||
private static class TextStrategy implements Strategy {
|
||||
private final int field;
|
||||
private final KeyValue[] keyValues;
|
||||
|
||||
/**
|
||||
* Construct a Strategy that parses a Text field
|
||||
* @param field The Calendar field
|
||||
*/
|
||||
TextStrategy(int field) {
|
||||
TextStrategy(int field, Calendar definingCalendar, Locale locale) {
|
||||
this.field= field;
|
||||
this.keyValues= getDisplayNames(field, definingCalendar, locale);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -670,7 +614,7 @@ public class FastDateParser implements DateParser, Serializable {
|
|||
@Override
|
||||
public boolean addRegex(FastDateParser parser, StringBuilder regex) {
|
||||
regex.append('(');
|
||||
for(KeyValue textKeyValue : parser.getDisplayNames(field)) {
|
||||
for(KeyValue textKeyValue : keyValues) {
|
||||
escapeRegex(regex, textKeyValue.key, false).append('|');
|
||||
}
|
||||
regex.setCharAt(regex.length()-1, ')');
|
||||
|
@ -682,21 +626,21 @@ public class FastDateParser implements DateParser, Serializable {
|
|||
*/
|
||||
@Override
|
||||
public void setCalendar(FastDateParser parser, Calendar cal, String value) {
|
||||
KeyValue[] textKeyValues= parser.getDisplayNames(field);
|
||||
int idx= Arrays.binarySearch(textKeyValues, new KeyValue(value, -1), IGNORE_CASE_COMPARATOR);
|
||||
int idx= Arrays.binarySearch(keyValues, new KeyValue(value, -1), IGNORE_CASE_COMPARATOR);
|
||||
if(idx<0) {
|
||||
StringBuilder sb= new StringBuilder(value);
|
||||
sb.append(" not in (");
|
||||
for(KeyValue textKeyValue : textKeyValues) {
|
||||
for(KeyValue textKeyValue : keyValues) {
|
||||
sb.append(textKeyValue.key).append(' ');
|
||||
}
|
||||
sb.setCharAt(sb.length()-1, ')');
|
||||
throw new IllegalArgumentException(sb.toString());
|
||||
}
|
||||
cal.set(field, textKeyValues[idx].value);
|
||||
cal.set(field, keyValues[idx].value);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* A strategy that handles a number field in the parsing pattern
|
||||
*/
|
||||
|
@ -838,12 +782,6 @@ public class FastDateParser implements DateParser, Serializable {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
private static final Strategy ERA_STRATEGY = new TextStrategy(Calendar.ERA);
|
||||
private static final Strategy DAY_OF_WEEK_STRATEGY = new TextStrategy(Calendar.DAY_OF_WEEK);
|
||||
private static final Strategy AM_PM_STRATEGY = new TextStrategy(Calendar.AM_PM);
|
||||
private static final Strategy TEXT_MONTH_STRATEGY = new TextStrategy(Calendar.MONTH);
|
||||
|
||||
private static final Strategy NUMBER_MONTH_STRATEGY = new NumberStrategy(Calendar.MONTH) {
|
||||
@Override
|
||||
public int modify(int iValue) {
|
||||
|
|
Loading…
Reference in New Issue