in java/text/SimpleDateFormat.java [2053:2425]
private int subParse(String text, int start, int patternCharIndex, int count,
boolean obeyCount, boolean[] ambiguousYear,
ParsePosition origPos,
boolean useFollowingMinusSignAsDelimiter, CalendarBuilder calb) {
Number number;
int value = 0;
ParsePosition pos = new ParsePosition(0);
pos.index = start;
if (patternCharIndex == PATTERN_WEEK_YEAR && !calendar.isWeekDateSupported()) {
// use calendar year 'y' instead
patternCharIndex = PATTERN_YEAR;
}
int field = PATTERN_INDEX_TO_CALENDAR_FIELD[patternCharIndex];
// If there are any spaces here, skip over them. If we hit the end
// of the string, then fail.
for (;;) {
if (pos.index >= text.length()) {
origPos.errorIndex = start;
return -1;
}
char c = text.charAt(pos.index);
if (c != ' ' && c != '\t') {
break;
}
++pos.index;
}
parsing:
{
// We handle a few special cases here where we need to parse
// a number value. We handle further, more generic cases below. We need
// to handle some of them here because some fields require extra processing on
// the parsed value.
if (patternCharIndex == PATTERN_HOUR_OF_DAY1 ||
patternCharIndex == PATTERN_HOUR1 ||
(patternCharIndex == PATTERN_MONTH && count <= 2) ||
patternCharIndex == PATTERN_YEAR ||
patternCharIndex == PATTERN_WEEK_YEAR) {
// It would be good to unify this with the obeyCount logic below,
// but that's going to be difficult.
if (obeyCount) {
if ((start+count) > text.length()) {
break parsing;
}
number = numberFormat.parse(text.substring(0, start+count), pos);
} else {
number = numberFormat.parse(text, pos);
}
if (number == null) {
if (patternCharIndex != PATTERN_YEAR || calendar instanceof GregorianCalendar) {
break parsing;
}
} else {
value = number.intValue();
if (useFollowingMinusSignAsDelimiter && (value < 0) &&
(((pos.index < text.length()) &&
(text.charAt(pos.index) != minusSign)) ||
((pos.index == text.length()) &&
(text.charAt(pos.index-1) == minusSign)))) {
value = -value;
pos.index--;
}
}
}
boolean useDateFormatSymbols = useDateFormatSymbols();
int index;
switch (patternCharIndex) {
case PATTERN_ERA: // 'G'
if (useDateFormatSymbols) {
if ((index = matchString(text, start, Calendar.ERA, formatData.getEras(), calb)) > 0) {
return index;
}
} else {
Map<String, Integer> map = calendar.getDisplayNames(field,
Calendar.ALL_STYLES,
locale);
if ((index = matchString(text, start, field, map, calb)) > 0) {
return index;
}
}
break parsing;
case PATTERN_WEEK_YEAR: // 'Y'
case PATTERN_YEAR: // 'y'
if (!(calendar instanceof GregorianCalendar)) {
// calendar might have text representations for year values,
// such as "\u5143" in JapaneseImperialCalendar.
int style = (count >= 4) ? Calendar.LONG : Calendar.SHORT;
Map<String, Integer> map = calendar.getDisplayNames(field, style, locale);
if (map != null) {
if ((index = matchString(text, start, field, map, calb)) > 0) {
return index;
}
}
calb.set(field, value);
return pos.index;
}
// If there are 3 or more YEAR pattern characters, this indicates
// that the year value is to be treated literally, without any
// two-digit year adjustments (e.g., from "01" to 2001). Otherwise
// we made adjustments to place the 2-digit year in the proper
// century, for parsed strings from "00" to "99". Any other string
// is treated literally: "2250", "-1", "1", "002".
if (count <= 2 && (pos.index - start) == 2
&& Character.isDigit(text.charAt(start))
&& Character.isDigit(text.charAt(start+1))) {
// Assume for example that the defaultCenturyStart is 6/18/1903.
// This means that two-digit years will be forced into the range
// 6/18/1903 to 6/17/2003. As a result, years 00, 01, and 02
// correspond to 2000, 2001, and 2002. Years 04, 05, etc. correspond
// to 1904, 1905, etc. If the year is 03, then it is 2003 if the
// other fields specify a date before 6/18, or 1903 if they specify a
// date afterwards. As a result, 03 is an ambiguous year. All other
// two-digit years are unambiguous.
int ambiguousTwoDigitYear = defaultCenturyStartYear % 100;
ambiguousYear[0] = value == ambiguousTwoDigitYear;
value += (defaultCenturyStartYear/100)*100 +
(value < ambiguousTwoDigitYear ? 100 : 0);
}
calb.set(field, value);
return pos.index;
case PATTERN_MONTH: // 'M'
// BEGIN Android-changed: extract parseMonth method.
{
final int idx = parseMonth(text, count, value, start, field, pos,
useDateFormatSymbols, false /* isStandalone */, calb);
if (idx > 0) {
return idx;
}
break parsing;
}
case PATTERN_MONTH_STANDALONE: // 'L'.
{
final int idx = parseMonth(text, count, value, start, field, pos,
useDateFormatSymbols, true /* isStandalone */, calb);
if (idx > 0) {
return idx;
}
break parsing;
}
// END Android-changed: extract parseMonth method.
case PATTERN_HOUR_OF_DAY1: // 'k' 1-based. eg, 23:59 + 1 hour =>> 24:59
if (!isLenient()) {
// Validate the hour value in non-lenient
if (value < 1 || value > 24) {
break parsing;
}
}
// [We computed 'value' above.]
if (value == calendar.getMaximum(Calendar.HOUR_OF_DAY) + 1) {
value = 0;
}
calb.set(Calendar.HOUR_OF_DAY, value);
return pos.index;
case PATTERN_DAY_OF_WEEK: // 'E'
// BEGIN Android-changed: extract parseWeekday method.
{
final int idx = parseWeekday(text, start, field, useDateFormatSymbols,
false /* standalone */, calb);
if (idx > 0) {
return idx;
}
break parsing;
}
// END Android-changed: extract parseWeekday method.
// BEGIN Android-added: support for 'c' (standalone day of week).
case PATTERN_STANDALONE_DAY_OF_WEEK: // 'c'
{
final int idx = parseWeekday(text, start, field, useDateFormatSymbols,
true /* standalone */, calb);
if (idx > 0) {
return idx;
}
break parsing;
}
// END Android-added: support for 'c' (standalone day of week).
case PATTERN_AM_PM: // 'a'
if (useDateFormatSymbols) {
if ((index = matchString(text, start, Calendar.AM_PM,
formatData.getAmPmStrings(), calb)) > 0) {
return index;
}
} else {
Map<String,Integer> map = calendar.getDisplayNames(field, Calendar.ALL_STYLES, locale);
if ((index = matchString(text, start, field, map, calb)) > 0) {
return index;
}
}
break parsing;
case PATTERN_HOUR1: // 'h' 1-based. eg, 11PM + 1 hour =>> 12 AM
if (!isLenient()) {
// Validate the hour value in non-lenient
if (value < 1 || value > 12) {
break parsing;
}
}
// [We computed 'value' above.]
if (value == calendar.getLeastMaximum(Calendar.HOUR) + 1) {
value = 0;
}
calb.set(Calendar.HOUR, value);
return pos.index;
case PATTERN_ZONE_NAME: // 'z'
case PATTERN_ZONE_VALUE: // 'Z'
{
int sign = 0;
try {
char c = text.charAt(pos.index);
if (c == '+') {
sign = 1;
} else if (c == '-') {
sign = -1;
}
if (sign == 0) {
// Try parsing a custom time zone "GMT+hh:mm" or "GMT".
if ((c == 'G' || c == 'g')
&& (text.length() - start) >= GMT.length()
&& text.regionMatches(true, start, GMT, 0, GMT.length())) {
pos.index = start + GMT.length();
if ((text.length() - pos.index) > 0) {
c = text.charAt(pos.index);
if (c == '+') {
sign = 1;
} else if (c == '-') {
sign = -1;
}
}
if (sign == 0) { /* "GMT" without offset */
calb.set(Calendar.ZONE_OFFSET, 0)
.set(Calendar.DST_OFFSET, 0);
return pos.index;
}
// Android-changed: tolerate colon in zone offset.
// Parse the rest as "hh[:]?mm"
int i = subParseNumericZone(text, ++pos.index, sign, 0,
false, calb);
if (i > 0) {
return i;
}
pos.index = -i;
} else {
// Try parsing the text as a time zone
// name or abbreviation.
int i = subParseZoneString(text, pos.index, calb);
if (i > 0) {
return i;
}
pos.index = -i;
}
} else {
// Android-changed: tolerate colon in zone offset.
// Parse the rest as "hh[:]?mm" (RFC 822)
int i = subParseNumericZone(text, ++pos.index, sign, 0,
false, calb);
if (i > 0) {
return i;
}
pos.index = -i;
}
} catch (IndexOutOfBoundsException e) {
}
}
break parsing;
case PATTERN_ISO_ZONE: // 'X'
{
if ((text.length() - pos.index) <= 0) {
break parsing;
}
int sign;
char c = text.charAt(pos.index);
if (c == 'Z') {
calb.set(Calendar.ZONE_OFFSET, 0).set(Calendar.DST_OFFSET, 0);
return ++pos.index;
}
// parse text as "+/-hh[[:]mm]" based on count
if (c == '+') {
sign = 1;
} else if (c == '-') {
sign = -1;
} else {
++pos.index;
break parsing;
}
int i = subParseNumericZone(text, ++pos.index, sign, count,
count == 3, calb);
if (i > 0) {
return i;
}
pos.index = -i;
}
break parsing;
default:
// case PATTERN_DAY_OF_MONTH: // 'd'
// case PATTERN_HOUR_OF_DAY0: // 'H' 0-based. eg, 23:59 + 1 hour =>> 00:59
// case PATTERN_MINUTE: // 'm'
// case PATTERN_SECOND: // 's'
// case PATTERN_MILLISECOND: // 'S'
// case PATTERN_DAY_OF_YEAR: // 'D'
// case PATTERN_DAY_OF_WEEK_IN_MONTH: // 'F'
// case PATTERN_WEEK_OF_YEAR: // 'w'
// case PATTERN_WEEK_OF_MONTH: // 'W'
// case PATTERN_HOUR0: // 'K' 0-based. eg, 11PM + 1 hour =>> 0 AM
// case PATTERN_ISO_DAY_OF_WEEK: // 'u' (pseudo field);
// Handle "generic" fields
int parseStart = pos.getIndex();
if (obeyCount) {
if ((start+count) > text.length()) {
break parsing;
}
number = numberFormat.parse(text.substring(0, start+count), pos);
} else {
number = numberFormat.parse(text, pos);
}
if (number != null) {
// BEGIN Android-changed: Better UTS#35 conformity for fractional seconds.
if (patternCharIndex == PATTERN_MILLISECOND) {
// Fractional seconds must be treated specially. We must always
// normalize them to their fractional second value [0, 1) before we attempt
// to parse them.
//
// Case 1: 11.78 seconds is 11 seconds and 780 (not 78) milliseconds.
// Case 2: 11.7890567 seconds is 11 seconds and 789 (not 7890567) milliseconds.
double doubleValue = number.doubleValue();
int width = pos.getIndex() - parseStart;
final double divisor = Math.pow(10, width);
value = (int) ((doubleValue / divisor) * 1000);
} else {
value = number.intValue();
}
// END Android-changed: Better UTS#35 conformity for fractional seconds.
if (useFollowingMinusSignAsDelimiter && (value < 0) &&
(((pos.index < text.length()) &&
(text.charAt(pos.index) != minusSign)) ||
((pos.index == text.length()) &&
(text.charAt(pos.index-1) == minusSign)))) {
value = -value;
pos.index--;
}
calb.set(field, value);
return pos.index;
}
break parsing;
}
}
// Parsing failed.
origPos.errorIndex = pos.index;
return -1;
}