LANG-828 FastDateParser does not handle non-Gregorian calendars properly
Use Calendar#getDisplayNames() instead of DateFormatSymbols#getEras() git-svn-id: https://svn.apache.org/repos/asf/commons/proper/lang/trunk@1390626 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
parent
e582456625
commit
d7af943548
|
@ -22,7 +22,6 @@ import java.io.Serializable;
|
||||||
import java.text.DateFormatSymbols;
|
import java.text.DateFormatSymbols;
|
||||||
import java.text.ParseException;
|
import java.text.ParseException;
|
||||||
import java.text.ParsePosition;
|
import java.text.ParsePosition;
|
||||||
import java.text.SimpleDateFormat;
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Calendar;
|
import java.util.Calendar;
|
||||||
|
@ -30,6 +29,7 @@ import java.util.Comparator;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
|
import java.util.Map;
|
||||||
import java.util.SortedMap;
|
import java.util.SortedMap;
|
||||||
import java.util.TimeZone;
|
import java.util.TimeZone;
|
||||||
import java.util.TreeMap;
|
import java.util.TreeMap;
|
||||||
|
@ -125,18 +125,6 @@ public class FastDateParser implements DateParser, Serializable {
|
||||||
throw new IllegalArgumentException("Invalid pattern");
|
throw new IllegalArgumentException("Invalid pattern");
|
||||||
}
|
}
|
||||||
|
|
||||||
// These locales don't use the Gregorian calendar
|
|
||||||
// See http://docs.oracle.com/javase/6/docs/technotes/guides/intl/calendar.doc.html
|
|
||||||
// Also, the getEras() methods don't return the correct era names.
|
|
||||||
// N.B. Not safe to use toString() comparison because that changes between Java versions
|
|
||||||
if (locale.equals(JAPANESE_IMPERIAL)
|
|
||||||
|| (locale.getLanguage().equals("th") && locale.getCountry().equals("TH"))) {
|
|
||||||
collector.add(new SimpleDateFormatStrategy());
|
|
||||||
strategies= collector.toArray(new Strategy[collector.size()]);
|
|
||||||
parsePattern= Pattern.compile("(.*+)");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
currentFormatField= patternMatcher.group();
|
currentFormatField= patternMatcher.group();
|
||||||
Strategy currentStrategy= getStrategy(currentFormatField);
|
Strategy currentStrategy= getStrategy(currentFormatField);
|
||||||
for(;;) {
|
for(;;) {
|
||||||
|
@ -256,7 +244,13 @@ public class FastDateParser implements DateParser, Serializable {
|
||||||
public Date parse(String source) throws ParseException {
|
public Date parse(String source) throws ParseException {
|
||||||
Date date= parse(source, new ParsePosition(0));
|
Date date= parse(source, new ParsePosition(0));
|
||||||
if(date==null) {
|
if(date==null) {
|
||||||
throw new ParseException(source+" does not match "+parsePattern.pattern(), 0);
|
// Add a note re supported date range
|
||||||
|
if (locale.equals(JAPANESE_IMPERIAL)) {
|
||||||
|
throw new ParseException(
|
||||||
|
"(The " +locale + " locale does not support dates before 1868 AD)\n" +
|
||||||
|
source+" does not match "+parsePattern.pattern(), 0);
|
||||||
|
}
|
||||||
|
throw new ParseException(source+" does not match "+parsePattern.pattern(), 0);
|
||||||
}
|
}
|
||||||
return date;
|
return date;
|
||||||
}
|
}
|
||||||
|
@ -383,7 +377,14 @@ public class FastDateParser implements DateParser, Serializable {
|
||||||
DateFormatSymbols symbols= DateFormatSymbols.getInstance(locale);
|
DateFormatSymbols symbols= DateFormatSymbols.getInstance(locale);
|
||||||
switch(field) {
|
switch(field) {
|
||||||
case Calendar.ERA:
|
case Calendar.ERA:
|
||||||
fieldKeyValues= createKeyValues(symbols.getEras(), null);
|
// 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
|
||||||
|
String[] shortEras = toArray(c.getDisplayNames(Calendar.ERA, Calendar.SHORT, locale));
|
||||||
|
String[] longEras = toArray(c.getDisplayNames(Calendar.ERA, Calendar.LONG, locale));
|
||||||
|
fieldKeyValues= createKeyValues(longEras, shortEras);
|
||||||
break;
|
break;
|
||||||
case Calendar.DAY_OF_WEEK:
|
case Calendar.DAY_OF_WEEK:
|
||||||
fieldKeyValues= createKeyValues(symbols.getWeekdays(), symbols.getShortWeekdays());
|
fieldKeyValues= createKeyValues(symbols.getWeekdays(), symbols.getShortWeekdays());
|
||||||
|
@ -404,6 +405,19 @@ public class FastDateParser implements DateParser, Serializable {
|
||||||
}
|
}
|
||||||
return fieldKeyValues;
|
return fieldKeyValues;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
return eras;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create key / value pairs from keys
|
* Create key / value pairs from keys
|
||||||
|
@ -820,37 +834,6 @@ public class FastDateParser implements DateParser, Serializable {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Dummy strategy which delegates to SimpleDateFormat.
|
|
||||||
*/
|
|
||||||
private static class SimpleDateFormatStrategy implements Strategy {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isNumber() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setCalendar(FastDateParser parser, Calendar cal, String value) {
|
|
||||||
String pat = parser.pattern;
|
|
||||||
Locale loc = parser.locale;
|
|
||||||
SimpleDateFormat sdf = new SimpleDateFormat(pat, loc);
|
|
||||||
try {
|
|
||||||
Date d = sdf.parse(value);
|
|
||||||
cal.setTime(d);
|
|
||||||
} catch (ParseException e) {
|
|
||||||
throw new IllegalArgumentException(
|
|
||||||
"Unexpected error using pattern " + pat + " with locale " + loc.toString(), e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean addRegex(FastDateParser parser, StringBuilder regex) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
private static final Strategy ERA_STRATEGY = new TextStrategy(Calendar.ERA);
|
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 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 AM_PM_STRATEGY = new TextStrategy(Calendar.AM_PM);
|
||||||
|
|
|
@ -20,24 +20,19 @@ import static org.junit.Assert.assertEquals;
|
||||||
import static org.junit.Assert.assertFalse;
|
import static org.junit.Assert.assertFalse;
|
||||||
import static org.junit.Assert.assertTrue;
|
import static org.junit.Assert.assertTrue;
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
import java.text.DateFormatSymbols;
|
|
||||||
import java.text.ParseException;
|
import java.text.ParseException;
|
||||||
import java.text.SimpleDateFormat;
|
import java.text.SimpleDateFormat;
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.Calendar;
|
import java.util.Calendar;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
import java.util.GregorianCalendar;
|
import java.util.GregorianCalendar;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
|
||||||
import java.util.TimeZone;
|
import java.util.TimeZone;
|
||||||
|
|
||||||
import junit.framework.Assert;
|
import junit.framework.Assert;
|
||||||
|
|
||||||
import org.apache.commons.lang3.SerializationUtils;
|
import org.apache.commons.lang3.SerializationUtils;
|
||||||
import org.apache.commons.lang3.tuple.ImmutablePair;
|
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -233,85 +228,6 @@ public class FastDateParserTest {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
|
||||||
// Check that all Locales generate Strings containing the expected eras
|
|
||||||
public void testEras() throws Exception {
|
|
||||||
Set<ImmutablePair<Locale, String>> locale2Absent = new HashSet<ImmutablePair<Locale, String>>();
|
|
||||||
Map<Locale, String[]> locale2Eras = new HashMap<Locale, String[]>();
|
|
||||||
for(Locale locale : Locale.getAvailableLocales()) {
|
|
||||||
for(TimeZone tz : new TimeZone[]{GMT}) {
|
|
||||||
Calendar cal = Calendar.getInstance(tz);
|
|
||||||
String[] eras = DateFormatSymbols.getInstance(locale).getEras();
|
|
||||||
String[] erasPrint = new String[eras.length];
|
|
||||||
for(int i = 0; i < eras.length ; i++) {
|
|
||||||
String s = eras[i];
|
|
||||||
if (s.length() > 4) {
|
|
||||||
erasPrint[i] = s;
|
|
||||||
} else {
|
|
||||||
erasPrint[i] = display(s);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for(int year : new int[]{2003, 1927, 1913, 1868, 1867, -2003}) {
|
|
||||||
cal.clear();
|
|
||||||
if (year < 0) {
|
|
||||||
cal.set(-year, 1, 10);
|
|
||||||
cal.set(Calendar.ERA, GregorianCalendar.BC);
|
|
||||||
} else {
|
|
||||||
cal.set(year, 1, 10);
|
|
||||||
}
|
|
||||||
Date in = cal.getTime();
|
|
||||||
for(String format : new String[]{"GGGG","G"}) {
|
|
||||||
SimpleDateFormat sdf = new SimpleDateFormat(format, locale);
|
|
||||||
String fmt = sdf.format(in);
|
|
||||||
boolean found = false;
|
|
||||||
for(String era : eras) {
|
|
||||||
if (fmt.startsWith(era)) {
|
|
||||||
found=true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!found) {
|
|
||||||
locale2Absent.add(ImmutablePair.of(locale, fmt));
|
|
||||||
locale2Eras.put(locale, erasPrint);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (locale2Absent.size() > 0) {
|
|
||||||
System.out.println("FastDateParserTest: one or more missing era designators detected:");
|
|
||||||
for(ImmutablePair<Locale, String> me : locale2Absent) {
|
|
||||||
Locale loc = me.getKey();
|
|
||||||
String [] erasPrint = locale2Eras.get(loc);
|
|
||||||
System.out.println("Locale: "+loc.toString()+" era: '"+display(me.getValue())+"' not found in eras: " + Arrays.toString(erasPrint));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// assertFalse("One or more failures detected",fail);
|
|
||||||
}
|
|
||||||
|
|
||||||
private String display(String fmt) {
|
|
||||||
if (fmt.matches("\\p{ASCII}*")) {
|
|
||||||
return fmt;
|
|
||||||
}
|
|
||||||
StringBuilder sb = new StringBuilder();
|
|
||||||
sb.append(fmt);
|
|
||||||
sb.append(" = ");
|
|
||||||
for(int i =0; i < fmt.length(); i++) {
|
|
||||||
if (i > 0) {
|
|
||||||
sb.append(' ');
|
|
||||||
}
|
|
||||||
String s = fmt.substring(i,i+1);
|
|
||||||
if (s.matches("\\p{ASCII}")) {
|
|
||||||
sb.append(s);
|
|
||||||
} else {
|
|
||||||
char charAt = fmt.charAt(i);
|
|
||||||
sb.append("\\u");
|
|
||||||
sb.append(Integer.toHexString(charAt));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return sb.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testLocales_Long_AD() throws Exception {
|
public void testLocales_Long_AD() throws Exception {
|
||||||
testLocales(LONG_FORMAT, false);
|
testLocales(LONG_FORMAT, false);
|
||||||
|
@ -360,10 +276,9 @@ public class FastDateParserTest {
|
||||||
if (eraBC) {
|
if (eraBC) {
|
||||||
cal.set(Calendar.ERA, GregorianCalendar.BC);
|
cal.set(Calendar.ERA, GregorianCalendar.BC);
|
||||||
}
|
}
|
||||||
boolean failed = false;
|
|
||||||
for(Locale locale : Locale.getAvailableLocales()) {
|
for(Locale locale : Locale.getAvailableLocales()) {
|
||||||
// ja_JP_JP cannot handle dates before 1868 properly
|
// ja_JP_JP cannot handle dates before 1868 properly
|
||||||
if (eraBC && format.equals(SHORT_FORMAT) && locale.equals(FastDateParser.JAPANESE_IMPERIAL)) {
|
if (eraBC && locale.equals(FastDateParser.JAPANESE_IMPERIAL)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
SimpleDateFormat sdf = new SimpleDateFormat(format, locale);
|
SimpleDateFormat sdf = new SimpleDateFormat(format, locale);
|
||||||
|
@ -372,16 +287,9 @@ public class FastDateParserTest {
|
||||||
try {
|
try {
|
||||||
checkParse(locale, cal, sdf, fdf);
|
checkParse(locale, cal, sdf, fdf);
|
||||||
} catch(ParseException ex) {
|
} catch(ParseException ex) {
|
||||||
failed = true;
|
Assert.fail("Locale "+locale+ " failed with "+format+" era "+(eraBC?"BC":"AD")+"\n" + trimMessage(ex.toString()));
|
||||||
// TODO: are these Java bugs?
|
|
||||||
// ja_JP_JP, th_TH, and th_TH_TH fail with both eras because the generated era name does not match
|
|
||||||
// ja_JP_JP fails with era BC because it converts to -2002
|
|
||||||
System.out.println("Locale "+locale+ " failed with "+format+" era "+(eraBC?"BC":"AD")+"\n" + trimMessage(ex.toString()));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (failed) {
|
|
||||||
Assert.fail("One or more tests failed, see above");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private String trimMessage(String msg) {
|
private String trimMessage(String msg) {
|
||||||
|
@ -399,7 +307,8 @@ public class FastDateParserTest {
|
||||||
String formattedDate= sdf.format(cal.getTime());
|
String formattedDate= sdf.format(cal.getTime());
|
||||||
Date expectedTime = sdf.parse(formattedDate);
|
Date expectedTime = sdf.parse(formattedDate);
|
||||||
Date actualTime = fdf.parse(formattedDate);
|
Date actualTime = fdf.parse(formattedDate);
|
||||||
assertEquals(locale.toString()+" "+formattedDate,expectedTime, actualTime);
|
assertEquals(locale.toString()+" "+formattedDate
|
||||||
|
+"\n",expectedTime, actualTime);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|
Loading…
Reference in New Issue