From d7af9435485453053a61f9baaca616602d8a4fd8 Mon Sep 17 00:00:00 2001 From: Sebastian Bazley Date: Wed, 26 Sep 2012 18:03:24 +0000 Subject: [PATCH] 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 --- .../commons/lang3/time/FastDateParser.java | 75 ++++++-------- .../lang3/time/FastDateParserTest.java | 99 +------------------ 2 files changed, 33 insertions(+), 141 deletions(-) diff --git a/src/main/java/org/apache/commons/lang3/time/FastDateParser.java b/src/main/java/org/apache/commons/lang3/time/FastDateParser.java index 3f8d5c96d..0e6cca49a 100644 --- a/src/main/java/org/apache/commons/lang3/time/FastDateParser.java +++ b/src/main/java/org/apache/commons/lang3/time/FastDateParser.java @@ -22,7 +22,6 @@ import java.io.Serializable; import java.text.DateFormatSymbols; import java.text.ParseException; import java.text.ParsePosition; -import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Arrays; import java.util.Calendar; @@ -30,6 +29,7 @@ import java.util.Comparator; import java.util.Date; import java.util.List; import java.util.Locale; +import java.util.Map; import java.util.SortedMap; import java.util.TimeZone; import java.util.TreeMap; @@ -125,18 +125,6 @@ public class FastDateParser implements DateParser, Serializable { 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(); Strategy currentStrategy= getStrategy(currentFormatField); for(;;) { @@ -256,7 +244,13 @@ public class FastDateParser implements DateParser, Serializable { public Date parse(String source) throws ParseException { Date date= parse(source, new ParsePosition(0)); 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; } @@ -383,7 +377,14 @@ public class FastDateParser implements DateParser, Serializable { DateFormatSymbols symbols= DateFormatSymbols.getInstance(locale); switch(field) { 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; case Calendar.DAY_OF_WEEK: fieldKeyValues= createKeyValues(symbols.getWeekdays(), symbols.getShortWeekdays()); @@ -404,6 +405,19 @@ public class FastDateParser implements DateParser, Serializable { } return fieldKeyValues; } + + private String[] toArray(Map era) { + String[] eras = new String[era.size()]; // assume no gaps in entry values + for(Map.Entry 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 @@ -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 DAY_OF_WEEK_STRATEGY = new TextStrategy(Calendar.DAY_OF_WEEK); private static final Strategy AM_PM_STRATEGY = new TextStrategy(Calendar.AM_PM); diff --git a/src/test/java/org/apache/commons/lang3/time/FastDateParserTest.java b/src/test/java/org/apache/commons/lang3/time/FastDateParserTest.java index da2cfc8b3..2be047f27 100644 --- a/src/test/java/org/apache/commons/lang3/time/FastDateParserTest.java +++ b/src/test/java/org/apache/commons/lang3/time/FastDateParserTest.java @@ -20,24 +20,19 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import java.io.Serializable; -import java.text.DateFormatSymbols; import java.text.ParseException; import java.text.SimpleDateFormat; -import java.util.Arrays; import java.util.Calendar; import java.util.Date; import java.util.GregorianCalendar; import java.util.HashMap; -import java.util.HashSet; import java.util.Locale; import java.util.Map; -import java.util.Set; import java.util.TimeZone; import junit.framework.Assert; import org.apache.commons.lang3.SerializationUtils; -import org.apache.commons.lang3.tuple.ImmutablePair; 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> locale2Absent = new HashSet>(); - Map locale2Eras = new HashMap(); - 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 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 public void testLocales_Long_AD() throws Exception { testLocales(LONG_FORMAT, false); @@ -360,10 +276,9 @@ public class FastDateParserTest { if (eraBC) { cal.set(Calendar.ERA, GregorianCalendar.BC); } - boolean failed = false; for(Locale locale : Locale.getAvailableLocales()) { // 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; } SimpleDateFormat sdf = new SimpleDateFormat(format, locale); @@ -372,16 +287,9 @@ public class FastDateParserTest { try { checkParse(locale, cal, sdf, fdf); } catch(ParseException ex) { - failed = true; - // 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())); + Assert.fail("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) { @@ -399,7 +307,8 @@ public class FastDateParserTest { String formattedDate= sdf.format(cal.getTime()); Date expectedTime = sdf.parse(formattedDate); Date actualTime = fdf.parse(formattedDate); - assertEquals(locale.toString()+" "+formattedDate,expectedTime, actualTime); + assertEquals(locale.toString()+" "+formattedDate + +"\n",expectedTime, actualTime); } @Test