Skip to content

Commit

Permalink
LANG-1133 FastDateParser_TimeZoneStrategyTest#testTimeZoneStrategyPat…
Browse files Browse the repository at this point in the history
…tern fails on Windows with German Locale

reimplementing LANG-1107
  • Loading branch information
chonton committed May 9, 2015
1 parent a9a73a7 commit 71d7c32
Show file tree
Hide file tree
Showing 3 changed files with 67 additions and 50 deletions.
1 change: 1 addition & 0 deletions src/changes/changes.xml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
<body>

<release version="3.5" date="tba" description="tba">
<action issue="LANG-1133" type="bug" dev="chas" due-to="Pascal Schumacher">FastDateParser_TimeZoneStrategyTest#testTimeZoneStrategyPattern fails on Windows with German Locale</action>
<action issue="LANG-1135" type="add" dev="britter" due-to="Eduardo Martins">Add method containsAllWords to WordUtils</action>
<action issue="LANG-1132" type="add" dev="britter" due-to="Jack Tan">ReflectionToStringBuilder doesn't throw IllegalArgumentException when the constructor's object param is null</action>
<action issue="LANG-1122" type="fix" dev="britter" due-to="Adrian Ber">Inconsistent behavior of swap for malformed inputs</action>
Expand Down
88 changes: 49 additions & 39 deletions src/main/java/org/apache/commons/lang3/time/FastDateParser.java
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,7 @@
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.SortedMap;
import java.util.TimeZone;
import java.util.TreeMap;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.regex.Matcher;
Expand Down Expand Up @@ -369,6 +367,30 @@ public Date parse(final String source, final ParsePosition pos) {
// Support for strategies
//-----------------------------------------------------------------------

private static StringBuilder simpleQuote(final StringBuilder sb, final String value) {
for(int i= 0; i<value.length(); ++i) {
char c= value.charAt(i);
switch(c) {
case '\\':
case '^':
case '$':
case '.':
case '|':
case '?':
case '*':
case '+':
case '(':
case ')':
case '[':
case '{':
sb.append('\\');
default:
sb.append(c);
}
}
return sb;
}

/**
* Escape constant fields into regular expression
* @param regex The destination regex
Expand Down Expand Up @@ -667,7 +689,7 @@ private static class CaseInsensitiveTextStrategy extends Strategy {
boolean addRegex(final FastDateParser parser, final StringBuilder regex) {
regex.append("((?iu)");
for(final String textKeyValue : lKeyValues.keySet()) {
escapeRegex(regex, textKeyValue, false).append('|');
simpleQuote(regex, textKeyValue).append('|');
}
regex.setCharAt(regex.length()-1, ')');
return true;
Expand Down Expand Up @@ -768,64 +790,52 @@ void setCalendar(final FastDateParser parser, final Calendar cal, final String v
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<String, TimeZone> tzNames= new TreeMap<String, TimeZone>(String.CASE_INSENSITIVE_ORDER);
private final Locale locale;
private final Map<String, TimeZone> tzNames= new HashMap<String, TimeZone>();
private final String validTimeZoneChars;

/**
* Index of zone id
*/
private static final int ID = 0;
/**
* Index of the long name of zone in standard time
*/
private static final int LONG_STD = 1;
/**
* Index of the short name of zone in standard time
*/
private static final int SHORT_STD = 2;
/**
* Index of the long name of zone in daylight saving time
*/
private static final int LONG_DST = 3;
/**
* Index of the short name of zone in daylight saving time
*/
private static final int SHORT_DST = 4;

/**
* Construct a Strategy that parses a TimeZone
* @param locale The Locale
*/
TimeZoneStrategy(final Locale locale) {
this.locale = locale;

final StringBuilder sb = new StringBuilder();
sb.append('(' + RFC_822_TIME_ZONE + "|(?iu)" + GMT_OPTION );

final String[][] zones = DateFormatSymbols.getInstance(locale).getZoneStrings();
for (final String[] zone : zones) {
final TimeZone tz = TimeZone.getTimeZone(zone[ID]);
if (!tzNames.containsKey(zone[LONG_STD])){
tzNames.put(zone[LONG_STD], tz);
}
if (!tzNames.containsKey(zone[SHORT_STD])){
tzNames.put(zone[SHORT_STD], tz);
for (final String[] zoneNames : zones) {
final String tzId = zoneNames[ID];
if (tzId.equalsIgnoreCase("GMT")) {
continue;
}
if (tz.useDaylightTime()) {
if (!tzNames.containsKey(zone[LONG_DST])){
tzNames.put(zone[LONG_DST], tz);
}
if (!tzNames.containsKey(zone[SHORT_DST])){
tzNames.put(zone[SHORT_DST], tz);
final TimeZone tz = TimeZone.getTimeZone(tzId);
for(int i= 1; i<zoneNames.length; ++i) {
String zoneName = zoneNames[i].toLowerCase(locale);
if (!tzNames.containsKey(zoneName)){
tzNames.put(zoneName, tz);
simpleQuote(sb.append('|'), zoneName);
}
}
}
}

sb.append(')');
validTimeZoneChars = sb.toString();
}

/**
* {@inheritDoc}
*/
@Override
boolean addRegex(final FastDateParser parser, final StringBuilder regex) {
regex.append(VALID_TZ);
regex.append(validTimeZoneChars);
return true;
}

Expand All @@ -842,7 +852,7 @@ else if(value.regionMatches(true, 0, "GMT", 0, 3)) {
tz= TimeZone.getTimeZone(value.toUpperCase());
}
else {
tz= tzNames.get(value);
tz= tzNames.get(value.toLowerCase(locale));
if(tz==null) {
throw new IllegalArgumentException(value + " is not a supported timezone name");
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,27 +16,33 @@
*/
package org.apache.commons.lang3.time;

import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;

import java.text.DateFormatSymbols;
import java.util.Locale;
import java.util.regex.Pattern;
import java.util.TimeZone;

import org.junit.Assert;
import org.junit.Test;

public class FastDateParser_TimeZoneStrategyTest {

@Test
public void testTimeZoneStrategyPattern() {
Pattern tz = Pattern.compile(FastDateParser.TimeZoneStrategy.TZ_DATABASE);
assertFalse(tz.matcher("GMT-1234").matches());

for (Locale locale : Locale.getAvailableLocales()) {
for(final Locale locale : Locale.getAvailableLocales()) {
final FastDateParser parser = new FastDateParser("z", TimeZone.getDefault(), locale);
final String[][] zones = DateFormatSymbols.getInstance(locale).getZoneStrings();
for (final String[] zone : zones) {
for (String zoneExpr : zone) {
assertTrue(locale.getDisplayName() + ":" + zoneExpr, tz.matcher(zoneExpr).matches());
for(final String[] zone : zones) {
for(int t = 1; t<zone.length; ++t) {
final String tzDisplay = zone[t];

try {
parser.parse(tzDisplay);
}
catch(Exception ex) {
Assert.fail(tzDisplay
+ " Locale: " + locale.getDisplayName()
+ " TimeZone: " + zone[0]
+ " offset: " + t);
}
}
}
}
Expand Down

0 comments on commit 71d7c32

Please sign in to comment.