You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@commons.apache.org by ch...@apache.org on 2015/07/08 05:20:34 UTC

[1/2] [lang] refactor FastDateParser

Repository: commons-lang
Updated Branches:
  refs/heads/master 3ff366c3d -> 40134ecdb


refactor FastDateParser

use hashmap for performance
    break down regular expressions to per-format, allowing
ParsePosition to get set
    add parse with Calendar input, allowing client to set leniency
and/or replace display names


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

Branch: refs/heads/master
Commit: 94faa31bcf5c4fcb20818a3a0d23ae789932d2df
Parents: 612236c
Author: Chas Honton <ch...@apache.org>
Authored: Thu Jun 11 20:07:13 2015 -0700
Committer: Chas Honton <ch...@apache.org>
Committed: Thu Jun 11 20:07:13 2015 -0700

----------------------------------------------------------------------
 .../apache/commons/lang3/time/DateUtils.java    |  15 +-
 .../commons/lang3/time/FastDateParser.java      | 588 ++++++++++---------
 .../commons/lang3/time/FastDateFormatTest.java  |   4 +-
 .../lang3/time/FastDateParserSDFTest.java       |  20 +-
 .../commons/lang3/time/FastDateParserTest.java  |  28 +-
 .../time/FastDateParser_MoreOrLessTest.java     | 113 ++++
 6 files changed, 458 insertions(+), 310 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/commons-lang/blob/94faa31b/src/main/java/org/apache/commons/lang3/time/DateUtils.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/commons/lang3/time/DateUtils.java b/src/main/java/org/apache/commons/lang3/time/DateUtils.java
index c49dbbb..2ae637b 100644
--- a/src/main/java/org/apache/commons/lang3/time/DateUtils.java
+++ b/src/main/java/org/apache/commons/lang3/time/DateUtils.java
@@ -368,18 +368,21 @@ public class DateUtils {
         final TimeZone tz = TimeZone.getDefault();
         final Locale lcl = locale==null ?Locale.getDefault() : locale;
         final ParsePosition pos = new ParsePosition(0);
+        final Calendar calendar = Calendar.getInstance(tz, lcl);
+        calendar.setLenient(lenient);
 
         for (final String parsePattern : parsePatterns) {
-            FastDateParser fdp = new FastDateParser(parsePattern, tz, lcl, null, lenient);
+            FastDateParser fdp = new FastDateParser(parsePattern, tz, lcl);
+            calendar.clear();
             try {
-                Date date = fdp.parse(str, pos);
-                if (pos.getIndex() == str.length()) {
-                    return date;
+                if (fdp.parse(str, pos, calendar) && pos.getIndex()==str.length()) {
+                    return calendar.getTime();
                 }
-                pos.setIndex(0);
             }
-            catch(IllegalArgumentException iae) {
+            catch(IllegalArgumentException ignore) {
+                // leniency is preventing calendar from being set
             }
+            pos.setIndex(0);
         }
         throw new ParseException("Unable to parse the date: " + str, -1);
     }

http://git-wip-us.apache.org/repos/asf/commons-lang/blob/94faa31b/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 7863322..26538d7 100644
--- a/src/main/java/org/apache/commons/lang3/time/FastDateParser.java
+++ b/src/main/java/org/apache/commons/lang3/time/FastDateParser.java
@@ -24,12 +24,17 @@ import java.text.ParseException;
 import java.text.ParsePosition;
 import java.util.ArrayList;
 import java.util.Calendar;
+import java.util.Comparator;
 import java.util.Date;
 import java.util.HashMap;
 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;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.ConcurrentMap;
 import java.util.regex.Matcher;
@@ -67,6 +72,7 @@ import java.util.regex.Pattern;
  * @see FastDatePrinter
  */
 public class FastDateParser implements DateParser, Serializable {
+
     /**
      * Required for serialization support.
      *
@@ -82,16 +88,11 @@ public class FastDateParser implements DateParser, Serializable {
     private final Locale locale;
     private final int century;
     private final int startYear;
-    private final boolean lenient;
 
     // derived fields
-    private transient Pattern parsePattern;
-    private transient Strategy[] strategies;
-
-    // dynamic fields to communicate with Strategy
-    private transient String currentFormatField;
-    private transient Strategy nextStrategy;
+    private transient List<StrategyAndWidth> patterns;
 
+    
     /**
      * <p>Constructs a new FastDateParser.</p>
      * 
@@ -104,22 +105,7 @@ public class FastDateParser implements DateParser, Serializable {
      * @param locale non-null locale
      */
     protected FastDateParser(final String pattern, final TimeZone timeZone, final Locale locale) {
-        this(pattern, timeZone, locale, null, true);
-    }
-
-    /**
-     * <p>Constructs a new FastDateParser.</p>
-     *
-     * @param pattern non-null {@link java.text.SimpleDateFormat} compatible
-     *  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
-     *
-     * @since 3.3
-     */
-    protected FastDateParser(final String pattern, final TimeZone timeZone, final Locale locale, final Date centuryStart) {
-        this(pattern, timeZone, locale, centuryStart, true);
+        this(pattern, timeZone, locale, null);
     }
 
     /**
@@ -135,12 +121,10 @@ public class FastDateParser implements DateParser, Serializable {
      *
      * @since 3.5
      */
-    protected FastDateParser(final String pattern, final TimeZone timeZone, final Locale locale,
-            final Date centuryStart, final boolean lenient) {
+    protected FastDateParser(final String pattern, final TimeZone timeZone, final Locale locale, final Date centuryStart) {
         this.pattern = pattern;
         this.timeZone = timeZone;
         this.locale = locale;
-        this.lenient = lenient;
 
         final Calendar definingCalendar = Calendar.getInstance(timeZone, locale);
 
@@ -170,41 +154,112 @@ public class FastDateParser implements DateParser, Serializable {
      * @param definingCalendar the {@link java.util.Calendar} instance used to initialize this FastDateParser
      */
     private void init(final Calendar definingCalendar) {
+        patterns = new ArrayList<StrategyAndWidth>();
 
-        final StringBuilder regex= new StringBuilder();
-        final List<Strategy> collector = new ArrayList<Strategy>();
+        StrategyParser fm = new StrategyParser(pattern, definingCalendar);
+        for(;;) {
+            StrategyAndWidth field = fm.getNextStrategy();
+            if(field==null) {
+                break;
+            }
+            patterns.add(field);
+        }
+    }
+
+    // helper classes to parse the format string
+    //-----------------------------------------------------------------------
 
-        final Matcher patternMatcher= formatPattern.matcher(pattern);
-        if(!patternMatcher.lookingAt()) {
-            throw new IllegalArgumentException(
-                    "Illegal pattern character '" + pattern.charAt(patternMatcher.regionStart()) + "'");
+    /**
+     * Struct to hold strategy and filed width 
+     */
+    private static class StrategyAndWidth {
+        final Strategy strategy;
+        final int width;
+
+        StrategyAndWidth(Strategy strategy, int width) {
+            this.strategy = strategy;
+            this.width = width;
         }
 
-        currentFormatField= patternMatcher.group();
-        Strategy currentStrategy= getStrategy(currentFormatField, definingCalendar);
-        for(;;) {
-            patternMatcher.region(patternMatcher.end(), patternMatcher.regionEnd());
-            if(!patternMatcher.lookingAt()) {
-                nextStrategy = null;
-                break;
+        int getMaxWidth(ListIterator<StrategyAndWidth> lt) {
+            if(!strategy.isNumber() || !lt.hasNext()) {
+                return 0;
             }
-            final String nextFormatField= patternMatcher.group();
-            nextStrategy = getStrategy(nextFormatField, definingCalendar);
-            if(currentStrategy.addRegex(this, regex)) {
-                collector.add(currentStrategy);
+            Strategy nextStrategy = lt.next().strategy;
+            lt.previous();
+            return nextStrategy.isNumber() ?width :0;
+       }
+    }
+
+    /**
+     * Parse format into Strategies
+     */
+    private class StrategyParser {
+        final private String pattern;
+        final private Calendar definingCalendar;
+        private int currentIdx;
+
+        StrategyParser(String pattern, Calendar definingCalendar) {
+           this.pattern = pattern;
+           this.definingCalendar = definingCalendar;
+        }
+
+        StrategyAndWidth getNextStrategy() {
+            if(currentIdx >= pattern.length()) {
+                return null;
+            }
+
+            char c = pattern.charAt(currentIdx);
+            if( isFormatLetter(c)) {
+                return letterPattern(c);
+            }
+            else {
+                return literal();
             }
-            currentFormatField= nextFormatField;
-            currentStrategy= nextStrategy;
         }
-        if (patternMatcher.regionStart() != patternMatcher.regionEnd()) {
-            throw new IllegalArgumentException("Failed to parse \""+pattern+"\" ; gave up at index "+patternMatcher.regionStart());
+
+        private StrategyAndWidth letterPattern(char c) {
+            int begin = currentIdx;
+            while( ++currentIdx<pattern.length() ) {
+                if(pattern.charAt(currentIdx) != c) {
+                    break;
+                }
+            }
+
+            int width = currentIdx - begin;
+            return new StrategyAndWidth(getStrategy(c, width, definingCalendar), width);
         }
-        if(currentStrategy.addRegex(this, regex)) {
-            collector.add(currentStrategy);
+
+        private StrategyAndWidth literal() {
+            boolean activeQuote = false;
+
+            StringBuilder sb = new StringBuilder();
+            while( currentIdx<pattern.length() ) {
+                char c= pattern.charAt(currentIdx);
+                if( !activeQuote && isFormatLetter( c ) ) {
+                    break;
+                }
+                else if( c=='\'' ) {
+                    if(++currentIdx==pattern.length() || pattern.charAt(currentIdx)!='\'') {
+                        activeQuote = !activeQuote;
+                        continue;
+                    }
+                }
+                ++currentIdx;
+                sb.append(c);
+            }
+
+            if(activeQuote) {
+                throw new IllegalArgumentException("Unterminated quote");
+            }
+
+            String formatField = sb.toString();
+            return new StrategyAndWidth(new CopyQuotedStrategy(formatField), formatField.length());
         }
-        currentFormatField= null;
-        strategies= collector.toArray(new Strategy[collector.size()]);
-        parsePattern= Pattern.compile(regex.toString());
+    }
+
+    private static boolean isFormatLetter(char c) {
+        return c>='A' && c<='Z' || c>='a' && c<='z';
     }
 
     // Accessors
@@ -233,14 +288,6 @@ public class FastDateParser implements DateParser, Serializable {
         return locale;
     }
 
-    /**
-     * Returns the generated pattern (for testing purposes).
-     *
-     * @return the generated pattern
-     */
-    Pattern getParsePattern() {
-        return parsePattern;
-    }
 
     // Basics
     //-----------------------------------------------------------------------
@@ -311,15 +358,16 @@ public class FastDateParser implements DateParser, Serializable {
      */
     @Override
     public Date parse(final String source) throws ParseException {
-        final Date date= parse(source, new ParsePosition(0));
+        ParsePosition pp = new ParsePosition(0);
+        final Date date= parse(source, pp);
         if(date==null) {
             // Add a note re supported date range
             if (locale.equals(JAPANESE_IMPERIAL)) {
                 throw new ParseException(
                         "(The " +locale + " locale does not support dates before 1868 AD)\n" +
-                                "Unparseable date: \""+source+"\" does not match "+parsePattern.pattern(), 0);
+                                "Unparseable date: \""+source, pp.getErrorIndex());
             }
-            throw new ParseException("Unparseable date: \""+source+"\" does not match "+parsePattern.pattern(), 0);
+            throw new ParseException("Unparseable date: "+source, pp.getErrorIndex());
         }
         return date;
     }
@@ -333,9 +381,10 @@ public class FastDateParser implements DateParser, Serializable {
     }
 
     /**
-     * This implementation updates the ParsePosition if the parse succeeeds.
-     * However, unlike the method {@link java.text.SimpleDateFormat#parse(String, ParsePosition)}
-     * it is not able to set the error Index - i.e. {@link ParsePosition#getErrorIndex()} -  if the parse fails.
+     * This implementation updates the ParsePosition if the parse succeeds.
+     * However, it sets the error index to the position before the failed field unlike 
+     * the method {@link java.text.SimpleDateFormat#parse(String, ParsePosition)} which sets 
+     * the error index to after the failed field.
      * <p>
      * To determine if the parse has succeeded, the caller must check if the current parse position
      * given by {@link ParsePosition#getIndex()} has been updated. If the input buffer has been fully
@@ -346,23 +395,37 @@ public class FastDateParser implements DateParser, Serializable {
      */
     @Override
     public Date parse(final String source, final ParsePosition pos) {
-        final int offset= pos.getIndex();
-        final Matcher matcher= parsePattern.matcher(source.substring(offset));
-        if(!matcher.lookingAt()) {
-            return null;
-        }
         // timing tests indicate getting new instance is 19% faster than cloning
         final Calendar cal= Calendar.getInstance(timeZone, locale);
         cal.clear();
-        cal.setLenient(lenient);
 
-        for(int i=0; i<strategies.length;) {
-            final Strategy strategy= strategies[i++];
-            strategy.setCalendar(this, cal, matcher.group(i));
-        }
-        pos.setIndex(offset+matcher.end());
-        return cal.getTime();
+        return parse(source, pos, cal) ?cal.getTime() :null;
     }
+    
+    /**
+     * Parse a formatted date string according to the format.  Updates the Calendar with parsed fields.  
+     * Upon success, the ParsePosition index is updated to indicate how much of the source text was consumed.
+     * Not all source text needs to be consumed.  Upon parse failure, ParsePosition error index is updated to
+     * the offset of the source text which does not match the supplied format.  
+     * 
+     * @param source The text to parse.
+     * @param pos On input, the position in the source to start parsing, on output, updated position.
+     * @param calendar The calendar into which to set parsed fields.
+     * @return true, if source has been parsed (pos parsePosition is updated); otherwise false (and pos errorIndex is updated)
+     * @throws IllegalArgumentException when Calendar has been set to be not lenient, and a parsed field is
+     * out of range.
+     */
+     public boolean parse(final String source, final ParsePosition pos, final Calendar calendar) {
+         ListIterator<StrategyAndWidth> lt = patterns.listIterator();
+         while(lt.hasNext()) {
+             StrategyAndWidth pattern = lt.next();
+             int maxWidth = pattern.getMaxWidth(lt);
+             if(!pattern.strategy.parse(this, calendar, source, pos, maxWidth)) {
+                 return false;
+             }
+         }
+         return true;
+     }
 
     // Support for strategies
     //-----------------------------------------------------------------------
@@ -392,62 +455,42 @@ public class FastDateParser implements DateParser, Serializable {
     }
 
     /**
-     * Escape constant fields into regular expression
-     * @param regex The destination regex
-     * @param value The source field
-     * @param unquote If true, replace two success quotes ('') with single quote (')
-     * @return The <code>StringBuilder</code>
+     * alternatives should be ordered longer first, and shorter last.  comparisons should be case insensitive.
      */
-    private static StringBuilder escapeRegex(final StringBuilder regex, final String value, final boolean unquote) {
-        regex.append("\\Q");
-        for(int i= 0; i<value.length(); ++i) {
-            char c= value.charAt(i);
-            switch(c) {
-            case '\'':
-                if(unquote) {
-                    if(++i==value.length()) {
-                        return regex;
-                    }
-                    c= value.charAt(i);
-                }
-                break;
-            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
-                 */
-                regex.append(c); // we always want the original \
-                c = value.charAt(i); // Is it followed by E ?
-                if (c == 'E') { // \E detected
-                  regex.append("E\\\\E\\"); // see comment above
-                  c = 'Q'; // appended below
-                }
-                break;
-            default:
-                break;
+    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;
             }
-            regex.append(c);
+            return right.getKey().compareToIgnoreCase(left.getKey());
         }
-        regex.append("\\E");
-        return regex;
-    }
-
+    };
 
     /**
      * Get the short and long values displayed for a field
-     * @param field The field of interest
-     * @param definingCalendar The calendar to obtain the short and long values
+     * @param cal The calendar to obtain the short and long values
      * @param locale The locale of display names
-     * @return A Map of the field key / value pairs
+     * @param field The field of interest
+     * @param regex The regular expression to build
+     * @param vales The map to fill
      */
-    private static Map<String, Integer> getDisplayNames(final int field, final Calendar definingCalendar, final Locale locale) {
-        return definingCalendar.getDisplayNames(field, Calendar.ALL_STYLES, locale);
+    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('|');
+                }
+            }
+        }
     }
 
     /**
@@ -461,82 +504,73 @@ public class FastDateParser implements DateParser, Serializable {
     }
 
     /**
-     * Is the next field a number?
-     * @return true, if next field will be a number
+     * A strategy to parse a single field from the parsing pattern
      */
-    boolean isNextNumber() {
-        return nextStrategy!=null && nextStrategy.isNumber();
-    }
+    private static abstract class Strategy {
+        /**
+         * Is this field a number?
+         * The default implementation returns false.
+         *
+         * @return true, if field is a number
+         */
+        boolean isNumber() {
+            return false;
+        }
 
-    /**
-     * What is the width of the current field?
-     * @return The number of characters in the current format field
-     */
-    int getFieldWidth() {
-        return currentFormatField.length();
+        abstract boolean parse(FastDateParser parser, Calendar calendar, String source, ParsePosition pos, int maxWidth);
     }
 
     /**
      * A strategy to parse a single field from the parsing pattern
      */
-    private static abstract class Strategy {
-        
+    private static abstract class PatternStrategy extends Strategy {
+
+        private Pattern pattern;
+
+        void createPattern(StringBuilder regex) {
+            createPattern(regex.toString());
+        }
+
+        void createPattern(String regex) {
+            this.pattern = Pattern.compile(regex);
+        }
+
         /**
          * Is this field a number?
          * The default implementation returns false.
          *
          * @return true, if field is a number
          */
+        @Override
         boolean isNumber() {
             return false;
         }
-        
-        /**
-         * 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) {
 
+        @Override
+        boolean parse(FastDateParser parser, Calendar calendar, String source, ParsePosition pos, int maxWidth) {
+            Matcher matcher = pattern.matcher(source.substring(pos.getIndex()));
+            if(!matcher.lookingAt()) {
+                pos.setErrorIndex(pos.getIndex());
+                return false;
+            }
+            pos.setIndex(pos.getIndex() + matcher.end(1));
+            setCalendar(parser, calendar, matcher.group(1));
+            return true;
         }
-        
-        /**
-         * Generate a <code>Pattern</code> regular expression to the <code>StringBuilder</code>
-         * which will accept this field
-         * @param parser The parser calling this strategy
-         * @param regex The <code>StringBuilder</code> to append to
-         * @return true, if this field will set the calendar;
-         * false, if this field is a constant value
-         */
-        abstract boolean addRegex(FastDateParser parser, StringBuilder regex);
 
+        abstract void setCalendar(FastDateParser parser, Calendar cal, String value);
     }
 
     /**
-     * A <code>Pattern</code> to parse the user supplied SimpleDateFormat pattern
-     */
-    private static final Pattern formatPattern= Pattern.compile(
-            "D+|E+|F+|G+|H+|K+|M+|S+|W+|X+|Z+|a+|d+|h+|k+|m+|s+|w+|y+|z+|''|'[^']++(''[^']*+)*+'|[^'A-Za-z]++");
-
-    /**
      * Obtain a Strategy given a field from a SimpleDateFormat pattern
      * @param formatField A sub-sequence of the SimpleDateFormat pattern
      * @param definingCalendar The calendar to obtain the short and long values
      * @return The Strategy that will handle parsing for the field
      */
-    private Strategy getStrategy(final String formatField, final Calendar definingCalendar) {
-        switch(formatField.charAt(0)) {
-        case '\'':
-            if(formatField.length()>2) {
-                return new CopyQuotedStrategy(formatField.substring(1, formatField.length()-1));
-            }
-            //$FALL-THROUGH$
+    private Strategy getStrategy(char f, int width, final Calendar definingCalendar) {
+        switch(f) {
         default:
-            return new CopyQuotedStrategy(formatField);
+            throw new IllegalArgumentException("Format '"+f+"' not supported");
         case 'D':
             return DAY_OF_YEAR_STRATEGY;
         case 'E':
@@ -550,7 +584,7 @@ public class FastDateParser implements DateParser, Serializable {
         case 'K':  // Hour in am/pm (0-11) 
             return HOUR_STRATEGY;
         case 'M':
-            return formatField.length()>=3 ?getLocaleSpecificStrategy(Calendar.MONTH, definingCalendar) :NUMBER_MONTH_STRATEGY;
+            return width>=3 ?getLocaleSpecificStrategy(Calendar.MONTH, definingCalendar) :NUMBER_MONTH_STRATEGY;
         case 'S':
             return MILLISECOND_STRATEGY;
         case 'W':
@@ -570,12 +604,12 @@ public class FastDateParser implements DateParser, Serializable {
         case 'w':
             return WEEK_OF_YEAR_STRATEGY;
         case 'y':
-            return formatField.length()>2 ?LITERAL_YEAR_STRATEGY :ABBREVIATED_YEAR_STRATEGY;
+            return width>2 ?LITERAL_YEAR_STRATEGY :ABBREVIATED_YEAR_STRATEGY;
         case 'X':
-            return ISO8601TimeZoneStrategy.getStrategy(formatField.length());
+            return ISO8601TimeZoneStrategy.getStrategy(width);
         case 'Z':
-            if (formatField.equals("ZZ")) {
-                return ISO_8601_STRATEGY;
+            if (width==2) {
+                return ISO8601TimeZoneStrategy.ISO_8601_3_STRATEGY;
             }
             //$FALL-THROUGH$
         case 'z':
@@ -611,7 +645,7 @@ public class FastDateParser implements DateParser, Serializable {
         Strategy strategy= cache.get(locale);
         if(strategy==null) {
             strategy= field==Calendar.ZONE_OFFSET
-                    ? new TimeZoneStrategy(locale)
+                    ? new TimeZoneStrategy(definingCalendar, locale)
                     : new CaseInsensitiveTextStrategy(field, definingCalendar, locale);
             final Strategy inCache= cache.putIfAbsent(locale, strategy);
             if(inCache!=null) {
@@ -625,14 +659,15 @@ public class FastDateParser implements DateParser, Serializable {
      * A strategy that copies the static or quoted field in the parsing pattern
      */
     private static class CopyQuotedStrategy extends Strategy {
-        private final String formatField;
+
+        final private String formatField;
 
         /**
          * Construct a Strategy that ensures the formatField has literal text
          * @param formatField The literal text to match
          */
         CopyQuotedStrategy(final String formatField) {
-            this.formatField= formatField;
+            this.formatField = formatField;
         }
 
         /**
@@ -640,30 +675,34 @@ public class FastDateParser implements DateParser, Serializable {
          */
         @Override
         boolean isNumber() {
-            char c= formatField.charAt(0);
-            if(c=='\'') {
-                c= formatField.charAt(1);
-            }
-            return Character.isDigit(c);
+            return false;
         }
 
-        /**
-         * {@inheritDoc}
-         */
         @Override
-        boolean addRegex(final FastDateParser parser, final StringBuilder regex) {
-            escapeRegex(regex, formatField, true);
-            return false;
+        boolean parse(FastDateParser parser, Calendar calendar, String source, ParsePosition pos, int maxWidth) {
+            for (int idx = 0; idx < formatField.length(); ++idx) {
+                int sIdx = idx + pos.getIndex();
+                if (sIdx == source.length()) {
+                    pos.setErrorIndex(sIdx);
+                    return false;
+                }
+                if (formatField.charAt(idx) != source.charAt(sIdx)) {
+                    pos.setErrorIndex(sIdx);
+                    return false;
+                }
+            }
+            pos.setIndex(formatField.length() + pos.getIndex());
+            return true;
         }
     }
 
     /**
      * A strategy that handles a text field in the parsing pattern
      */
-     private static class CaseInsensitiveTextStrategy extends Strategy {
+     private static class CaseInsensitiveTextStrategy extends PatternStrategy {
         private final int field;
-        private final Locale locale;
-        private final Map<String, Integer> lKeyValues;
+        final Locale locale;
+        private final Map<String, Integer> lKeyValues = new HashMap<String,Integer>();
 
         /**
          * Construct a Strategy that parses a Text field
@@ -672,44 +711,23 @@ public class FastDateParser implements DateParser, Serializable {
          * @param locale  The Locale to use
          */
         CaseInsensitiveTextStrategy(final int field, final Calendar definingCalendar, final Locale locale) {
-            this.field= field;
-            this.locale= locale;
-            final Map<String, Integer> keyValues = getDisplayNames(field, definingCalendar, locale);
-            this.lKeyValues= new HashMap<String,Integer>();
-
-            for(final Map.Entry<String, Integer> entry : keyValues.entrySet()) {
-                lKeyValues.put(entry.getKey().toLowerCase(locale), entry.getValue());
-            }
-        }
-
-        /**
-         * {@inheritDoc}
-         */
-        @Override
-        boolean addRegex(final FastDateParser parser, final StringBuilder regex) {
+            this.field = field;
+            this.locale = locale;
+            
+            StringBuilder regex = new StringBuilder();
             regex.append("((?iu)");
-            for(final String textKeyValue : lKeyValues.keySet()) {
-                simpleQuote(regex, textKeyValue).append('|');
-            }
-            regex.setCharAt(regex.length()-1, ')');
-            return true;
+            appendDisplayNames(definingCalendar, locale, field, regex, lKeyValues);
+            regex.setLength(regex.length()-1);
+            regex.append(")");
+            createPattern(regex);
         }
 
         /**
          * {@inheritDoc}
          */
         @Override
-        void setCalendar(final FastDateParser parser, final Calendar cal, final String value) {
+        void setCalendar(FastDateParser parser, Calendar cal, String value) {
             final Integer iVal = lKeyValues.get(value.toLowerCase(locale));
-            if(iVal == null) {
-                final StringBuilder sb= new StringBuilder(value);
-                sb.append(" not in (");
-                for(final String textKeyValue : lKeyValues.keySet()) {
-                    sb.append(textKeyValue).append(' ');
-                }
-                sb.setCharAt(sb.length()-1, ')');
-                throw new IllegalArgumentException(sb.toString());
-            }
             cal.set(field, iVal.intValue());
         }
     }
@@ -737,37 +755,56 @@ public class FastDateParser implements DateParser, Serializable {
             return true;
         }
 
-        /**
-         * {@inheritDoc}
-         */
         @Override
-        boolean addRegex(final FastDateParser parser, final StringBuilder regex) {
-            // See LANG-954: We use {Nd} rather than {IsNd} because Android does not support the Is prefix
-            if(parser.isNextNumber()) {
-                regex.append("(\\p{Nd}{").append(parser.getFieldWidth()).append("}+)");
+        boolean parse(FastDateParser parser, Calendar calendar, String source, ParsePosition pos, int maxWidth) {
+            int idx = pos.getIndex();
+            int last = source.length();
+
+            if (maxWidth == 0) {
+                // if no maxWidth, strip leading white space
+                for (; idx < last; ++idx) {
+                    char c = source.charAt(idx);
+                    if (!Character.isWhitespace(c)) {
+                        break;
+                    }
+                }
+                pos.setIndex(idx);
+            } else {
+                int end = idx + maxWidth;
+                if (last > end) {
+                    last = end;
+                }
             }
-            else {
-                regex.append("(\\p{Nd}++)");
+
+            for (; idx < last; ++idx) {
+                char c = source.charAt(idx);
+                if (!Character.isDigit(c)) {
+                    break;
+                }
             }
-            return true;
-        }
 
-        /**
-         * {@inheritDoc}
-         */
-        @Override
-        void setCalendar(final FastDateParser parser, final Calendar cal, final String value) {
-            cal.set(field, modify(Integer.parseInt(value)));
+            if (pos.getIndex() == idx) {
+                pos.setErrorIndex(idx);
+                return false;
+            }
+
+            int value = Integer.parseInt(source.substring(pos.getIndex(), idx));
+            pos.setIndex(idx);
+
+            calendar.set(field, modify(parser, value));
+            return true;
         }
 
         /**
          * Make any modifications to parsed integer
+         * @param parser The parser
          * @param iValue The parsed integer
          * @return The modified value
          */
-        int modify(final int iValue) {
+        int modify(FastDateParser parser, final int iValue) {
             return iValue;
         }
+
     }
 
     private static final Strategy ABBREVIATED_YEAR_STRATEGY = new NumberStrategy(Calendar.YEAR) {
@@ -775,26 +812,21 @@ public class FastDateParser implements DateParser, Serializable {
          * {@inheritDoc}
          */
         @Override
-        void setCalendar(final FastDateParser parser, final Calendar cal, final String value) {
-            int iValue= Integer.parseInt(value);
-            if(iValue<100) {
-                iValue= parser.adjustYear(iValue);
-            }
-            cal.set(Calendar.YEAR, iValue);
+        int modify(FastDateParser parser, final int iValue) {
+            return iValue<100 ?parser.adjustYear(iValue) :iValue;
         }
     };
 
     /**
      * A strategy that handles a timezone field in the parsing pattern
      */
-    static class TimeZoneStrategy extends Strategy {
+    static class TimeZoneStrategy extends PatternStrategy {
         private static final String RFC_822_TIME_ZONE = "[+-]\\d{4}";
         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 String validTimeZoneChars;
-
+ 
         /**
          * Index of zone id
          */
@@ -802,9 +834,11 @@ public class FastDateParser implements DateParser, Serializable {
 
         /**
          * Construct a Strategy that parses a TimeZone
+         * @param cal TODO
          * @param locale The Locale
          */
-        TimeZoneStrategy(final Locale locale) {
+        TimeZoneStrategy(Calendar cal, final Locale locale) {
+
             this.locale = locale;
 
             final StringBuilder sb = new StringBuilder();
@@ -818,25 +852,15 @@ public class FastDateParser implements DateParser, Serializable {
                 }
                 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);
+                    String zoneName = zoneNames[i];
+                    if (tzNames.put(zoneName.toLowerCase(locale), tz) == null) {
                         simpleQuote(sb.append('|'), zoneName);
                     }
                 }
             }
 
-            sb.append(')');
-            validTimeZoneChars = sb.toString();
-        }
-
-        /**
-         * {@inheritDoc}
-         */
-        @Override
-        boolean addRegex(final FastDateParser parser, final StringBuilder regex) {
-            regex.append(validTimeZoneChars);
-            return true;
+            sb.append(")");
+            createPattern(sb);
         }
 
         /**
@@ -853,33 +877,20 @@ public class FastDateParser implements DateParser, Serializable {
             }
             else {
                 tz= tzNames.get(value.toLowerCase(locale));
-                if(tz==null) {
-                    throw new IllegalArgumentException(value + " is not a supported timezone name");
-                }
             }
             cal.setTimeZone(tz);
         }
     }
     
-    private static class ISO8601TimeZoneStrategy extends Strategy {
+    private static class ISO8601TimeZoneStrategy extends PatternStrategy {
         // Z, +hh, -hh, +hhmm, -hhmm, +hh:mm or -hh:mm 
-        private final String pattern;
 
         /**
          * Construct a Strategy that parses a TimeZone
          * @param pattern The Pattern
          */
         ISO8601TimeZoneStrategy(String pattern) {
-            this.pattern = pattern;
-        }
-        
-        /**
-         * {@inheritDoc}
-         */
-        @Override
-        boolean addRegex(FastDateParser parser, StringBuilder regex) {
-            regex.append(pattern);
-            return true;
+            createPattern(pattern);
         }
         
         /**
@@ -921,7 +932,7 @@ public class FastDateParser implements DateParser, Serializable {
 
     private static final Strategy NUMBER_MONTH_STRATEGY = new NumberStrategy(Calendar.MONTH) {
         @Override
-        int modify(final int iValue) {
+        int modify(FastDateParser parser, final int iValue) {
             return iValue-1;
         }
     };
@@ -934,13 +945,13 @@ public class FastDateParser implements DateParser, Serializable {
     private static final Strategy HOUR_OF_DAY_STRATEGY = new NumberStrategy(Calendar.HOUR_OF_DAY);
     private static final Strategy HOUR24_OF_DAY_STRATEGY = new NumberStrategy(Calendar.HOUR_OF_DAY) {
         @Override
-        int modify(final int iValue) {
+        int modify(FastDateParser parser, final int iValue) {
             return iValue == 24 ? 0 : iValue;
         }
     };
     private static final Strategy HOUR12_STRATEGY = new NumberStrategy(Calendar.HOUR) {
         @Override
-        int modify(final int iValue) {
+        int modify(FastDateParser parser, final int iValue) {
             return iValue == 12 ? 0 : iValue;
         }
     };
@@ -948,7 +959,4 @@ public class FastDateParser implements DateParser, Serializable {
     private static final Strategy MINUTE_STRATEGY = new NumberStrategy(Calendar.MINUTE);
     private static final Strategy SECOND_STRATEGY = new NumberStrategy(Calendar.SECOND);
     private static final Strategy MILLISECOND_STRATEGY = new NumberStrategy(Calendar.MILLISECOND);
-    private static final Strategy ISO_8601_STRATEGY = new ISO8601TimeZoneStrategy("(Z|(?:[+-]\\d{2}(?::?\\d{2})?))");
-
-
 }

http://git-wip-us.apache.org/repos/asf/commons-lang/blob/94faa31b/src/test/java/org/apache/commons/lang3/time/FastDateFormatTest.java
----------------------------------------------------------------------
diff --git a/src/test/java/org/apache/commons/lang3/time/FastDateFormatTest.java b/src/test/java/org/apache/commons/lang3/time/FastDateFormatTest.java
index f8c5e46..1bdc25a 100644
--- a/src/test/java/org/apache/commons/lang3/time/FastDateFormatTest.java
+++ b/src/test/java/org/apache/commons/lang3/time/FastDateFormatTest.java
@@ -35,8 +35,8 @@ import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicInteger;
 import java.util.concurrent.atomic.AtomicLong;
 
-import org.apache.commons.lang3.test.SystemDefaultsSwitch;
 import org.apache.commons.lang3.test.SystemDefaults;
+import org.apache.commons.lang3.test.SystemDefaultsSwitch;
 import org.junit.Rule;
 import org.junit.Test;
 
@@ -230,7 +230,7 @@ public class FastDateFormatTest {
 
     @Test
     public void testParseSync() throws InterruptedException {
-        final String pattern = "yyyy-MM-dd'T'HH:mm:ss.SSS Z";
+        final String pattern = "yyyy-MM-dd'T'HH:mm:ss.SSS";
         final FastDateFormat formatter= FastDateFormat.getInstance(pattern);
         
         final long sdfTime= measureTime(formatter, new SimpleDateFormat(pattern) {

http://git-wip-us.apache.org/repos/asf/commons-lang/blob/94faa31b/src/test/java/org/apache/commons/lang3/time/FastDateParserSDFTest.java
----------------------------------------------------------------------
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 75ca342..1a460de 100644
--- a/src/test/java/org/apache/commons/lang3/time/FastDateParserSDFTest.java
+++ b/src/test/java/org/apache/commons/lang3/time/FastDateParserSDFTest.java
@@ -16,7 +16,10 @@
  */
 package org.apache.commons.lang3.time;
 
-import static org.junit.Assert.*;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
 
 import java.text.ParseException;
 import java.text.ParsePosition;
@@ -193,8 +196,9 @@ public class FastDateParserSDFTest {
 
         ParsePosition sdfP = new ParsePosition(0);
         Date expectedTime = sdf.parse(formattedDate, sdfP);
+        final int sdferrorIndex = sdfP.getErrorIndex();
         if (valid) {
-            assertEquals("Expected SDF error index -1 ", -1, sdfP.getErrorIndex());
+            assertEquals("Expected SDF error index -1 ", -1, sdferrorIndex);
             final int endIndex = sdfP.getIndex();
             final int length = formattedDate.length();
             if (endIndex != length) {
@@ -216,15 +220,11 @@ public class FastDateParserSDFTest {
             final int endIndex = fdfP.getIndex();
             final int length = formattedDate.length();
             assertEquals("Expected FDF to parse full string " + fdfP, length, endIndex);
-            assertEquals(locale.toString()+" "+formattedDate +"\n",expectedTime, actualTime);            
+            assertEquals(locale.toString()+" "+formattedDate +"\n", expectedTime, actualTime);
         } else {
-            final int endIndex = fdfP.getIndex();
-            if (endIndex != -0) {
-                fail("Expected FDF parse to fail, but got " + fdfP);                
-            }
-            if (fdferrorIndex != -1) {
-                assertEquals("FDF error index should match SDF index (if it is set)", sdfP.getErrorIndex(), fdferrorIndex);
-            }
+            assertNotEquals("Test data error: expected FDF parse to fail, but got " + actualTime, -1, fdferrorIndex);
+            assertTrue("FDF error index ("+ fdferrorIndex + ") should approxiamate SDF index (" + sdferrorIndex + ")",
+                    sdferrorIndex - fdferrorIndex <= 4);
         }        
     }
 }

http://git-wip-us.apache.org/repos/asf/commons-lang/blob/94faa31b/src/test/java/org/apache/commons/lang3/time/FastDateParserTest.java
----------------------------------------------------------------------
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 3c47bd2..330935c 100644
--- a/src/test/java/org/apache/commons/lang3/time/FastDateParserTest.java
+++ b/src/test/java/org/apache/commons/lang3/time/FastDateParserTest.java
@@ -31,6 +31,7 @@ import java.util.Locale;
 import java.util.Map;
 import java.util.TimeZone;
 
+import org.apache.commons.lang3.LocaleUtils;
 import org.apache.commons.lang3.SerializationUtils;
 import org.junit.Assert;
 import org.junit.Test;
@@ -325,7 +326,8 @@ public class FastDateParserTest {
         if (eraBC) {
             cal.set(Calendar.ERA, GregorianCalendar.BC);
         }
-        for(final Locale locale : Locale.getAvailableLocales()) {
+
+        for(final Locale locale : Locale.getAvailableLocales() ) {
             // ja_JP_JP cannot handle dates before 1868 properly
             if (eraBC && locale.equals(FastDateParser.JAPANESE_IMPERIAL)) {
                 continue;
@@ -340,6 +342,28 @@ public class FastDateParserTest {
             }
         }
     }
+    
+    @Test
+    public void testJpLocales() {
+
+        final Calendar cal= Calendar.getInstance(GMT);
+        cal.clear();
+        cal.set(2003, Calendar.FEBRUARY, 10);
+        cal.set(Calendar.ERA, GregorianCalendar.BC);
+
+        final Locale locale = LocaleUtils.toLocale("zh"); {
+            // ja_JP_JP cannot handle dates before 1868 properly
+
+            final SimpleDateFormat sdf = new SimpleDateFormat(LONG_FORMAT, locale);
+            final DateParser fdf = getInstance(LONG_FORMAT, locale);
+
+            try {
+                checkParse(locale, cal, sdf, fdf);
+            } catch(final ParseException ex) {
+                Assert.fail("Locale "+locale+ " failed with "+LONG_FORMAT+"\n" + trimMessage(ex.toString()));
+            }
+        }
+    }
 
     private String trimMessage(final String msg) {
         if (msg.length() < 100) {
@@ -441,7 +465,7 @@ public class FastDateParserTest {
             final DateParser fdp = getInstance(format, NEW_YORK, Locale.US);
             dfdp = fdp.parse(date);
             if (shouldFail) {
-                Assert.fail("Expected FDF failure, but got " + dfdp + " for ["+format+","+date+"] using "+((FastDateParser)fdp).getParsePattern());
+                Assert.fail("Expected FDF failure, but got " + dfdp + " for ["+format+","+date+"]");
             }
         } catch (final Exception e) {
             f = e;

http://git-wip-us.apache.org/repos/asf/commons-lang/blob/94faa31b/src/test/java/org/apache/commons/lang3/time/FastDateParser_MoreOrLessTest.java
----------------------------------------------------------------------
diff --git a/src/test/java/org/apache/commons/lang3/time/FastDateParser_MoreOrLessTest.java b/src/test/java/org/apache/commons/lang3/time/FastDateParser_MoreOrLessTest.java
new file mode 100644
index 0000000..15b8817
--- /dev/null
+++ b/src/test/java/org/apache/commons/lang3/time/FastDateParser_MoreOrLessTest.java
@@ -0,0 +1,113 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.lang3.time;
+
+import java.text.ParseException;
+import java.text.ParsePosition;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.Locale;
+import java.util.TimeZone;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+public class FastDateParser_MoreOrLessTest {
+
+    private static final TimeZone NEW_YORK = TimeZone.getTimeZone("America/New_York");
+    
+    @Test
+    public void testInputHasPrecedingCharacters() throws ParseException {
+        FastDateParser parser = new FastDateParser("MM/dd", TimeZone.getDefault(), Locale.getDefault());
+        ParsePosition parsePosition = new ParsePosition(0);
+        Date date = parser.parse("A 3/23/61", parsePosition);
+        Assert.assertNull(date);
+        Assert.assertEquals(0, parsePosition.getIndex());      
+        Assert.assertEquals(0, parsePosition.getErrorIndex());        
+    }
+
+    @Test
+    public void testInputHasWhitespace() throws ParseException {
+        FastDateParser parser = new FastDateParser("M/d/y", TimeZone.getDefault(), Locale.getDefault());
+        //SimpleDateFormat parser = new SimpleDateFormat("M/d/y");
+        ParsePosition parsePosition = new ParsePosition(0);
+        Date date = parser.parse(" 3/ 23/ 1961", parsePosition);
+        Assert.assertEquals(12, parsePosition.getIndex());
+
+        Calendar calendar = Calendar.getInstance();
+        calendar.setTime(date);
+        Assert.assertEquals(1961, calendar.get(Calendar.YEAR));
+        Assert.assertEquals(2, calendar.get(Calendar.MONTH));
+        Assert.assertEquals(23, calendar.get(Calendar.DATE));       
+    }
+
+    @Test
+    public void testInputHasMoreCharacters() throws ParseException {
+        FastDateParser parser = new FastDateParser("MM/dd", TimeZone.getDefault(), Locale.getDefault());
+        ParsePosition parsePosition = new ParsePosition(0);
+        Date date = parser.parse("3/23/61", parsePosition);
+        Assert.assertEquals(4, parsePosition.getIndex());
+
+        Calendar calendar = Calendar.getInstance();
+        calendar.setTime(date);
+        Assert.assertEquals(2, calendar.get(Calendar.MONTH));
+        Assert.assertEquals(23, calendar.get(Calendar.DATE));       
+    }
+    
+    @Test
+    public void testInputHasWrongCharacters() {
+        FastDateParser parser = new FastDateParser("MM-dd-yyy", TimeZone.getDefault(), Locale.getDefault());
+        ParsePosition parsePosition = new ParsePosition(0);
+        Assert.assertNull(parser.parse("03/23/1961", parsePosition));
+        Assert.assertEquals(2, parsePosition.getErrorIndex());
+    }
+    
+    @Test
+    public void testInputHasLessCharacters() {
+        FastDateParser parser = new FastDateParser("MM/dd/yyy", TimeZone.getDefault(), Locale.getDefault());
+        ParsePosition parsePosition = new ParsePosition(0);
+        Assert.assertNull(parser.parse("03/23", parsePosition));
+        Assert.assertEquals(5, parsePosition.getErrorIndex());
+    }
+    
+    @Test
+    public void testInputHasWrongTimeZone() {
+        FastDateParser parser = new FastDateParser("mm:ss z", NEW_YORK, Locale.US);
+        
+        String input = "11:23 Pacific Standard Time";
+        ParsePosition parsePosition = new ParsePosition(0);
+        Assert.assertNotNull(parser.parse(input, parsePosition));
+        Assert.assertEquals(input.length(), parsePosition.getIndex());
+        
+        parsePosition.setIndex(0);
+        Assert.assertNull(parser.parse( "11:23 Pacific Standard ", parsePosition));
+        Assert.assertEquals(6, parsePosition.getErrorIndex());
+    }
+    
+    @Test
+    public void testInputHasWrongDay() throws ParseException {
+        FastDateParser parser = new FastDateParser("EEEE, MM/dd/yyy", NEW_YORK, Locale.US);
+        String input = "Thursday, 03/23/61";
+        ParsePosition parsePosition = new ParsePosition(0);
+        Assert.assertNotNull(parser.parse(input, parsePosition));
+        Assert.assertEquals(input.length(), parsePosition.getIndex());
+        
+        parsePosition.setIndex(0);
+        Assert.assertNull(parser.parse( "Thorsday, 03/23/61", parsePosition));
+        Assert.assertEquals(0, parsePosition.getErrorIndex());
+    }
+}


[2/2] [lang] LANG-1153 Implement ParsePosition api for FastDateParser

Posted by ch...@apache.org.
LANG-1153
Implement ParsePosition api for FastDateParser


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

Branch: refs/heads/master
Commit: 40134ecdb327d1b82936f7ee3fa925b7b181c726
Parents: 3ff366c 94faa31
Author: Chas Honton <ch...@apache.org>
Authored: Tue Jul 7 20:20:19 2015 -0700
Committer: Chas Honton <ch...@apache.org>
Committed: Tue Jul 7 20:20:19 2015 -0700

----------------------------------------------------------------------
 src/changes/changes.xml                         |   1 +
 .../apache/commons/lang3/time/DateUtils.java    |  15 +-
 .../commons/lang3/time/FastDateParser.java      | 588 ++++++++++---------
 .../commons/lang3/time/FastDateFormatTest.java  |   4 +-
 .../lang3/time/FastDateParserSDFTest.java       |  20 +-
 .../commons/lang3/time/FastDateParserTest.java  |  28 +-
 .../time/FastDateParser_MoreOrLessTest.java     | 113 ++++
 7 files changed, 459 insertions(+), 310 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/commons-lang/blob/40134ecd/src/changes/changes.xml
----------------------------------------------------------------------
diff --cc src/changes/changes.xml
index 9d961cc,c848755..9e14f45
--- a/src/changes/changes.xml
+++ b/src/changes/changes.xml
@@@ -22,9 -22,6 +22,10 @@@
    <body>
  
    <release version="3.5" date="tba" description="tba">
++    <action issue="LANG-1153" type="add" dev="chas">Implement ParsePosition api for FastDateParser</action>
 +    <action issue="LANG-1141" type="fix" dev="oheger">StrLookup.systemPropertiesLookup() no longer reacts on changes on system properties</action>
 +    <action issue="LANG-1147" type="fix" dev="sebb" due-to="Loic Guibert">EnumUtils *BitVector issue with more than 32 values Enum</action>
 +    <action issue="LANG-1059" type="fix" dev="sebb" due-to="Colin Casey">Capitalize javadoc is incorrect</action>
      <action issue="LANG-1137" type="add" dev="britter" due-to="Matthew Aguirre">Add check for duplicate event listener in EventListenerSupport</action>
      <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>