diff --git a/src/changes/changes.xml b/src/changes/changes.xml index 6ba92b626..7f1fdf334 100644 --- a/src/changes/changes.xml +++ b/src/changes/changes.xml @@ -22,7 +22,7 @@ - + Fix parsing edge cases in FastDateParser 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 d704f69ca..2d469a6b3 100644 --- a/src/main/java/org/apache/commons/lang3/time/FastDateParser.java +++ b/src/main/java/org/apache/commons/lang3/time/FastDateParser.java @@ -744,9 +744,13 @@ void setCalendar(final FastDateParser parser, final Calendar cal, final String v /** * A strategy that handles a timezone field in the parsing pattern */ - private static class TimeZoneStrategy extends Strategy { - - private final String validTimeZoneChars; + static class TimeZoneStrategy extends Strategy { + private static final String RFC_822_TIME_ZONE = "[+-]\\d{4}"; + private static final String GMT_OPTION= "GMT[+-]\\d{1,2}:\\d{2}"; + // see http://www.iana.org/time-zones and http://cldr.unicode.org/translation/timezones + static final String TZ_DATABASE= "(?:\\p{L}[\\p{L}\\p{Mc}\\p{Nd}\\p{Zs}\\p{P}&&[^-]]*-?\\p{Zs}?)*"; + private static final String VALID_TZ = "((?iu)"+RFC_822_TIME_ZONE+"|"+GMT_OPTION+"|"+TZ_DATABASE+")"; + private final SortedMap tzNames= new TreeMap(String.CASE_INSENSITIVE_ORDER); /** @@ -777,9 +781,6 @@ private static class TimeZoneStrategy extends Strategy { TimeZoneStrategy(final Locale locale) { final String[][] zones = DateFormatSymbols.getInstance(locale).getZoneStrings(); for (final String[] zone : zones) { - if (zone[ID].startsWith("GMT")) { - continue; - } final TimeZone tz = TimeZone.getTimeZone(zone[ID]); if (!tzNames.containsKey(zone[LONG_STD])){ tzNames.put(zone[LONG_STD], tz); @@ -795,16 +796,7 @@ private static class TimeZoneStrategy extends Strategy { tzNames.put(zone[SHORT_DST], tz); } } - } - - final StringBuilder sb= new StringBuilder(); - sb.append("(GMT[+-]\\d{1,2}:\\d{2}").append('|'); - sb.append("[+-]\\d{4}").append('|'); - for(final String id : tzNames.keySet()) { - escapeRegex(sb, id, false).append('|'); - } - sb.setCharAt(sb.length()-1, ')'); - validTimeZoneChars= sb.toString(); + } } /** @@ -812,7 +804,7 @@ private static class TimeZoneStrategy extends Strategy { */ @Override boolean addRegex(final FastDateParser parser, final StringBuilder regex) { - regex.append(validTimeZoneChars); + regex.append(VALID_TZ); return true; } @@ -825,8 +817,8 @@ void setCalendar(final FastDateParser parser, final Calendar cal, final String v if(value.charAt(0)=='+' || value.charAt(0)=='-') { tz= TimeZone.getTimeZone("GMT"+value); } - else if(value.startsWith("GMT")) { - tz= TimeZone.getTimeZone(value); + else if(value.regionMatches(true, 0, "GMT", 0, 3)) { + tz= TimeZone.getTimeZone(value.toUpperCase()); } else { tz= tzNames.get(value); diff --git a/src/test/java/org/apache/commons/lang3/reflect/FieldUtilsTest.java b/src/test/java/org/apache/commons/lang3/reflect/FieldUtilsTest.java index 36c232c94..284579dec 100644 --- a/src/test/java/org/apache/commons/lang3/reflect/FieldUtilsTest.java +++ b/src/test/java/org/apache/commons/lang3/reflect/FieldUtilsTest.java @@ -24,8 +24,8 @@ import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.Modifier; -import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.List; import static org.junit.Assert.*; @@ -146,26 +146,36 @@ public void testGetFieldForceAccessIllegalArgumentException4() { FieldUtils.getField(PublicChild.class, " ", true); } + private Field[] allPublicChildFields() { + Class parentClass = PublicChild.class.getSuperclass(); + final Field[] fieldsParent = parentClass.getDeclaredFields(); + assertArrayEquals(fieldsParent, FieldUtils.getAllFields(parentClass)); + + final Field[] fieldsPublicChild = PublicChild.class.getDeclaredFields(); + return ArrayUtils.addAll(fieldsPublicChild, fieldsParent); + } + + private Field[] allIntegerFields() { + final Field[] fieldsNumber = Number.class.getDeclaredFields(); + assertArrayEquals(Number.class.getDeclaredFields(), FieldUtils.getAllFields(Number.class)); + final Field[] fieldsInteger = Integer.class.getDeclaredFields(); + return ArrayUtils.addAll(fieldsInteger, fieldsNumber); + } + @Test public void testGetAllFields() { assertArrayEquals(new Field[0], FieldUtils.getAllFields(Object.class)); - final Field[] fieldsNumber = Number.class.getDeclaredFields(); - assertArrayEquals(fieldsNumber, FieldUtils.getAllFields(Number.class)); - final Field[] fieldsInteger = Integer.class.getDeclaredFields(); - assertArrayEquals(ArrayUtils.addAll(fieldsInteger, fieldsNumber), FieldUtils.getAllFields(Integer.class)); - assertEquals(5, FieldUtils.getAllFields(PublicChild.class).length); + assertArrayEquals(allIntegerFields(), FieldUtils.getAllFields(Integer.class)); + + assertArrayEquals(allPublicChildFields(), FieldUtils.getAllFields(PublicChild.class)); } @Test public void testGetAllFieldsList() { - assertEquals(0, FieldUtils.getAllFieldsList(Object.class).size()); - final List fieldsNumber = Arrays.asList(Number.class.getDeclaredFields()); - assertEquals(fieldsNumber, FieldUtils.getAllFieldsList(Number.class)); - final List fieldsInteger = Arrays.asList(Integer.class.getDeclaredFields()); - final List allFieldsInteger = new ArrayList(fieldsInteger); - allFieldsInteger.addAll(fieldsNumber); - assertEquals(allFieldsInteger, FieldUtils.getAllFieldsList(Integer.class)); - assertEquals(5, FieldUtils.getAllFieldsList(PublicChild.class).size()); + assertEquals(Collections.emptyList(), FieldUtils.getAllFieldsList(Object.class)); + assertEquals(Arrays.asList(allIntegerFields()), FieldUtils.getAllFieldsList(Integer.class)); + + assertEquals(Arrays.asList(allPublicChildFields()), FieldUtils.getAllFieldsList(PublicChild.class)); } @Test diff --git a/src/test/java/org/apache/commons/lang3/time/FastDateParserSDFTest.java b/src/test/java/org/apache/commons/lang3/time/FastDateParserSDFTest.java index bdf5e27f8..75ca3428c 100644 --- a/src/test/java/org/apache/commons/lang3/time/FastDateParserSDFTest.java +++ b/src/test/java/org/apache/commons/lang3/time/FastDateParserSDFTest.java @@ -27,7 +27,6 @@ import java.util.Locale; import java.util.TimeZone; -import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; @@ -138,13 +137,11 @@ public void testUpperCasePP() throws Exception { } @Test - @Ignore // not currently supported public void testLowerCase() throws Exception { checkParse(input.toLowerCase(locale)); } @Test - @Ignore // not currently supported public void testLowerCasePP() throws Exception { checkParsePosition(input.toLowerCase(locale)); } 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 b75d0861e..c2a387f48 100644 --- a/src/test/java/org/apache/commons/lang3/time/FastDateParserTest.java +++ b/src/test/java/org/apache/commons/lang3/time/FastDateParserTest.java @@ -21,6 +21,7 @@ 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.Calendar; @@ -30,6 +31,7 @@ import java.util.Locale; import java.util.Map; import java.util.TimeZone; +import java.util.regex.Pattern; import org.apache.commons.lang3.SerializationUtils; import org.junit.Assert; @@ -260,21 +262,21 @@ public void testParses() throws Exception { @Test public void testTzParses() throws Exception { // Check that all Locales can parse the time formats we use - for(final Locale locale : Locale.getAvailableLocales()) { - final FastDateParser fdp= new FastDateParser("yyyy/MM/dd z", TimeZone.getDefault(), locale); + for(final Locale locale : Locale.getAvailableLocales()) { + final FastDateParser fdp= new FastDateParser("yyyy/MM/dd z", TimeZone.getDefault(), locale); - for(final TimeZone tz : new TimeZone[]{NEW_YORK, REYKJAVIK, GMT}) { - final Calendar cal= Calendar.getInstance(tz, locale); - cal.clear(); - cal.set(Calendar.YEAR, 2000); - cal.set(Calendar.MONTH, 1); - cal.set(Calendar.DAY_OF_MONTH, 10); - final Date expected= cal.getTime(); + for(final TimeZone tz : new TimeZone[]{NEW_YORK, REYKJAVIK, GMT}) { + final Calendar cal= Calendar.getInstance(tz, locale); + cal.clear(); + cal.set(Calendar.YEAR, 2000); + cal.set(Calendar.MONTH, 1); + cal.set(Calendar.DAY_OF_MONTH, 10); + final Date expected= cal.getTime(); - final Date actual = fdp.parse("2000/02/10 "+tz.getDisplayName(locale)); - Assert.assertEquals("tz:"+tz.getID()+" locale:"+locale.getDisplayName(), expected, actual); - } - } + final Date actual = fdp.parse("2000/02/10 "+tz.getDisplayName(locale)); + Assert.assertEquals("tz:"+tz.getID()+" locale:"+locale.getDisplayName(), expected, actual); + } + } } @@ -640,4 +642,19 @@ public void test1806() throws ParseException { } } + @Test + public void testTimeZoneStrategyPattern() { + Pattern tz = Pattern.compile(FastDateParser.TimeZoneStrategy.TZ_DATABASE); + Assert.assertFalse(tz.matcher("GMT-1234").matches()); + + for (Locale locale : Locale.getAvailableLocales()) { + final String[][] zones = DateFormatSymbols.getInstance(locale).getZoneStrings(); + for (final String[] zone : zones) { + for (String zoneExpr : zone) { + Assert.assertTrue(locale.getDisplayName() + ":" + zoneExpr, tz.matcher(zoneExpr).matches()); + } + } + } + } + }