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
|
@ -366,8 +366,23 @@ public class FastDateFormat extends Format implements DateParser, DatePrinter {
|
||||||
* @throws NullPointerException if pattern, timeZone, or locale is null.
|
* @throws NullPointerException if pattern, timeZone, or locale is null.
|
||||||
*/
|
*/
|
||||||
protected FastDateFormat(final String pattern, final TimeZone timeZone, final Locale locale) {
|
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);
|
printer= new FastDatePrinter(pattern, timeZone, locale);
|
||||||
parser= new FastDateParser(pattern, timeZone, locale);
|
parser= new FastDateParser(pattern, timeZone, locale, centuryStart);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Format methods
|
// Format methods
|
||||||
|
|
|
@ -71,7 +71,7 @@ public class FastDateParser implements DateParser, Serializable {
|
||||||
*
|
*
|
||||||
* @see java.io.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");
|
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 String pattern;
|
||||||
private final TimeZone timeZone;
|
private final TimeZone timeZone;
|
||||||
private final Locale locale;
|
private final Locale locale;
|
||||||
|
private final int century;
|
||||||
|
private final int startYear;
|
||||||
|
|
||||||
// derived fields
|
// derived fields
|
||||||
private transient Pattern parsePattern;
|
private transient Pattern parsePattern;
|
||||||
private transient Strategy[] strategies;
|
private transient Strategy[] strategies;
|
||||||
private transient int thisYear;
|
|
||||||
|
|
||||||
// dynamic fields to communicate with Strategy
|
// dynamic fields to communicate with Strategy
|
||||||
private transient String currentFormatField;
|
private transient String currentFormatField;
|
||||||
|
@ -96,21 +97,38 @@ public class FastDateParser implements DateParser, Serializable {
|
||||||
* pattern
|
* pattern
|
||||||
* @param timeZone non-null time zone to use
|
* @param timeZone non-null time zone to use
|
||||||
* @param locale non-null locale
|
* @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.pattern = pattern;
|
||||||
this.timeZone = timeZone;
|
this.timeZone = timeZone;
|
||||||
this.locale = locale;
|
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.
|
* Initialize derived fields from defining fields.
|
||||||
* This is called from constructor and from readObject (de-serialization)
|
* This is called from constructor and from readObject (de-serialization)
|
||||||
*/
|
*/
|
||||||
private void init() {
|
private void init(Calendar definingCalendar) {
|
||||||
final Calendar definingCalendar = Calendar.getInstance(timeZone, locale);
|
|
||||||
thisYear= definingCalendar.get(Calendar.YEAR);
|
|
||||||
|
|
||||||
final StringBuilder regex= new StringBuilder();
|
final StringBuilder regex= new StringBuilder();
|
||||||
final List<Strategy> collector = new ArrayList<Strategy>();
|
final List<Strategy> collector = new ArrayList<Strategy>();
|
||||||
|
@ -234,7 +252,9 @@ public class FastDateParser implements DateParser, Serializable {
|
||||||
*/
|
*/
|
||||||
private void readObject(final ObjectInputStream in) throws IOException, ClassNotFoundException {
|
private void readObject(final ObjectInputStream in) throws IOException, ClassNotFoundException {
|
||||||
in.defaultReadObject();
|
in.defaultReadObject();
|
||||||
init();
|
|
||||||
|
final Calendar definingCalendar = Calendar.getInstance(timeZone, locale);
|
||||||
|
init(definingCalendar);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* (non-Javadoc)
|
/* (non-Javadoc)
|
||||||
|
@ -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
|
* @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) {
|
private int adjustYear(final int twoDigitYear) {
|
||||||
final int trial= twoDigitYear + thisYear - thisYear%100;
|
int trial= century + twoDigitYear;
|
||||||
if(trial < thisYear+20) {
|
return twoDigitYear>=startYear ?trial :trial+100;
|
||||||
return trial;
|
|
||||||
}
|
|
||||||
return trial-100;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -19,6 +19,7 @@ package org.apache.commons.lang3.time;
|
||||||
import static org.junit.Assert.assertEquals;
|
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.ParseException;
|
import java.text.ParseException;
|
||||||
import java.text.SimpleDateFormat;
|
import java.text.SimpleDateFormat;
|
||||||
|
@ -30,9 +31,8 @@ import java.util.Locale;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.TimeZone;
|
import java.util.TimeZone;
|
||||||
|
|
||||||
import org.junit.Assert;
|
|
||||||
|
|
||||||
import org.apache.commons.lang3.SerializationUtils;
|
import org.apache.commons.lang3.SerializationUtils;
|
||||||
|
import org.junit.Assert;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -42,8 +42,8 @@ import org.junit.Test;
|
||||||
* @since 3.2
|
* @since 3.2
|
||||||
*/
|
*/
|
||||||
public class FastDateParserTest {
|
public class FastDateParserTest {
|
||||||
private static final String SHORT_FORMAT_NOERA = "y/M/d/h/a/m/E/Z";
|
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/aaaa/EEEE/ZZZZ";
|
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 SHORT_FORMAT = "G/" + SHORT_FORMAT_NOERA;
|
||||||
private static final String LONG_FORMAT = "GGGG/" + LONG_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
|
* Override this method in derived tests to change the construction of instances
|
||||||
*/
|
*/
|
||||||
protected DateParser getInstance(final String format, final TimeZone timeZone, final Locale locale) {
|
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
|
@Test
|
||||||
|
@ -188,42 +188,59 @@ public class FastDateParserTest {
|
||||||
assertEquals(cal.getTime(), H.parse("2010-08-01 12:33:20"));
|
assertEquals(cal.getTime(), H.parse("2010-08-01 12:33:20"));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
private Calendar getEraStart(int year, TimeZone zone, Locale locale) {
|
||||||
// Check that all Locales can parse the formats we use
|
Calendar cal = Calendar.getInstance(zone, locale);
|
||||||
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();
|
cal.clear();
|
||||||
if (year < 0) {
|
|
||||||
cal.set(-year, 1, 10);
|
// http://docs.oracle.com/javase/6/docs/technotes/guides/intl/calendar.doc.html
|
||||||
cal.set(Calendar.ERA, GregorianCalendar.BC);
|
if (locale.equals(FastDateParser.JAPANESE_IMPERIAL)) {
|
||||||
} else {
|
if(year < 1868) {
|
||||||
cal.set(year, 1, 10);
|
cal.set(Calendar.ERA, 0);
|
||||||
|
cal.set(Calendar.YEAR, 1868-year);
|
||||||
}
|
}
|
||||||
final Date in = cal.getTime();
|
}
|
||||||
for(final String format : new String[]{LONG_FORMAT, SHORT_FORMAT}) {
|
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);
|
final SimpleDateFormat sdf = new SimpleDateFormat(format, locale);
|
||||||
if (format.equals(SHORT_FORMAT)) {
|
if (format.equals(SHORT_FORMAT)) {
|
||||||
if (year < 1930) {
|
sdf.set2DigitYearStart( cs );
|
||||||
sdf.set2DigitYearStart(cal.getTime());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
final String fmt = sdf.format(in);
|
final String fmt = sdf.format(in);
|
||||||
try {
|
try {
|
||||||
final Date out = sdf.parse(fmt);
|
final Date out = fdp.parse(fmt);
|
||||||
|
assertEquals(locale.toString()+" "+in+" "+ format+ " "+tz.getID(), in, out);
|
||||||
assertEquals(locale.toString()+" "+year+" "+ format+ " "+tz.getID(), in, out);
|
|
||||||
} catch (final ParseException pe) {
|
} catch (final ParseException pe) {
|
||||||
System.out.println(fmt+" "+locale.toString()+" "+year+" "+ format+ " "+tz.getID());
|
System.out.println(fmt+" "+locale.toString()+" "+year+" "+ format+ " "+tz.getID());
|
||||||
throw pe;
|
throw pe;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
// Check that all Locales can parse the formats we use
|
||||||
|
public void testParses() throws Exception {
|
||||||
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue