diff --git a/src/changes/changes.xml b/src/changes/changes.xml index 9d961ccab..9e14f45af 100644 --- a/src/changes/changes.xml +++ b/src/changes/changes.xml @@ -22,6 +22,7 @@
Constructs a new FastDateParser.
* @@ -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); - } - - /** - *Constructs a new FastDateParser.
- * - * @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
* 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; iStringBuilder
+ * 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; iCalendar
to set
- * @param value The parsed field to translate and set in cal
- */
- void setCalendar(final FastDateParser parser, final Calendar cal, final String value) {
-
- }
-
- /**
- * Generate a Pattern
regular expression to the StringBuilder
- * which will accept this field
- * @param parser The parser calling this strategy
- * @param regex The StringBuilder
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 boolean parse(FastDateParser parser, Calendar calendar, String source, ParsePosition pos, int maxWidth);
}
/**
- * A Pattern
to parse the user supplied SimpleDateFormat pattern
+ * A strategy to parse a single field from the parsing 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]++");
+ 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;
+ }
+
+ @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;
+ }
+
+ abstract void setCalendar(FastDateParser parser, Calendar cal, String value);
+ }
/**
* Obtain a Strategy given a field from a SimpleDateFormat pattern
@@ -528,15 +567,10 @@ public class FastDateParser implements DateParser, Serializable {
* @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