Author: sebb Date: Tue Sep 25 16:32:20 2012 New Revision: 1389976 URL: http://svn.apache.org/viewvc?rev=1389976&view=rev Log: LANG-828 FastDateParser does not handle non-Gregorian calendars properly
Modified: commons/proper/lang/trunk/src/changes/changes.xml commons/proper/lang/trunk/src/main/java/org/apache/commons/lang3/time/FastDateParser.java commons/proper/lang/trunk/src/test/java/org/apache/commons/lang3/time/FastDateParserTest.java Modified: commons/proper/lang/trunk/src/changes/changes.xml URL: http://svn.apache.org/viewvc/commons/proper/lang/trunk/src/changes/changes.xml?rev=1389976&r1=1389975&r2=1389976&view=diff ============================================================================== --- commons/proper/lang/trunk/src/changes/changes.xml (original) +++ commons/proper/lang/trunk/src/changes/changes.xml Tue Sep 25 16:32:20 2012 @@ -22,6 +22,7 @@ <body> <release version="3.2" date="TBA" description="Next release"> + <action issue="LANG-828" type="fix">FastDateParser does not handle non-Gregorian calendars properly</action> <action issue="LANG-826" type="fix">FastDateParser does not handle non-ASCII digits correctly</action> <action issue="LANG-825" type="add">Create StrBuilder APIs similar to String.format(String, Object...)</action> <action issue="LANG-817" type="fix">Add org.apache.commons.lang3.SystemUtils.IS_OS_WINDOWS_8</action> Modified: commons/proper/lang/trunk/src/main/java/org/apache/commons/lang3/time/FastDateParser.java URL: http://svn.apache.org/viewvc/commons/proper/lang/trunk/src/main/java/org/apache/commons/lang3/time/FastDateParser.java?rev=1389976&r1=1389975&r2=1389976&view=diff ============================================================================== --- commons/proper/lang/trunk/src/main/java/org/apache/commons/lang3/time/FastDateParser.java (original) +++ commons/proper/lang/trunk/src/main/java/org/apache/commons/lang3/time/FastDateParser.java Tue Sep 25 16:32:20 2012 @@ -22,6 +22,7 @@ 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; @@ -55,6 +56,13 @@ import java.util.regex.Pattern; * <p>Timing tests indicate this class is as about as fast as SimpleDateFormat * in single thread applications and about 25% faster in multi-thread applications.</p> * + * <p>Note that the code only handles Gregorian calendars. The following non-Gregorian + * calendars use SimpleDateFormat internally, and so will be slower: + * <ul> + * <li>ja_JP_TH - Japanese Imperial</li> + * <li>th_TH (any variant) - Thai Buddhist</li> + * </ul> + * </p> * @since 3.2 */ public class FastDateParser implements DateParser, Serializable { @@ -114,7 +122,17 @@ public class FastDateParser implements D if(!patternMatcher.lookingAt()) { throw new IllegalArgumentException("Invalid pattern"); } - + + String localeName = locale.toString(); + // These locales don't use the Gregorian calendar + // See http://docs.oracle.com/javase/6/docs/technotes/guides/intl/calendar.doc.html + if (localeName.equals("ja_JP_JP") || localeName.startsWith("th_TH")) { + collector.add(new SimpleDateFormatStrategy()); + strategies= collector.toArray(new Strategy[collector.size()]); + parsePattern= Pattern.compile("(.*+)"); + return; + } + currentFormatField= patternMatcher.group(); Strategy currentStrategy= getStrategy(currentFormatField); for(;;) { @@ -797,6 +815,38 @@ public class FastDateParser implements D } } + + /** + * 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); Modified: commons/proper/lang/trunk/src/test/java/org/apache/commons/lang3/time/FastDateParserTest.java URL: http://svn.apache.org/viewvc/commons/proper/lang/trunk/src/test/java/org/apache/commons/lang3/time/FastDateParserTest.java?rev=1389976&r1=1389975&r2=1389976&view=diff ============================================================================== --- commons/proper/lang/trunk/src/test/java/org/apache/commons/lang3/time/FastDateParserTest.java (original) +++ commons/proper/lang/trunk/src/test/java/org/apache/commons/lang3/time/FastDateParserTest.java Tue Sep 25 16:32:20 2012 @@ -19,7 +19,6 @@ package org.apache.commons.lang3.time; 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.ParseException; import java.text.SimpleDateFormat; @@ -31,6 +30,8 @@ import java.util.Locale; import java.util.Map; import java.util.TimeZone; +import junit.framework.Assert; + import org.apache.commons.lang3.SerializationUtils; import org.junit.Test; @@ -191,15 +192,37 @@ public class FastDateParserTest { public void testParses() throws Exception { for(Locale locale : Locale.getAvailableLocales()) { for(TimeZone tz : new TimeZone[]{NEW_YORK, GMT}) { - Calendar cal = Calendar.getInstance(tz); - cal.clear(); - cal.set(2003, 1, 10); - Date in = cal.getTime(); - for(String format : new String[]{LONG_FORMAT, SHORT_FORMAT}) { - SimpleDateFormat sdf = new SimpleDateFormat(LONG_FORMAT, locale); - String fmt = sdf.format(in); - Date out = sdf.parse(fmt); - assertEquals(locale.toString()+" "+ format+ " "+tz.getID(), in, out); + Calendar cal = Calendar.getInstance(tz); + for(int year : new int[]{2003, 1940, 1868, 1867, 0, -1940}) { + // http://docs.oracle.com/javase/6/docs/technotes/guides/intl/calendar.doc.html + if (year < 1868 && locale.toString().equals("ja_JP_JP")) { + continue; // Japanese imperial calendar does not support eras before 1868 + } + 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[]{LONG_FORMAT, SHORT_FORMAT}) { + SimpleDateFormat sdf = new SimpleDateFormat(format, locale); + if (format.equals(SHORT_FORMAT)) { + if (year < 1930) { + sdf.set2DigitYearStart(cal.getTime()); + } + } + String fmt = sdf.format(in); + try { + Date out = sdf.parse(fmt); + + assertEquals(locale.toString()+" "+year+" "+ format+ " "+tz.getID(), in, out); + } catch (ParseException pe) { + System.out.println(fmt+" "+locale.toString()+" "+year+" "+ format+ " "+tz.getID()); + throw pe; + } + } } } } @@ -253,19 +276,28 @@ 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.toString().equals("ja_JP_JP")) { + continue; + } SimpleDateFormat sdf = new SimpleDateFormat(format, locale); DateParser fdf = getInstance(format, locale); 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())); } } + if (failed) { + Assert.fail("One or more tests failed, see above"); + } } private String trimMessage(String msg) {