LANG-949 and LANG-950
git-svn-id: https://svn.apache.org/repos/asf/commons/proper/lang/trunk@1557882 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
parent
2abe2c8366
commit
03645a1ec1
|
@ -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 Format implements DateParser, DatePrinter {
|
|||
|
||||
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 Format implements DateParser, DatePrinter {
|
|||
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 Format implements DateParser, DatePrinter {
|
|||
* @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 Format implements DateParser, DatePrinter {
|
|||
// Parsing
|
||||
//-----------------------------------------------------------------------
|
||||
|
||||
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see DateParser#parse(java.lang.String)
|
||||
*/
|
||||
|
|
|
@ -71,7 +71,7 @@ public class FastDateParser implements DateParser, Serializable {
|
|||
*
|
||||
* @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 DateParser, Serializable {
|
|||
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 DateParser, Serializable {
|
|||
* 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 DateParser, Serializable {
|
|||
|
||||
/**
|
||||
* Returns the generated pattern (for testing purposes).
|
||||
*
|
||||
*
|
||||
* @return the generated pattern
|
||||
*/
|
||||
Pattern getParsePattern() {
|
||||
|
@ -234,7 +252,9 @@ public class FastDateParser implements DateParser, Serializable {
|
|||
*/
|
||||
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 DateParser, Serializable {
|
|||
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 DateParser, Serializable {
|
|||
}
|
||||
|
||||
/**
|
||||
* 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 DateParser, Serializable {
|
|||
/**
|
||||
* 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 DateParser, Serializable {
|
|||
}
|
||||
/**
|
||||
* 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>
|
||||
|
|
|
@ -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);
|
||||
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();
|
||||
|
||||
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;
|
||||
}
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue