Repository: commons-lang
Updated Branches:
  refs/heads/master 38bafd283 -> 3c92830ec


LANG-1219: FastDateFormat doesn't respect summer daylight in some localized 
strings


Project: http://git-wip-us.apache.org/repos/asf/commons-lang/repo
Commit: http://git-wip-us.apache.org/repos/asf/commons-lang/commit/3c92830e
Tree: http://git-wip-us.apache.org/repos/asf/commons-lang/tree/3c92830e
Diff: http://git-wip-us.apache.org/repos/asf/commons-lang/diff/3c92830e

Branch: refs/heads/master
Commit: 3c92830ec51fb6b6e9a7ca2fb8fda324cd33e48a
Parents: 38bafd2
Author: Chas Honton <[email protected]>
Authored: Sat Apr 23 13:02:54 2016 -0700
Committer: Chas Honton <[email protected]>
Committed: Sat Apr 23 13:02:59 2016 -0700

----------------------------------------------------------------------
 src/changes/changes.xml                         |   2 +
 .../commons/lang3/time/FastDateParser.java      | 118 +++++++++++--------
 .../FastDateParser_TimeZoneStrategyTest.java    |  11 ++
 3 files changed, 84 insertions(+), 47 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/commons-lang/blob/3c92830e/src/changes/changes.xml
----------------------------------------------------------------------
diff --git a/src/changes/changes.xml b/src/changes/changes.xml
index 44458a8..35c9185 100644
--- a/src/changes/changes.xml
+++ b/src/changes/changes.xml
@@ -22,6 +22,8 @@
   <body>
 
   <release version="3.5" date="tba" description="tba">
+  
+    <action issue="LANG-1219" type="fix" dev="chas" 
due-to="Jarek">FastDateFormat doesn't respect summer daylight in some localized 
strings</action>
     <action issue="LANG-1201" type="add" dev="ggregory" due-to="Gary 
Gregory">Add a TimeUnit-like classes for base 2 and base 10 digital conversions 
(bits, bytes, KB, MB, and so on)</action>
     <action issue="LANG-1146" type="add" dev="ggregory" due-to="Gabor 
Liptak">z/OS identification in SystemUtils</action>
     <action issue="LANG-1210" type="update" dev="ggregory" due-to="Matthias 
Niehoff">StringUtils#startsWithAny has error in Javadoc</action>

http://git-wip-us.apache.org/repos/asf/commons-lang/blob/3c92830e/src/main/java/org/apache/commons/lang3/time/FastDateParser.java
----------------------------------------------------------------------
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 7b20903..62f3577 100644
--- a/src/main/java/org/apache/commons/lang3/time/FastDateParser.java
+++ b/src/main/java/org/apache/commons/lang3/time/FastDateParser.java
@@ -31,7 +31,6 @@ import java.util.List;
 import java.util.ListIterator;
 import java.util.Locale;
 import java.util.Map;
-import java.util.Map.Entry;
 import java.util.Set;
 import java.util.TimeZone;
 import java.util.TreeSet;
@@ -92,7 +91,16 @@ public class FastDateParser implements DateParser, 
Serializable {
     // derived fields
     private transient List<StrategyAndWidth> patterns;
 
-    
+    // comparator used to sort regex alternatives
+    // alternatives should be ordered longer first, and shorter last. 
('february' before 'feb')
+    // all entries must be lowercase by locale.
+    private static final Comparator<String> LONGER_FIRST_LOWERCASE = new 
Comparator<String>() {
+        @Override
+        public int compare(String left, String right) {
+            return right.compareTo(left);
+        }
+    };
+
     /**
      * <p>Constructs a new FastDateParser.</p>
      * 
@@ -453,42 +461,28 @@ public class FastDateParser implements DateParser, 
Serializable {
     }
 
     /**
-     * alternatives should be ordered longer first, and shorter last.  
comparisons should be case insensitive.
-     */
-    private static final Comparator<Map.Entry<String, Integer>> 
ALTERNATIVES_ORDERING = new Comparator<Map.Entry<String, Integer>>() {
-        @Override
-        public int compare(Map.Entry<String, Integer> left, Map.Entry<String, 
Integer> right) {
-            int v = left.getValue() - right.getValue();
-            if (v != 0) {
-                return v;
-            }
-            return right.getKey().compareToIgnoreCase(left.getKey());
-        }
-    };
-
-    /**
      * Get the short and long values displayed for a field
      * @param cal The calendar to obtain the short and long values
      * @param locale The locale of display names
      * @param field The field of interest
      * @param regex The regular expression to build
-     * @param vales The map to fill
+     * @return The map of string display names to field values
      */
-    private static void appendDisplayNames(Calendar cal, Locale locale, int 
field, 
-            StringBuilder regex, Map<String, Integer> values) {
-
-        Set<Entry<String, Integer>> displayNames = cal.getDisplayNames(field, 
Calendar.ALL_STYLES, locale).entrySet();
-        TreeSet<Map.Entry<String, Integer>> sort = new 
TreeSet<Map.Entry<String, Integer>>(ALTERNATIVES_ORDERING);
-        sort.addAll(displayNames);
-
-        for (Map.Entry<String, Integer> entry : sort) {
-            String symbol = entry.getKey();
-            if (symbol.length() > 0) {
-                if (values.put(symbol.toLowerCase(locale), entry.getValue()) 
== null) {
-                    simpleQuote(regex, symbol).append('|');
-                }
+    private static Map<String, Integer> appendDisplayNames(Calendar cal, 
Locale locale, int field, StringBuilder regex) {
+        Map<String, Integer> values = new HashMap<String, Integer>();
+
+        Map<String, Integer> displayNames = cal.getDisplayNames(field, 
Calendar.ALL_STYLES, locale);
+        TreeSet<String> sorted = new TreeSet<String>(LONGER_FIRST_LOWERCASE);
+        for (Map.Entry<String, Integer> displayName : displayNames.entrySet()) 
{
+            String key = displayName.getKey().toLowerCase(locale);
+            if (sorted.add(key)) {
+                values.put(key, displayName.getValue());
             }
         }
+        for (String symbol : sorted) {
+            simpleQuote(regex, symbol).append('|');
+        }
+        return values;
     }
 
     /**
@@ -703,7 +697,7 @@ public class FastDateParser implements DateParser, 
Serializable {
      private static class CaseInsensitiveTextStrategy extends PatternStrategy {
         private final int field;
         final Locale locale;
-        private final Map<String, Integer> lKeyValues = new 
HashMap<String,Integer>();
+        private final Map<String, Integer> lKeyValues;
 
         /**
          * Construct a Strategy that parses a Text field
@@ -717,7 +711,7 @@ public class FastDateParser implements DateParser, 
Serializable {
             
             StringBuilder regex = new StringBuilder();
             regex.append("((?iu)");
-            appendDisplayNames(definingCalendar, locale, field, regex, 
lKeyValues);
+            lKeyValues = appendDisplayNames(definingCalendar, locale, field, 
regex);
             regex.setLength(regex.length()-1);
             regex.append(")");
             createPattern(regex);
@@ -826,8 +820,18 @@ public class FastDateParser implements DateParser, 
Serializable {
         private static final String GMT_OPTION= "GMT[+-]\\d{1,2}:\\d{2}";
 
         private final Locale locale;
-        private final Map<String, TimeZone> tzNames= new HashMap<String, 
TimeZone>();
- 
+        private final Map<String, TzInfo> tzNames= new HashMap<String, 
TzInfo>();
+
+        private static class TzInfo {
+            TimeZone zone;
+            int dstOffset;
+
+            TzInfo(TimeZone tz, boolean useDst) {
+                zone = tz;
+                dstOffset = useDst ?tz.getDSTSavings() :0;
+            }
+        }
+
         /**
          * Index of zone id
          */
@@ -839,30 +843,48 @@ public class FastDateParser implements DateParser, 
Serializable {
          * @param locale The Locale
          */
         TimeZoneStrategy(Calendar cal, final Locale locale) {
-
             this.locale = locale;
 
             final StringBuilder sb = new StringBuilder();
-            sb.append('(' + RFC_822_TIME_ZONE + "|(?iu)" + GMT_OPTION );
+            sb.append("((?iu)" + RFC_822_TIME_ZONE + "|" + GMT_OPTION );
+
+            final Set<String> sorted = new 
TreeSet<String>(LONGER_FIRST_LOWERCASE);
 
             final String[][] zones = 
DateFormatSymbols.getInstance(locale).getZoneStrings();
             for (final String[] zoneNames : zones) {
+                // offset 0 is the time zone ID and is not localized
                 final String tzId = zoneNames[ID];
                 if (tzId.equalsIgnoreCase("GMT")) {
                     continue;
                 }
                 final TimeZone tz = TimeZone.getTimeZone(tzId);
-                for(int i= 1; i<zoneNames.length; ++i) {
-                    String zoneName = zoneNames[i];
-                    if (zoneName == null) {
+                // offset 1 is long standard name
+                // offset 2 is short standard name
+                TzInfo standard = new TzInfo(tz, false);
+                TzInfo tzInfo = standard;
+                for (int i = 1; i < zoneNames.length; ++i) {
+                    switch (i) {
+                    case 3: // offset 3 is long daylight savings (or 
summertime) name
+                            // offset 4 is the short summertime name
+                        tzInfo = new TzInfo(tz, true);
+                        break;
+                    case 5: // offset 5 starts additional names, probably 
standard time
+                        tzInfo = standard;
                         break;
                     }
-                    if (tzNames.put(zoneName.toLowerCase(locale), tz) == null) 
{
-                        simpleQuote(sb.append('|'), zoneName);
+                    String key = zoneNames[i].toLowerCase(locale);
+                    // ignore the data associated with duplicates supplied in
+                    // the additional names
+                    if (sorted.add(key)) {
+                        tzNames.put(key, tzInfo);
                     }
                 }
             }
-
+            // order the regex alternatives with longer strings first, greedy
+            // match will ensure longest string will be consumed
+            for (String zoneName : sorted) {
+                simpleQuote(sb.append('|'), zoneName);
+            }
             sb.append(")");
             createPattern(sb);
         }
@@ -872,15 +894,17 @@ public class FastDateParser implements DateParser, 
Serializable {
          */
         @Override
         void setCalendar(final FastDateParser parser, final Calendar cal, 
final String value) {
-            TimeZone tz;
             if (value.charAt(0) == '+' || value.charAt(0) == '-') {
-                tz = TimeZone.getTimeZone("GMT" + value);
+                TimeZone tz = TimeZone.getTimeZone("GMT" + value);
+                cal.setTimeZone(tz);
             } else if (value.regionMatches(true, 0, "GMT", 0, 3)) {
-                tz = TimeZone.getTimeZone(value.toUpperCase());
+                TimeZone tz = TimeZone.getTimeZone(value.toUpperCase());
+                cal.setTimeZone(tz);
             } else {
-                tz = tzNames.get(value.toLowerCase(locale));
+                TzInfo tzInfo = tzNames.get(value.toLowerCase(locale));
+                cal.set(Calendar.DST_OFFSET, tzInfo.dstOffset);
+                cal.set(Calendar.ZONE_OFFSET, tzInfo.zone.getRawOffset());
             }
-            cal.setTimeZone(tz);
         }
     }
     

http://git-wip-us.apache.org/repos/asf/commons-lang/blob/3c92830e/src/test/java/org/apache/commons/lang3/time/FastDateParser_TimeZoneStrategyTest.java
----------------------------------------------------------------------
diff --git 
a/src/test/java/org/apache/commons/lang3/time/FastDateParser_TimeZoneStrategyTest.java
 
b/src/test/java/org/apache/commons/lang3/time/FastDateParser_TimeZoneStrategyTest.java
index be08454..2ed0c7f 100644
--- 
a/src/test/java/org/apache/commons/lang3/time/FastDateParser_TimeZoneStrategyTest.java
+++ 
b/src/test/java/org/apache/commons/lang3/time/FastDateParser_TimeZoneStrategyTest.java
@@ -17,6 +17,8 @@
 package org.apache.commons.lang3.time;
 
 import java.text.DateFormatSymbols;
+import java.text.ParseException;
+import java.util.Date;
 import java.util.Locale;
 import java.util.TimeZone;
 
@@ -52,4 +54,13 @@ public class FastDateParser_TimeZoneStrategyTest {
             }
         }
     }
+
+    @Test
+    public void testLang1219() throws ParseException {
+        FastDateParser parser = new FastDateParser("dd.MM.yyyy HH:mm:ss z", 
TimeZone.getDefault(), Locale.GERMAN);
+
+        Date summer = parser.parse("26.10.2014 02:00:00 MESZ");
+        Date standard = parser.parse("26.10.2014 02:00:00 MEZ");
+        Assert.assertNotEquals(summer.getTime(), standard.getTime());
+    }
 }

Reply via email to