Author: chas Date: Mon Jan 13 23:01:23 2014 New Revision: 1557882 URL: http://svn.apache.org/r1557882 Log: LANG-949 and LANG-950
Modified: commons/proper/lang/trunk/src/main/java/org/apache/commons/lang3/time/FastDateFormat.java 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/main/java/org/apache/commons/lang3/time/FastDateFormat.java URL: http://svn.apache.org/viewvc/commons/proper/lang/trunk/src/main/java/org/apache/commons/lang3/time/FastDateFormat.java?rev=1557882&r1=1557881&r2=1557882&view=diff ============================================================================== --- commons/proper/lang/trunk/src/main/java/org/apache/commons/lang3/time/FastDateFormat.java (original) +++ commons/proper/lang/trunk/src/main/java/org/apache/commons/lang3/time/FastDateFormat.java Mon Jan 13 23:01:23 2014 @@ -39,7 +39,7 @@ import java.util.TimeZone; * * <p>All patterns are compatible with * SimpleDateFormat (except time zones and some year patterns - see below).</p> - * + * * <p>Since 3.2, FastDateFormat supports parsing as well as printing.</p> * * <p>Java 1.4 introduced a new pattern letter, {@code 'Z'}, to represent @@ -94,7 +94,7 @@ public class FastDateFormat extends Form private final FastDatePrinter printer; private final FastDateParser parser; - + //----------------------------------------------------------------------- /** * <p>Gets a formatter instance using the default pattern in the @@ -210,7 +210,7 @@ public class FastDateFormat extends Form public static FastDateFormat getDateInstance(final int style, final TimeZone timeZone) { return cache.getDateInstance(style, timeZone, null); } - + /** * <p>Gets a date formatter instance using the specified style, time * zone and locale.</p> @@ -366,8 +366,23 @@ public class FastDateFormat extends Form * @throws NullPointerException if pattern, timeZone, or locale is null. */ protected FastDateFormat(final String pattern, final TimeZone timeZone, final Locale locale) { + this(pattern, timeZone, locale, null); + } + + // Constructor + //----------------------------------------------------------------------- + /** + * <p>Constructs a new FastDateFormat.</p> + * + * @param pattern {@link java.text.SimpleDateFormat} compatible pattern + * @param timeZone non-null time zone to use + * @param locale non-null locale to use + * @param centuryStart The start of the 100 year period to use as the "default century" for 2 digit year parsing. If centuryStart is null, defaults to now - 80 years + * @throws NullPointerException if pattern, timeZone, or locale is null. + */ + protected FastDateFormat(final String pattern, final TimeZone timeZone, final Locale locale, final Date centuryStart) { printer= new FastDatePrinter(pattern, timeZone, locale); - parser= new FastDateParser(pattern, timeZone, locale); + parser= new FastDateParser(pattern, timeZone, locale, centuryStart); } // Format methods @@ -463,7 +478,7 @@ public class FastDateFormat extends Form // Parsing //----------------------------------------------------------------------- - + /* (non-Javadoc) * @see DateParser#parse(java.lang.String) */ 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=1557882&r1=1557881&r2=1557882&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 Mon Jan 13 23:01:23 2014 @@ -71,7 +71,7 @@ public class FastDateParser implements D * * @see java.io.Serializable */ - private static final long serialVersionUID = 1L; + private static final long serialVersionUID = 2L; static final Locale JAPANESE_IMPERIAL = new Locale("ja","JP","JP"); @@ -79,11 +79,12 @@ public class FastDateParser implements D private final String pattern; private final TimeZone timeZone; private final Locale locale; + private final int century; + private final int startYear; // derived fields private transient Pattern parsePattern; private transient Strategy[] strategies; - private transient int thisYear; // dynamic fields to communicate with Strategy private transient String currentFormatField; @@ -96,21 +97,38 @@ public class FastDateParser implements D * pattern * @param timeZone non-null time zone to use * @param locale non-null locale + * @param centuryStart The start of the century for 2 digit year parsing */ - protected FastDateParser(final String pattern, final TimeZone timeZone, final Locale locale) { + protected FastDateParser(final String pattern, final TimeZone timeZone, final Locale locale, Date centuryStart) { this.pattern = pattern; this.timeZone = timeZone; this.locale = locale; - init(); + + final Calendar definingCalendar = Calendar.getInstance(timeZone, locale); + int centuryStartYear; + if(centuryStart!=null) { + definingCalendar.setTime(centuryStart); + centuryStartYear= definingCalendar.get(Calendar.YEAR); + } + else if(locale.equals(JAPANESE_IMPERIAL)) { + centuryStartYear= 0; + } + else { + // from 80 years ago to 20 years from now + definingCalendar.setTime(new Date()); + centuryStartYear= definingCalendar.get(Calendar.YEAR)-80; + } + century= centuryStartYear / 100 * 100; + startYear= centuryStartYear - century; + + init(definingCalendar); } - /** + /** * Initialize derived fields from defining fields. * This is called from constructor and from readObject (de-serialization) */ - private void init() { - final Calendar definingCalendar = Calendar.getInstance(timeZone, locale); - thisYear= definingCalendar.get(Calendar.YEAR); + private void init(Calendar definingCalendar) { final StringBuilder regex= new StringBuilder(); final List<Strategy> collector = new ArrayList<Strategy>(); @@ -176,7 +194,7 @@ public class FastDateParser implements D /** * Returns the generated pattern (for testing purposes). - * + * * @return the generated pattern */ Pattern getParsePattern() { @@ -234,7 +252,9 @@ public class FastDateParser implements D */ private void readObject(final ObjectInputStream in) throws IOException, ClassNotFoundException { in.defaultReadObject(); - init(); + + final Calendar definingCalendar = Calendar.getInstance(timeZone, locale); + init(definingCalendar); } /* (non-Javadoc) @@ -319,11 +339,11 @@ public class FastDateParser implements D case '\\': if(++i==value.length()) { break; - } + } /* * If we have found \E, we replace it with \E\\E\Q, i.e. we stop the quoting, * quote the \ in \E, then restart the quoting. - * + * * Otherwise we just output the two characters. * In each case the initial \ needs to be output and the final char is done at the end */ @@ -354,16 +374,13 @@ public class FastDateParser implements D } /** - * Adjust dates to be within 80 years before and 20 years after instantiation + * Adjust dates to be within appropriate century * @param twoDigitYear The year to adjust - * @return A value within -80 and +20 years from instantiation of this instance + * @return A value between centuryStart(inclusive) to centuryStart+100(exclusive) */ - int adjustYear(final int twoDigitYear) { - final int trial= twoDigitYear + thisYear - thisYear%100; - if(trial < thisYear+20) { - return trial; - } - return trial-100; + private int adjustYear(final int twoDigitYear) { + int trial= century + twoDigitYear; + return twoDigitYear>=startYear ?trial :trial+100; } /** @@ -389,7 +406,7 @@ public class FastDateParser implements D /** * Is this field a number? * The default implementation returns false. - * + * * @return true, if field is a number */ boolean isNumber() { @@ -397,15 +414,15 @@ public class FastDateParser implements D } /** * Set the Calendar with the parsed field. - * + * * The default implementation does nothing. - * + * * @param parser The parser calling this strategy * @param cal The <code>Calendar</code> to set * @param value The parsed field to translate and set in cal */ void setCalendar(final FastDateParser parser, final Calendar cal, final String value) { - + } /** * Generate a <code>Pattern</code> regular expression to the <code>StringBuilder</code> 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=1557882&r1=1557881&r2=1557882&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 Mon Jan 13 23:01:23 2014 @@ -19,6 +19,7 @@ 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; @@ -30,9 +31,8 @@ import java.util.Locale; import java.util.Map; import java.util.TimeZone; -import org.junit.Assert; - import org.apache.commons.lang3.SerializationUtils; +import org.junit.Assert; import org.junit.Test; /** @@ -42,8 +42,8 @@ import org.junit.Test; * @since 3.2 */ public class FastDateParserTest { - private static final String SHORT_FORMAT_NOERA = "y/M/d/h/a/m/E/Z"; - private static final String LONG_FORMAT_NOERA = "yyyy/MMMM/dddd/hhhh/mmmm/aaaa/EEEE/ZZZZ"; + private static final String SHORT_FORMAT_NOERA = "y/M/d/h/a/m/s/E/Z"; + private static final String LONG_FORMAT_NOERA = "yyyy/MMMM/dddd/hhhh/mmmm/ss/aaaa/EEEE/ZZZZ"; private static final String SHORT_FORMAT = "G/" + SHORT_FORMAT_NOERA; private static final String LONG_FORMAT = "GGGG/" + LONG_FORMAT_NOERA; @@ -79,7 +79,7 @@ public class FastDateParserTest { * Override this method in derived tests to change the construction of instances */ protected DateParser getInstance(final String format, final TimeZone timeZone, final Locale locale) { - return new FastDateParser(format, timeZone, locale); + return new FastDateParser(format, timeZone, locale, null); } @Test @@ -188,41 +188,58 @@ public class FastDateParserTest { assertEquals(cal.getTime(), H.parse("2010-08-01 12:33:20")); } + private Calendar getEraStart(int year, TimeZone zone, Locale locale) { + Calendar cal = Calendar.getInstance(zone, locale); + cal.clear(); + + // http://docs.oracle.com/javase/6/docs/technotes/guides/intl/calendar.doc.html + if (locale.equals(FastDateParser.JAPANESE_IMPERIAL)) { + if(year < 1868) { + cal.set(Calendar.ERA, 0); + cal.set(Calendar.YEAR, 1868-year); + } + } + else { + if (year < 0) { + cal.set(Calendar.ERA, GregorianCalendar.BC); + year= -year; + } + cal.set(Calendar.YEAR, year/100 * 100); + } + return cal; + } + + private void validateSdfFormatFdpParseEquality(String format, Locale locale, TimeZone tz, DateParser fdp, Date in, int year, Date cs) throws ParseException { + final SimpleDateFormat sdf = new SimpleDateFormat(format, locale); + if (format.equals(SHORT_FORMAT)) { + sdf.set2DigitYearStart( cs ); + } + final String fmt = sdf.format(in); + try { + final Date out = fdp.parse(fmt); + assertEquals(locale.toString()+" "+in+" "+ format+ " "+tz.getID(), in, out); + } catch (final ParseException pe) { + System.out.println(fmt+" "+locale.toString()+" "+year+" "+ format+ " "+tz.getID()); + throw pe; + } + } + @Test // Check that all Locales can parse the formats we use public void testParses() throws Exception { - for(final Locale locale : Locale.getAvailableLocales()) { - for(final TimeZone tz : new TimeZone[]{NEW_YORK, GMT}) { - final Calendar cal = Calendar.getInstance(tz); - for(final 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.equals(FastDateParser.JAPANESE_IMPERIAL)) { - 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); - } - final Date in = cal.getTime(); - for(final String format : new String[]{LONG_FORMAT, SHORT_FORMAT}) { - final SimpleDateFormat sdf = new SimpleDateFormat(format, locale); - if (format.equals(SHORT_FORMAT)) { - if (year < 1930) { - sdf.set2DigitYearStart(cal.getTime()); - } - } - final String fmt = sdf.format(in); - try { - final Date out = sdf.parse(fmt); - - assertEquals(locale.toString()+" "+year+" "+ format+ " "+tz.getID(), in, out); - } catch (final ParseException pe) { - System.out.println(fmt+" "+locale.toString()+" "+year+" "+ format+ " "+tz.getID()); - throw pe; - } + for(final String format : new String[]{LONG_FORMAT, SHORT_FORMAT}) { + for(final Locale locale : Locale.getAvailableLocales()) { + for(final TimeZone tz : new TimeZone[]{NEW_YORK, REYKJAVIK, GMT}) { + for(final int year : new int[]{2003, 1940, 1868, 1867, 1, -1, -1940}) { + Calendar cal= getEraStart(year, tz, locale); + Date centuryStart= cal.getTime(); + + cal.set(Calendar.MONTH, 1); + cal.set(Calendar.DAY_OF_MONTH, 10); + Date in= cal.getTime(); + + final FastDateParser fdp= new FastDateParser(format, tz, locale, centuryStart); + validateSdfFormatFdpParseEquality(format, locale, tz, fdp, in, year, centuryStart); } } }