diff --git a/icu4j/main/classes/core/src/com/ibm/icu/text/DateFormat.java b/icu4j/main/classes/core/src/com/ibm/icu/text/DateFormat.java
index f83fc3c4197..d8eda16b4e7 100644
--- a/icu4j/main/classes/core/src/com/ibm/icu/text/DateFormat.java
+++ b/icu4j/main/classes/core/src/com/ibm/icu/text/DateFormat.java
@@ -452,6 +452,8 @@ public abstract class DateFormat extends UFormat {
* PARSE_ALLOW_WHITESPACE - indicates whitespace tolerance. Also included is trailing dot tolerance.
*
* PARSE_ALLOW_NUMERIC - indicates tolerance of numeric data when String data may be assumed. eg: YEAR_NAME_FIELD
+ *
+ * PRASE_PARTIAL_MATCH - indicates tolerance of partial matches against pattern literals
*
* @internal ICU technology preview
*/
@@ -465,7 +467,12 @@ public abstract class DateFormat extends UFormat {
* indicates tolerance of numeric data when String data may be assumed. eg: YEAR_NAME_FIELD
* @internal ICU technology preview
*/
- PARSE_ALLOW_NUMERIC
+ PARSE_ALLOW_NUMERIC,
+ /**
+ * indicates tolerance of a partial literal match
+ * @draft ICU 53
+ */
+ PARSE_PARTIAL_MATCH
};
/**
@@ -1472,7 +1479,7 @@ public abstract class DateFormat extends UFormat {
return calendar.isLenient();
}
- /**
+ /**
* set a boolean attribute for this instance. Aspects of DateFormat leniency are controlled by
* boolean attributes.
*
diff --git a/icu4j/main/classes/core/src/com/ibm/icu/text/SimpleDateFormat.java b/icu4j/main/classes/core/src/com/ibm/icu/text/SimpleDateFormat.java
index be8470858e2..f3513e43add 100644
--- a/icu4j/main/classes/core/src/com/ibm/icu/text/SimpleDateFormat.java
+++ b/icu4j/main/classes/core/src/com/ibm/icu/text/SimpleDateFormat.java
@@ -2438,6 +2438,9 @@ public class SimpleDateFormat extends DateFormat {
} else if ((pch == ' ' || pch == '.') && getBooleanAttribute(DateFormat.BooleanAttribute.PARSE_ALLOW_WHITESPACE)) {
++idx;
continue;
+ } else if (pos != originalPos && getBooleanAttribute(DateFormat.BooleanAttribute.PARSE_PARTIAL_MATCH)) {
+ ++idx;
+ continue;
}
break;
}
@@ -2731,11 +2734,14 @@ public class SimpleDateFormat extends DateFormat {
if (patternCharIndex == 4 /*'k' HOUR_OF_DAY1_FIELD*/ ||
patternCharIndex == 15 /*'h' HOUR1_FIELD*/ ||
(patternCharIndex == 2 /*'M' MONTH_FIELD*/ && count <= 2) ||
- (patternCharIndex == 26 /*'L' STAND_ALONE_MONTH*/ && count <= 2) ||
- (patternCharIndex == 19 /*'e' DOW_LOCAL*/ && count <= 2) ||
+ patternCharIndex == 26 /*'L' STAND_ALONE_MONTH*/ ||
+ patternCharIndex == 19 /*'e' DOW_LOCAL*/ ||
+ patternCharIndex == 25 /*'c' STAND_ALONE_DAY_OF_WEEK*/ ||
patternCharIndex == 1 /*'y' YEAR */ || patternCharIndex == 18 /*'Y' YEAR_WOY */ ||
patternCharIndex == 30 /*'U' YEAR_NAME_FIELD, falls back to numeric */ ||
(patternCharIndex == 0 /*'G' ERA */ && isChineseCalendar) ||
+ patternCharIndex == 27 /* 'Q' - QUARTER*/ ||
+ patternCharIndex == 28 /* 'q' - STANDALONE QUARTER*/ ||
patternCharIndex == 8 /*'S' FRACTIONAL_SECOND */ )
{
// It would be good to unify this with the obeyCount logic below,
@@ -2764,7 +2770,8 @@ public class SimpleDateFormat extends DateFormat {
} else {
number = parseInt(text, pos, allowNegative,currentNumberFormat);
}
- if (number == null && patternCharIndex != 30) {
+ if (number == null && !allowNumericFallback(patternCharIndex)) {
+ // only return if pattern is NOT one that allows numeric fallback
return ~start;
}
}
@@ -2853,7 +2860,8 @@ public class SimpleDateFormat extends DateFormat {
return ~start;
case 2: // 'M' - MONTH
case 26: // 'L' - STAND_ALONE_MONTH
- if (count <= 2) { // i.e., M/MM, L/LL
+ if (count <= 2 || (number != null && getBooleanAttribute(DateFormat.BooleanAttribute.PARSE_ALLOW_NUMERIC))) {
+ // i.e., M/MM, L/LL or lenient & have a number
// Don't want to parse the month if it is a string
// while pattern uses numeric style: M/MM, L/LL.
// [We computed 'value' above.]
@@ -2918,7 +2926,8 @@ public class SimpleDateFormat extends DateFormat {
cal.set(Calendar.MILLISECOND, value);
return pos.getIndex();
case 19: // 'e' - DOW_LOCAL
- if(count <= 2) { // i.e. e/ee
+ if(count <= 2 || (number != null && (getBooleanAttribute(DateFormat.BooleanAttribute.PARSE_ALLOW_NUMERIC))) ) {
+ // i.e. e/ee or lenient and have a number
cal.set(field, value);
return pos.getIndex();
}
@@ -2946,6 +2955,11 @@ public class SimpleDateFormat extends DateFormat {
return newStart;
}
case 25: { // 'c' - STAND_ALONE_DAY_OF_WEEK
+ if(count == 1 || (number != null && (getBooleanAttribute(DateFormat.BooleanAttribute.PARSE_ALLOW_NUMERIC))) ) {
+ // i.e. c or lenient and have a number
+ cal.set(field, value);
+ return pos.getIndex();
+ }
// Want to be able to parse at least wide, abbrev, short forms.
int newStart = matchString(text, start, Calendar.DAY_OF_WEEK, formatData.standaloneWeekdays, null, cal); // try cccc wide
if (newStart > 0) {
@@ -3100,7 +3114,8 @@ public class SimpleDateFormat extends DateFormat {
return ~start;
}
case 27: // 'Q' - QUARTER
- if (count <= 2) { // i.e., Q or QQ.
+ if (count <= 2 || (number != null && getBooleanAttribute(DateFormat.BooleanAttribute.PARSE_ALLOW_NUMERIC))) {
+ // i.e., Q or QQ. or lenient & have number
// Don't want to parse the quarter if it is a string
// while pattern uses numeric style: Q or QQ.
// [We computed 'value' above.]
@@ -3121,7 +3136,8 @@ public class SimpleDateFormat extends DateFormat {
}
case 28: // 'q' - STANDALONE QUARTER
- if (count <= 2) { // i.e., q or qq.
+ if (count <= 2 || (number != null && getBooleanAttribute(DateFormat.BooleanAttribute.PARSE_ALLOW_NUMERIC))) {
+ // i.e., q or qq. or lenient & have number
// Don't want to parse the quarter if it is a string
// while pattern uses numeric style: q or qq.
// [We computed 'value' above.]
@@ -3170,6 +3186,22 @@ public class SimpleDateFormat extends DateFormat {
}
}
+ /**
+ * return true if the pattern specified by patternCharIndex is one that allows
+ * numeric fallback regardless of actual pattern size.
+ */
+ private boolean allowNumericFallback(int patternCharIndex) {
+ if (patternCharIndex == 26 /*'L' STAND_ALONE_MONTH*/ ||
+ patternCharIndex == 19 /*'e' DOW_LOCAL*/ ||
+ patternCharIndex == 25 /*'c' STAND_ALONE_DAY_OF_WEEK*/ ||
+ patternCharIndex == 30 /*'U' YEAR_NAME_FIELD*/ ||
+ patternCharIndex == 27 /* 'Q' - QUARTER*/ ||
+ patternCharIndex == 28 /* 'q' - STANDALONE QUARTER*/) {
+ return true;
+ }
+ return false;
+ }
+
/**
* Parse an integer using numberFormat. This method is semantically
* const, but actually may modify fNumberFormat.
diff --git a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/format/DateFormatRegressionTest.java b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/format/DateFormatRegressionTest.java
index 68f6bbcb76a..69c8dfde94c 100644
--- a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/format/DateFormatRegressionTest.java
+++ b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/format/DateFormatRegressionTest.java
@@ -1275,5 +1275,80 @@ public class DateFormatRegressionTest extends com.ibm.icu.dev.test.TestFmwk {
}
}
}
-
+
+
+ public void TestT10334() {
+ String pattern = new String("'--: 'EEE-WW-MMMM-yyyy");
+ String text = new String("--mon-02-march-2011");
+ SimpleDateFormat format = new SimpleDateFormat(pattern);
+
+ format.setBooleanAttribute(DateFormat.BooleanAttribute.PARSE_PARTIAL_MATCH, false);
+ try {
+ format.parse(text);
+ errln("parse partial match did NOT fail in strict mode!");
+ } catch (ParseException pe) {
+ // expected
+ }
+
+ format.setBooleanAttribute(DateFormat.BooleanAttribute.PARSE_PARTIAL_MATCH, true);
+ try {
+ format.parse(text);
+ } catch (ParseException pe) {
+ errln("parse partial match failure in lenient mode: " + pe.getLocalizedMessage());
+ }
+
+ pattern = new String("YYYY MM dd");
+ text = new String("2013 12 10");
+ format.applyPattern(pattern);
+ Date referenceDate = null;
+ try {
+ referenceDate = format.parse(text);
+ } catch (ParseException pe) {
+ errln("unable to instantiate reference date: " + pe.getLocalizedMessage());
+ }
+
+ FieldPosition fp = new FieldPosition(0);
+ pattern = new String("YYYY LL dd ee cc qq QQ");
+ format.applyPattern(pattern);
+ StringBuffer formattedString = new StringBuffer();
+ formattedString = format.format(referenceDate, formattedString, fp);
+ logln("ref date: " + formattedString);
+
+
+ pattern = new String("YYYY LLL dd eee ccc qqq QQQ");
+ text = new String("2013 12 10 03 3 04 04");
+ format.applyPattern(pattern);
+ logln(format.format(referenceDate));
+
+ format.setBooleanAttribute(DateFormat.BooleanAttribute.PARSE_ALLOW_NUMERIC, true);
+ ParsePosition pp = new ParsePosition(0);
+ format.parse(text, pp);
+ int errorIdx = pp.getErrorIndex();
+ if (errorIdx != -1) {
+
+ errln("numeric parse error at["+errorIdx+"] on char["+pattern.substring(errorIdx, errorIdx+1)+"] in pattern["+pattern+"]");
+ }
+
+ format.setBooleanAttribute(DateFormat.BooleanAttribute.PARSE_ALLOW_NUMERIC, false);
+ try {
+ format.parse(text);
+ errln("numeric parse did NOT fail in strict mode!");
+ } catch (ParseException pe) {
+ // expected
+ }
+
+ /*
+ * test to verify new code (and improve code coverage) for normal quarter processing
+ */
+ text = new String("2013 Dec 10 Thu Thu Q4 Q4");
+ try {
+ format.parse(text);
+ } catch (ParseException pe) {
+ errln("normal quarter processing failed");
+ }
+
+
+
+ }
+
}
diff --git a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/format/DateFormatTest.java b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/format/DateFormatTest.java
index e04d978a3b0..86bd4e2bd80 100644
--- a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/format/DateFormatTest.java
+++ b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/format/DateFormatTest.java
@@ -4320,7 +4320,7 @@ public class DateFormatTest extends com.ibm.icu.dev.test.TestFmwk {
if(p.getErrorIndex() != -1)
continue;
else
- errln("error: unexpected parse success..."+item.parseString + " w/ lenient="+item.leniency+" should have faile");
+ errln("error: unexpected parse success..."+item.parseString + " w/ lenient="+item.leniency+" should have failed");
}
if(p.getErrorIndex() != -1) {
errln("error: parse error for string " +item.parseString + " -- idx["+p.getIndex()+"] errIdx["+p.getErrorIndex()+"]");
@@ -4335,5 +4335,5 @@ public class DateFormatTest extends com.ibm.icu.dev.test.TestFmwk {
}
}
}
-
+
}