ICU-9108 Cleaned up some garbages and made minor improvements. Ready for review.

X-SVN-Rev: 31463
This commit is contained in:
Yoshito Umaoka 2012-02-21 06:24:52 +00:00
parent 47890124a7
commit 08b039b84a
2 changed files with 157 additions and 320 deletions

View File

@ -351,8 +351,7 @@ public class SimpleDateFormat extends DateFormat {
// We need to preserve time zone type when parsing specific
// time zone text (xxx Standard Time vs xxx Daylight Time)
private static final int TZTYPE_UNK = 0, TZTYPE_STD = 1, TZTYPE_DST = 2;
private transient int tztype = TZTYPE_UNK;
private transient TimeType tztype = TimeType.UNKNOWN;
private static final int millisPerHour = 60 * 60 * 1000;
@ -1416,7 +1415,7 @@ public class SimpleDateFormat extends DateFormat {
int start = pos;
// Reset tztype
tztype = TZTYPE_UNK;
tztype = TimeType.UNKNOWN;
boolean[] ambiguousYear = { false };
// item index for the first numeric field within a contiguous numeric run
@ -1606,7 +1605,7 @@ public class SimpleDateFormat extends DateFormat {
// front or the back of the default century. This only works because we adjust
// the year correctly to start with in other cases -- see subParse().
try {
if (ambiguousYear[0] || tztype != TZTYPE_UNK) {
if (ambiguousYear[0] || tztype != TimeType.UNKNOWN) {
// We need a copy of the fields, and we need to avoid triggering a call to
// complete(), which will recalculate the fields. Since we can't access
// the fields[] array in Calendar, we clone the entire object. This will
@ -1620,7 +1619,7 @@ public class SimpleDateFormat extends DateFormat {
cal.set(Calendar.YEAR, getDefaultCenturyStartYear() + 100);
}
}
if (tztype != TZTYPE_UNK) {
if (tztype != TimeType.UNKNOWN) {
copy = (Calendar)cal.clone();
TimeZone tz = copy.getTimeZone();
BasicTimeZone btz = null;
@ -1637,7 +1636,7 @@ public class SimpleDateFormat extends DateFormat {
// matches the rule used by the parsed time zone.
int[] offsets = new int[2];
if (btz != null) {
if (tztype == TZTYPE_STD) {
if (tztype == TimeType.STANDARD) {
btz.getOffsetFromLocal(localMillis,
BasicTimeZone.LOCAL_STD, BasicTimeZone.LOCAL_STD, offsets);
} else {
@ -1649,8 +1648,8 @@ public class SimpleDateFormat extends DateFormat {
// but following code work in most case.
tz.getOffset(localMillis, true, offsets);
if (tztype == TZTYPE_STD && offsets[1] != 0
|| tztype == TZTYPE_DST && offsets[1] == 0) {
if (tztype == TimeType.STANDARD && offsets[1] != 0
|| tztype == TimeType.DAYLIGHT && offsets[1] == 0) {
// Roll back one day and try it again.
// Note: This code assumes 1. timezone transition only happens
// once within 24 hours at max
@ -1663,7 +1662,7 @@ public class SimpleDateFormat extends DateFormat {
// Now, compare the results with parsed type, either standard or
// daylight saving time
int resolvedSavings = offsets[1];
if (tztype == TZTYPE_STD) {
if (tztype == TimeType.STANDARD) {
if (offsets[1] != 0) {
// Override DST_OFFSET = 0 in the result calendar
resolvedSavings = 0;
@ -2212,11 +2211,7 @@ public class SimpleDateFormat extends DateFormat {
Style style = (count < 4) ? Style.SPECIFIC_SHORT : Style.SPECIFIC_LONG;
TimeZone tz = tzFormat().parse(style, text, pos, tzTimeType);
if (tz != null) {
if (tzTimeType.value == TimeType.STANDARD) {
tztype = TZTYPE_STD;
} else if (tzTimeType.value == TimeType.DAYLIGHT) {
tztype = TZTYPE_DST;
}
tztype = tzTimeType.value;
cal.setTimeZone(tz);
return pos.getIndex();
}
@ -2228,11 +2223,7 @@ public class SimpleDateFormat extends DateFormat {
Style style = (count < 4) ? Style.RFC822 : ((count == 5) ? Style.ISO8601 : Style.LOCALIZED_GMT);
TimeZone tz = tzFormat().parse(style, text, pos, tzTimeType);
if (tz != null) {
if (tzTimeType.value == TimeType.STANDARD) {
tztype = TZTYPE_STD;
} else if (tzTimeType.value == TimeType.DAYLIGHT) {
tztype = TZTYPE_DST;
}
tztype = tzTimeType.value;
cal.setTimeZone(tz);
return pos.getIndex();
}
@ -2245,11 +2236,7 @@ public class SimpleDateFormat extends DateFormat {
Style style = (count < 4) ? Style.GENERIC_SHORT : Style.GENERIC_LONG;
TimeZone tz = tzFormat().parse(style, text, pos, tzTimeType);
if (tz != null) {
if (tzTimeType.value == TimeType.STANDARD) {
tztype = TZTYPE_STD;
} else if (tzTimeType.value == TimeType.DAYLIGHT) {
tztype = TZTYPE_DST;
}
tztype = tzTimeType.value;
cal.setTimeZone(tz);
return pos.getIndex();
}
@ -2262,11 +2249,7 @@ public class SimpleDateFormat extends DateFormat {
Style style = (count < 4) ? Style.SPECIFIC_SHORT : Style.GENERIC_LOCATION;
TimeZone tz = tzFormat().parse(style, text, pos, tzTimeType);
if (tz != null) {
if (tzTimeType.value == TimeType.STANDARD) {
tztype = TZTYPE_STD;
} else if (tzTimeType.value == TimeType.DAYLIGHT) {
tztype = TZTYPE_DST;
}
tztype = tzTimeType.value;
cal.setTimeZone(tz);
return pos.getIndex();
}

View File

@ -244,7 +244,8 @@ public class TimeZoneFormat extends UFormat implements Freezable<TimeZoneFormat>
*/
private transient volatile TimeZoneGenericNames _gnames;
private transient String[] _gmtPatternTokens;
private transient String _gmtPatternPrefix;
private transient String _gmtPatternSuffix;
private transient Object[][] _gmtOffsetPatternItems;
private transient String _region;
@ -273,15 +274,18 @@ public class TimeZoneFormat extends UFormat implements Freezable<TimeZoneFormat>
GMTOffsetPatternType.POSITIVE_HM, GMTOffsetPatternType.NEGATIVE_HM,
};
private static final int MILLIS_PER_HOUR = 60 * 60 * 1000;
private static final int MILLIS_PER_MINUTE = 60 * 1000;
private static final int MILLIS_PER_SECOND = 1000;
// Maximum offset (exclusive) in millisecond supported by offset formats
private static final int MAX_OFFSET = 24 * MILLIS_PER_HOUR;
// Maximum values for GMT offset fields
private static final int MAX_OFFSET_HOUR = 23;
private static final int MAX_OFFSET_MINUTE = 59;
private static final int MAX_OFFSET_SECOND = 59;
private static final int MILLIS_PER_HOUR = 60 * 60 * 1000;
private static final int MILLIS_PER_MINUTE = 60 * 1000;
private static final int MILLIS_PER_SECOND = 1000;
private static final int UNKNOWN_OFFSET = Integer.MAX_VALUE;
private static TimeZoneFormatCache _tzfCache = new TimeZoneFormatCache();
@ -621,13 +625,15 @@ public class TimeZoneFormat extends UFormat implements Freezable<TimeZoneFormat>
* @param offset the offset from GMT(UTC) in milliseconds.
* @return the RFC822 style GMT(UTC) offset format.
* @see #parseOffsetRFC822(String, ParsePosition)
* @throws IllegalArgumentException if the specified offset is out of supported range
* (-24 hours &lt; offset &lt; +24 hours).
* @draft ICU 49
* @provisional This API might change or be removed in a future release.
*/
public final String formatOffsetRFC822(int offset) {
// Note: OffsetFields.HMS as maxFields is an ICU extension. RFC822 specification
// defines exactly 4 digits for the offset field in HHss format.
return formatOffsetWithASCIIDigits(offset, null, OffsetFields.HM, OffsetFields.HMS);
return formatOffsetWithAsciiDigits(offset, null, OffsetFields.HM, OffsetFields.HMS);
}
/**
@ -637,6 +643,8 @@ public class TimeZoneFormat extends UFormat implements Freezable<TimeZoneFormat>
* @param offset the offset from GMT(UTC) in milliseconds.
* @return the ISO 8601 style GMT(UTC) offset format.
* @see #parseOffsetISO8601(String, ParsePosition)
* @throws IllegalArgumentException if the specified offset is out of supported range
* (-24 hours &lt; offset &lt; +24 hours).
* @draft ICU 49
* @provisional This API might change or be removed in a future release.
*/
@ -646,7 +654,7 @@ public class TimeZoneFormat extends UFormat implements Freezable<TimeZoneFormat>
}
// Note: OffsetFields.HMS as maxFields is an ICU extension. ISO 8601 specification does
// not support second field.
return formatOffsetWithASCIIDigits(offset, ':', OffsetFields.HM, OffsetFields.HMS);
return formatOffsetWithAsciiDigits(offset, ':', OffsetFields.HM, OffsetFields.HMS);
}
/**
@ -661,6 +669,8 @@ public class TimeZoneFormat extends UFormat implements Freezable<TimeZoneFormat>
* @param offset the offset from GMT(UTC) in milliseconds.
* @return the localized GMT format string
* @see #parseOffsetLocalizedGMT(String, ParsePosition)
* @throws IllegalArgumentException if the specified offset is out of supported range
* (-24 hours &lt; offset &lt; +24 hours).
* @draft ICU 49
* @provisional This API might change or be removed in a future release.
*/
@ -698,7 +708,7 @@ public class TimeZoneFormat extends UFormat implements Freezable<TimeZoneFormat>
}
// Building the GMT format string
buf.append(_gmtPatternTokens[0]);
buf.append(_gmtPatternPrefix);
for (Object item : offsetPatternItems) {
if (item instanceof String) {
@ -720,7 +730,7 @@ public class TimeZoneFormat extends UFormat implements Freezable<TimeZoneFormat>
}
}
}
buf.append(_gmtPatternTokens[1]);
buf.append(_gmtPatternSuffix);
return buf.toString();
}
@ -858,7 +868,7 @@ public class TimeZoneFormat extends UFormat implements Freezable<TimeZoneFormat>
// Parse digits
pos.setIndex(start + 1);
int offset = parseContiguousAsciiDigitOffset(text, pos, OffsetFields.H, OffsetFields.HMS, false);
int offset = parseAbuttingAsciiOffsetFields(text, pos, OffsetFields.H, OffsetFields.HMS, false);
if (pos.getErrorIndex() != -1) {
pos.setIndex(start); // reset
@ -906,206 +916,6 @@ public class TimeZoneFormat extends UFormat implements Freezable<TimeZoneFormat>
return parseOffsetLocalizedGMT(text, pos, null);
}
/**
* Returns a <code>TimeZone</code> by parsing the time zone string according to
* the specified parse position, the style and the parse options.
* <p>
* <b>Note:</b>When the input text does not match the specified style, this method
* evaluate the input using the following order and return the longest match.
* <ol>
* <li>ISO 8601 style time zone format</li>
* <li>RFC822 style time zone format</li>
* <li>Localized GMT offset format</li>
* <li>Time zone display names available for the given <code>style</code> argument</li>
* <li>When {@link ParseOption#ALL_STYLES} is enabled in the parse options, all time zone
* display names other than the <code>style</code></li>
* </ol>
* @param text the text contains a time zone string at the position.
* @param style the format style
* @param pos the position.
* @param options the parse options to be used, or null to use the default parse options.
* @param timeType The output argument for receiving the time type (standard/daylight/unknown),
* or specify null if the information is not necessary.
* @return A <code>TimeZone</code>, or null if the input could not be parsed.
* @see Style
* @see ParseOption
* @draft ICU 49
* @provisional This API might change or be removed in a future release.
*/
public TimeZone parseX(Style style, String text, ParsePosition pos, EnumSet<ParseOption> options, Output<TimeType> timeType) {
boolean parseAllStyles;
if (options == null) {
parseAllStyles = _parseAllStyles;
} else {
parseAllStyles = options.contains(ParseOption.ALL_STYLES);
}
if (timeType != null) {
timeType.value = TimeType.UNKNOWN;
}
int startIdx = pos.getIndex();
ParsePosition tmpPos = new ParsePosition(startIdx);
// try RFC822
int offset = parseOffsetRFC822(text, tmpPos);
if (tmpPos.getErrorIndex() < 0) {
pos.setIndex(tmpPos.getIndex());
return getTimeZoneForOffset(offset);
}
// try Localized GMT
int gmtZeroLen = 0;
tmpPos.setErrorIndex(-1);
tmpPos.setIndex(pos.getIndex());
Output<Boolean> hasDigitOffset = new Output<Boolean>(false);
offset = parseOffsetLocalizedGMT(text, tmpPos, hasDigitOffset);
if (tmpPos.getErrorIndex() < 0) {
if (hasDigitOffset.value || style == Style.LOCALIZED_GMT || style == Style.RFC822 || tmpPos.getIndex() == text.length()) {
// When GMT zero format was detected, we won't try other styles if;
// 1) LOCALIZED_GMT or RFC822 was requested.
// 2) The input text was fully consumed.
//
// Note: Localized GMT format with offset numbers (such as "GMT+03:00") won't collide with other type of names
// practically. But GMT zero formats (localized one + global ones - "GMT", "UTC", "UT") could - for example,
// if a locale has a time zone name like "Utah Time", it should not be detected as GMT ("UT" matches the first
// 2 letters).
pos.setIndex(tmpPos.getIndex());
return getTimeZoneForOffset(offset);
} else {
// Preserve the length of GMT zero format.
// If no better matches are found later, GMT should be returned.
gmtZeroLen = tmpPos.getIndex() - startIdx;
}
}
if (!parseAllStyles && (style == Style.RFC822 || style == Style.LOCALIZED_GMT)) {
pos.setErrorIndex(pos.getErrorIndex());
return null;
}
// Find the best match within names which are possibly produced by the style
if (style == Style.SPECIFIC_LONG || style == Style.SPECIFIC_SHORT) {
// Specific styles
EnumSet<NameType> nameTypes = null;
switch (style) {
case SPECIFIC_LONG:
nameTypes = EnumSet.of(NameType.LONG_STANDARD, NameType.LONG_DAYLIGHT);
break;
case SPECIFIC_SHORT:
nameTypes = EnumSet.of(NameType.SHORT_STANDARD, NameType.SHORT_DAYLIGHT);
break;
}
Collection<MatchInfo> specificMatches = _tznames.find(text, startIdx, nameTypes);
if (specificMatches != null) {
int matchLen = 0;
MatchInfo bestSpecific = null;
for (MatchInfo match : specificMatches) {
if (bestSpecific == null || match.matchLength() > matchLen) {
bestSpecific = match;
matchLen = match.matchLength();
}
}
if (bestSpecific != null) {
if (timeType != null) {
timeType.value = getTimeType(bestSpecific.nameType());
}
pos.setIndex(startIdx + bestSpecific.matchLength());
return TimeZone.getTimeZone(getTimeZoneID(bestSpecific.tzID(), bestSpecific.mzID()));
}
}
} else {
// Generic styles
assert(style == Style.GENERIC_LOCATION || style == Style.GENERIC_LONG || style == Style.GENERIC_SHORT);
EnumSet<GenericNameType> genericNameTypes = null;
switch (style) {
case GENERIC_LOCATION:
genericNameTypes = EnumSet.of(GenericNameType.LOCATION);
break;
case GENERIC_LONG:
genericNameTypes = EnumSet.of(GenericNameType.LONG, GenericNameType.LOCATION);
break;
case GENERIC_SHORT:
genericNameTypes = EnumSet.of(GenericNameType.SHORT, GenericNameType.LOCATION);
break;
}
GenericMatchInfo bestGeneric = getTimeZoneGenericNames().findBestMatch(text, startIdx, genericNameTypes);
if (bestGeneric != null) {
if (timeType != null) {
timeType.value = bestGeneric.timeType();
}
pos.setIndex(startIdx + bestGeneric.matchLength());
return TimeZone.getTimeZone(bestGeneric.tzID());
}
}
// If GMT zero format was detected at the beginning, but there was no better match found
// in names available for the given style, then GMT is returned here.
// This should be done before evaluating other names even parseAllStyles is true, because
// all styles (except RFC822 and LOCALIZED_GMT itself) use LOCALIZED_GMT as the final
// fallback.
if (gmtZeroLen > 0) {
pos.setIndex(startIdx + gmtZeroLen);
return getTimeZoneForOffset(0);
}
// If no match was found above, check if parseAllStyle is enabled.
// If so, find the longest match in all possible names.
// For example, when style is GENERIC_LONG, "EST" (SPECIFIC_SHORT) is never
// used for America/New_York. With parseAllStyles true, this code parses "EST"
// as America/New_York.
if (parseAllStyles) {
int maxMatchLength = text.length() - startIdx;
// Try specific names first
Collection<MatchInfo> specificMatches = _tznames.find(text, startIdx, ALL_SPECIFIC_NAME_TYPES);
MatchInfo bestSpecific = null;
if (specificMatches != null) {
int matchLen = 0;
for (MatchInfo match : specificMatches) {
if (bestSpecific == null || match.matchLength() > matchLen) {
bestSpecific = match;
matchLen = match.matchLength();
}
}
if (bestSpecific != null && bestSpecific.matchLength() == maxMatchLength) {
// complete match
if (timeType != null) {
timeType.value = getTimeType(bestSpecific.nameType());
}
pos.setIndex(startIdx + bestSpecific.matchLength());
return TimeZone.getTimeZone(getTimeZoneID(bestSpecific.tzID(), bestSpecific.mzID()));
}
}
// Then generic names
GenericMatchInfo bestGeneric = getTimeZoneGenericNames().findBestMatch(text, startIdx, ALL_GENERIC_NAME_TYPES);
if (bestSpecific != null || bestGeneric != null) {
if (bestGeneric == null ||
(bestSpecific != null && bestSpecific.matchLength() > bestGeneric.matchLength())) {
// the best specific match
if (timeType != null) {
timeType.value = getTimeType(bestSpecific.nameType());
}
pos.setIndex(startIdx + bestSpecific.matchLength());
return TimeZone.getTimeZone(getTimeZoneID(bestSpecific.tzID(), bestSpecific.mzID()));
} else if (bestGeneric != null){
// the best generic match
if (timeType != null) {
timeType.value = bestGeneric.timeType();
}
pos.setIndex(startIdx + bestGeneric.matchLength());
return TimeZone.getTimeZone(bestGeneric.tzID());
}
}
}
pos.setErrorIndex(startIdx);
return null;
}
public TimeZone parse(Style style, String text, ParsePosition pos, EnumSet<ParseOption> options, Output<TimeType> timeType) {
if (timeType == null) {
timeType = new Output<TimeType>(TimeType.UNKNOWN);
@ -1189,13 +999,11 @@ public class TimeZoneFormat extends UFormat implements Freezable<TimeZoneFormat>
{
// Specific styles
EnumSet<NameType> nameTypes = null;
switch (style) {
case SPECIFIC_LONG:
nameTypes = EnumSet.of(NameType.LONG_STANDARD, NameType.LONG_DAYLIGHT);
break;
case SPECIFIC_SHORT:
if (style == Style.SPECIFIC_LONG) {
nameTypes = EnumSet.of(NameType.LONG_STANDARD, NameType.LONG_DAYLIGHT);
} else {
assert style == Style.SPECIFIC_SHORT;
nameTypes = EnumSet.of(NameType.SHORT_STANDARD, NameType.SHORT_DAYLIGHT);
break;
}
Collection<MatchInfo> specificMatches = _tznames.find(text, startIdx, nameTypes);
if (specificMatches != null) {
@ -1600,13 +1408,12 @@ public class TimeZoneFormat extends UFormat implements Freezable<TimeZoneFormat>
throw new IllegalArgumentException("Bad localized GMT pattern: " + gmtPattern);
}
_gmtPattern = gmtPattern;
_gmtPatternTokens = new String[2];
_gmtPatternTokens[0] = unquote(gmtPattern.substring(0, idx));
_gmtPatternTokens[1] = unquote(gmtPattern.substring(idx + 3));
_gmtPatternPrefix = unquote(gmtPattern.substring(0, idx));
_gmtPatternSuffix = unquote(gmtPattern.substring(idx + 3));
}
/**
* Unquotes the message format style pattern
* Unquotes the message format style pattern.
*
* @param s the pattern
* @return the unquoted pattern string
@ -1760,8 +1567,8 @@ public class TimeZoneFormat extends UFormat implements Freezable<TimeZoneFormat>
}
itemType = ch;
itemLength = 1;
checkBits.set(patFieldIdx);
}
checkBits.set(patFieldIdx);
} else {
// a string literal
if (itemType != 0) {
@ -1879,26 +1686,25 @@ public class TimeZoneFormat extends UFormat implements Freezable<TimeZoneFormat>
do {
// Prefix part
int len = _gmtPatternTokens[0].length();
if (len > 0 && !text.regionMatches(true, idx, _gmtPatternTokens[0], 0, len)) {
int len = _gmtPatternPrefix.length();
if (len > 0 && !text.regionMatches(true, idx, _gmtPatternPrefix, 0, len)) {
// prefix match failed
break;
}
idx += len;
// Offset part
int[] tmpOffset = new int[1];
int offsetLen = parseGMTOffset(text, idx, false, tmpOffset);
if (offsetLen == 0) {
int[] offsetLen = new int[1];
offset = parseOffsetFields(text, idx, false, offsetLen);
if (offsetLen[0] == 0) {
// offset field match failed
break;
}
offset = tmpOffset[0];
idx += offsetLen;
idx += offsetLen[0];
// Suffix part
len = _gmtPatternTokens[1].length();
if (len > 0 && !text.regionMatches(true, idx, _gmtPatternTokens[1], 0, len)) {
len = _gmtPatternSuffix.length();
if (len > 0 && !text.regionMatches(true, idx, _gmtPatternSuffix, 0, len)) {
// no suffix match
break;
}
@ -1910,17 +1716,19 @@ public class TimeZoneFormat extends UFormat implements Freezable<TimeZoneFormat>
if (parsed) {
if (hasDigitOffset != null) {
hasDigitOffset.value = true;
} pos.setIndex(idx);
}
pos.setIndex(idx);
return offset;
}
// Try the default patterns
int[] parsedLength = {0};
offset = parseDefaultGMT(text, start, parsedLength);
offset = parseOffsetDefaultLocalizedGMT(text, start, parsedLength);
if (parsedLength[0] > 0) {
if (hasDigitOffset != null) {
hasDigitOffset.value = true;
} pos.setIndex(start + parsedLength[0]);
}
pos.setIndex(start + parsedLength[0]);
return offset;
}
@ -1944,24 +1752,30 @@ public class TimeZoneFormat extends UFormat implements Freezable<TimeZoneFormat>
}
/**
* Parses localized GMT string into offset.
* Parses localized GMT offset fields into offset.
*
* @param text the input text
* @param start the start index
* @param minimumHourWidth the minimum hour width, 1 or 2.
* @param offset the result offset set to offset[0]
* @return parsed length
* @param minimumHourWidth true if the parser allows hour field width to be 1
* @param parsedLen the parsed length, or 0 on failure.
* @return the parsed offset in milliseconds.
*/
private int parseGMTOffset(String text, int start, boolean minimumHourWidth, int[] offset) {
int parsedLen = 0;
int[] tmpParsedLen = new int[1];
offset[0] = 0;
private int parseOffsetFields(String text, int start, boolean minimumHourWidth, int[] parsedLen) {
int outLen = 0;
int[] tmpParsedLen = {0};
int offset = 0;
boolean sawVarHourAndAbuttingField = false;
if (parsedLen != null && parsedLen.length >= 1) {
parsedLen[0] = 0;
}
for (GMTOffsetPatternType gmtPatType : PARSE_GMT_OFFSET_TYPES) {
int offsetH = 0, offsetM = 0, offsetS = 0;
int idx = start;
Object[] items = _gmtOffsetPatternItems[gmtPatType.ordinal()];
assert items != null;
boolean failed = false;
for (int i = 0; i < items.length; i++) {
if (items[i] instanceof String) {
@ -1980,15 +1794,15 @@ public class TimeZoneFormat extends UFormat implements Freezable<TimeZoneFormat>
int minDigits = 1;
int maxDigits = minimumHourWidth ? 1 : 2;
if (!minimumHourWidth && !sawVarHourAndAbuttingField) {
if (i + 1 < items.length && (items[i] instanceof GMTOffsetField)) {
if (i + 1 < items.length && (items[i + 1] instanceof GMTOffsetField)) {
sawVarHourAndAbuttingField = true;
}
}
offsetH = parseOffsetDigits(text, idx, minDigits, maxDigits, 0, MAX_OFFSET_HOUR, tmpParsedLen);
offsetH = parseOffsetFieldWithLocalizedDigits(text, idx, minDigits, maxDigits, 0, MAX_OFFSET_HOUR, tmpParsedLen);
} else if (fieldType == 'm') {
offsetM = parseOffsetDigits(text, idx, 2, 2, 0, MAX_OFFSET_MINUTE, tmpParsedLen);
offsetM = parseOffsetFieldWithLocalizedDigits(text, idx, 2, 2, 0, MAX_OFFSET_MINUTE, tmpParsedLen);
} else if (fieldType == 's') {
offsetS = parseOffsetDigits(text, idx, 2, 2, 0, MAX_OFFSET_SECOND, tmpParsedLen);
offsetS = parseOffsetFieldWithLocalizedDigits(text, idx, 2, 2, 0, MAX_OFFSET_SECOND, tmpParsedLen);
}
if (tmpParsedLen[0] == 0) {
@ -2000,13 +1814,13 @@ public class TimeZoneFormat extends UFormat implements Freezable<TimeZoneFormat>
}
if (!failed) {
int sign = gmtPatType.isPositive() ? 1 : -1;
offset[0] = ((((offsetH * 60) + offsetM) * 60) + offsetS) * 1000 * sign;
parsedLen = idx - start;
offset = ((((offsetH * 60) + offsetM) * 60) + offsetS) * 1000 * sign;
outLen = idx - start;
break;
}
}
if (parsedLen == 0 && sawVarHourAndAbuttingField && !minimumHourWidth) {
if (outLen == 0 && sawVarHourAndAbuttingField && !minimumHourWidth) {
// When hour field is variable width and another non-literal pattern
// field follows, the parse loop above might eat up the digit from
// the abutting field. For example, with pattern "-Hmm" and input "-100",
@ -2019,14 +1833,24 @@ public class TimeZoneFormat extends UFormat implements Freezable<TimeZoneFormat>
// the option is designed for supporting the case like "GMT+5". In this case,
// we should get better result for parsing hour digits as much as possible.
return parseGMTOffset(text, start, true, offset);
return parseOffsetFields(text, start, true, parsedLen);
}
return parsedLen;
if (parsedLen != null && parsedLen.length >= 1) {
parsedLen[0] = outLen;
}
return offset;
}
private int parseDefaultGMT(String text, int start, int[] parsedLength) {
int idx = start;;
/**
* Parses the input text using the default format patterns (e.g. "UTC{0}").
* @param text the input text
* @param start the start index
* @param parsedLen the parsed length, or 0 on failure
* @return the parsed offset in milliseconds.
*/
private int parseOffsetDefaultLocalizedGMT(String text, int start, int[] parsedLen) {
int idx = start;
int offset = 0;
int parsed = 0;
do {
@ -2085,32 +1909,40 @@ public class TimeZoneFormat extends UFormat implements Freezable<TimeZoneFormat>
parsed = idx - start;
} while (false);
parsedLength[0] = parsed;
parsedLen[0] = parsed;
return offset;
}
private int parseDefaultOffsetFields(String text, int start, char separator, int[] parsedLength) {
/**
* Parses the input GMT offset fields with the default offset pattern.
* @param text the input text
* @param start the start index
* @param separator the separator character, e.g. ':'
* @param parsedLen the parsed length, or 0 on failure.
* @return the parsed offset in milliseconds.
*/
private int parseDefaultOffsetFields(String text, int start, char separator, int[] parsedLen) {
int max = text.length();
int idx = start;
int[] len = {0};
int hour = 0, min = 0, sec = 0;
do {
hour = parseOffsetDigits(text, idx, 1, 2, 0, MAX_OFFSET_HOUR, len);
hour = parseOffsetFieldWithLocalizedDigits(text, idx, 1, 2, 0, MAX_OFFSET_HOUR, len);
if (len[0] == 0) {
break;
}
idx += len[0];
if (idx + 1 < max && text.charAt(idx) == separator) {
min = parseOffsetDigits(text, idx + 1, 2, 2, 0, MAX_OFFSET_MINUTE, len);
min = parseOffsetFieldWithLocalizedDigits(text, idx + 1, 2, 2, 0, MAX_OFFSET_MINUTE, len);
if (len[0] == 0) {
break;
}
idx += (1 + len[0]);
if (idx + 1 < max && text.charAt(idx) == separator) {
sec = parseOffsetDigits(text, idx + 1, 2, 2, 0, MAX_OFFSET_SECOND, len);
sec = parseOffsetFieldWithLocalizedDigits(text, idx + 1, 2, 2, 0, MAX_OFFSET_SECOND, len);
if (len[0] == 0) {
break;
}
@ -2120,15 +1952,22 @@ public class TimeZoneFormat extends UFormat implements Freezable<TimeZoneFormat>
} while (false);
if (idx == start) {
parsedLength[0] = 0;
parsedLen[0] = 0;
return 0;
}
parsedLength[0] = idx - start;
parsedLen[0] = idx - start;
return hour * MILLIS_PER_HOUR + min * MILLIS_PER_MINUTE + sec * MILLIS_PER_SECOND;
}
private int parseAbuttingOffsetFields(String text, int start, int[] parsedLength) {
/**
* Parses abutting localized GMT offset fields (such as 0800) into offset.
* @param text the input text
* @param start the start index
* @param parsedLen the parsed length, or 0 on failure
* @return the parsed offset in milliseconds.
*/
private int parseAbuttingOffsetFields(String text, int start, int[] parsedLen) {
final int MAXDIGITS = 6;
int[] digits = new int[MAXDIGITS];
int[] parsed = new int[MAXDIGITS]; // accumulative offsets
@ -2138,7 +1977,7 @@ public class TimeZoneFormat extends UFormat implements Freezable<TimeZoneFormat>
int[] len = {0};
int numDigits = 0;
for (int i = 0; i < MAXDIGITS; i++) {
digits[i] = parseSingleDigit(text, idx, len);
digits[i] = parseSingleLocalizedDigit(text, idx, len);
if (digits[i] < 0) {
break;
}
@ -2148,7 +1987,7 @@ public class TimeZoneFormat extends UFormat implements Freezable<TimeZoneFormat>
}
if (numDigits == 0) {
parsedLength[0] = 0;
parsedLen[0] = 0;
return 0;
}
@ -2188,7 +2027,7 @@ public class TimeZoneFormat extends UFormat implements Freezable<TimeZoneFormat>
if (hour <= MAX_OFFSET_HOUR && min <= MAX_OFFSET_MINUTE && sec <= MAX_OFFSET_SECOND) {
// found a valid combination
offset = hour * MILLIS_PER_HOUR + min * MILLIS_PER_MINUTE + sec * MILLIS_PER_SECOND;
parsedLength[0] = parsed[numDigits - 1];
parsedLen[0] = parsed[numDigits - 1];
break;
}
numDigits--;
@ -2197,7 +2036,7 @@ public class TimeZoneFormat extends UFormat implements Freezable<TimeZoneFormat>
}
/**
* Read an offset field number. This method will stop parsing when
* Reads an offset field value. This method will stop parsing when
* 1) number of digits reaches <code>maxDigits</code>
* 2) just before already parsed number exceeds <code>maxVal</code>
*
@ -2207,20 +2046,20 @@ public class TimeZoneFormat extends UFormat implements Freezable<TimeZoneFormat>
* @param maxDigits the maximum number of digits
* @param minVal the minimum value
* @param maxVal the maximum value
* @param parsedLength the actual parsed length is set to parsedLength[0], must not be null.
* @param parsedLen the actual parsed length is set to parsedLen[0], must not be null.
* @return the integer value parsed
*/
private int parseOffsetDigits(String text, int start, int minDigits, int maxDigits,
int minVal, int maxVal, int[] parsedLength) {
private int parseOffsetFieldWithLocalizedDigits(String text, int start, int minDigits, int maxDigits,
int minVal, int maxVal, int[] parsedLen) {
parsedLength[0] = 0;
parsedLen[0] = 0;
int decVal = 0;
int numDigits = 0;
int idx = start;
int[] digitLen = {0};
while (idx < text.length() && numDigits < maxDigits) {
int digit = parseSingleDigit(text, idx, digitLen);
int digit = parseSingleLocalizedDigit(text, idx, digitLen);
if (digit < 0) {
break;
}
@ -2238,18 +2077,27 @@ public class TimeZoneFormat extends UFormat implements Freezable<TimeZoneFormat>
decVal = -1;
numDigits = 0;
} else {
parsedLength[0] = idx - start;
parsedLen[0] = idx - start;
}
return decVal;
}
private int parseSingleDigit(String text, int offset, int[] len) {
/**
* Reads a single decimal digit, either localized digits used by this object
* or any Unicode numeric character.
* @param text the text
* @param start the start index
* @param len the actual length read from the text
* the start index is not a decimal number.
* @return the integer value of the parsed digit, or -1 on failure.
*/
private int parseSingleLocalizedDigit(String text, int start, int[] len) {
int digit = -1;
len[0] = 0;
if (offset < text.length()) {
int cp = Character.codePointAt(text, offset);
if (start < text.length()) {
int cp = Character.codePointAt(text, start);
// First, try digits configured for this instance
for (int i = 0; i < _gmtOffsetDigits.length; i++) {
@ -2335,13 +2183,13 @@ public class TimeZoneFormat extends UFormat implements Freezable<TimeZoneFormat>
return 0;
}
ParsePosition posOffset = new ParsePosition(start + 1);
int offset = parseAsciiDigitOffsetWithSeparators(text, posOffset, ':', OffsetFields.H, OffsetFields.HMS, false);
if (posOffset.getErrorIndex() == -1 && !extendedOnly) {
int offset = parseAsciiOffsetFields(text, posOffset, ':', OffsetFields.H, OffsetFields.HMS, false);
if (posOffset.getErrorIndex() == -1 && !extendedOnly && (posOffset.getIndex() - start <= 3)) {
// If the text is successfully parsed as extended format with the options above, it can be also parsed
// as basic format. For example, "0230" can be parsed as offset 2:00 (only first digits are valid for
// extended format), but it can be parsed as offset 2:30 with basic format. We use longer result.
ParsePosition posBasic = new ParsePosition(start + 1);
int tmpOffset = parseContiguousAsciiDigitOffset(text, posBasic, OffsetFields.H, OffsetFields.HMS, false);
int tmpOffset = parseAbuttingAsciiOffsetFields(text, posBasic, OffsetFields.H, OffsetFields.HMS, false);
if (posBasic.getErrorIndex() == -1 && posBasic.getIndex() > posOffset.getIndex()) {
offset = tmpOffset;
posOffset.setIndex(posBasic.getIndex());
@ -2368,16 +2216,22 @@ public class TimeZoneFormat extends UFormat implements Freezable<TimeZoneFormat>
}
/**
* Format offset using ASCII digits
* Formats offset using ASCII digits
* @param offset The offset
* @param sep The field separator character or null if not required
* @param minFields The minimum fields
* @param maxFields The maximum fields
* @return The offset string
* @throws IllegalArgumentException if the specified offset is out of supported range
* (-24 hours &lt; offset &lt; +24 hours).
*/
private static String formatOffsetWithASCIIDigits(int offset, Character sep, OffsetFields minFields, OffsetFields maxFields) {
private static String formatOffsetWithAsciiDigits(int offset, Character sep, OffsetFields minFields, OffsetFields maxFields) {
assert maxFields.ordinal() >= minFields.ordinal();
if (Math.abs(offset) >= MAX_OFFSET) {
throw new IllegalArgumentException("Offset out of range :" + offset);
}
StringBuilder buf = new StringBuilder();
char sign = '+';
if (offset < 0) {
@ -2393,9 +2247,9 @@ public class TimeZoneFormat extends UFormat implements Freezable<TimeZoneFormat>
offset = offset % MILLIS_PER_MINUTE;
fields[2] = offset / MILLIS_PER_SECOND;
assert(fields[0] >= 0 && fields[0] < 100);
assert(fields[1] >= 0 && fields[1] < 60);
assert(fields[2] >= 0 && fields[2] < 60);
assert(fields[0] >= 0 && fields[0] <= MAX_OFFSET_HOUR);
assert(fields[1] >= 0 && fields[1] <= MAX_OFFSET_MINUTE);
assert(fields[2] >= 0 && fields[2] <= MAX_OFFSET_SECOND);
int lastIdx = maxFields.ordinal();
while (lastIdx > minFields.ordinal()) {
@ -2418,7 +2272,7 @@ public class TimeZoneFormat extends UFormat implements Freezable<TimeZoneFormat>
}
/**
* Parse offset represented by contiguous ASCII digits
* Parses offset represented by contiguous ASCII digits
* <p>
* Note: This method expects the input position is already at the start of
* ASCII digits and does not parse sign (+/-).
@ -2427,14 +2281,14 @@ public class TimeZoneFormat extends UFormat implements Freezable<TimeZoneFormat>
* @param pos The parse position
* @param minFields The minimum Fields to be parsed
* @param maxFields The maximum Fields to be parsed
* @param fixedHourWitdh true if hour field must be width of 2
* @param fixedHourWidth true if hour field must be width of 2
* @return Parsed offset, 0 or positive number.
*/
private int parseContiguousAsciiDigitOffset(String text, ParsePosition pos,
OffsetFields minFields, OffsetFields maxFields, boolean fixedHourWitdh) {
private static int parseAbuttingAsciiOffsetFields(String text, ParsePosition pos,
OffsetFields minFields, OffsetFields maxFields, boolean fixedHourWidth) {
int start = pos.getIndex();
int minDigits = 2 * (minFields.ordinal() + 1) - (fixedHourWitdh ? 0 : 1);
int minDigits = 2 * (minFields.ordinal() + 1) - (fixedHourWidth ? 0 : 1);
int maxDigits = 2 * (maxFields.ordinal() + 1);
int[] digits = new int[maxDigits];
@ -2450,7 +2304,7 @@ public class TimeZoneFormat extends UFormat implements Freezable<TimeZoneFormat>
idx++;
}
if (fixedHourWitdh && (numDigits % 2 != 0)) {
if (fixedHourWidth && ((numDigits & 1) != 0)) {
// Fixed digits, so the number of digits must be even number. Truncating.
numDigits--;
}
@ -2497,7 +2351,7 @@ public class TimeZoneFormat extends UFormat implements Freezable<TimeZoneFormat>
}
// Truncating
numDigits -= (fixedHourWitdh ? 2 : 1);
numDigits -= (fixedHourWidth ? 2 : 1);
hour = min = sec = 0;
}
@ -2510,7 +2364,7 @@ public class TimeZoneFormat extends UFormat implements Freezable<TimeZoneFormat>
}
/**
* Parse offset represented by ASCII digits and separators.
* Parses offset represented by ASCII digits and separators.
* <p>
* Note: This method expects the input position is already at the start of
* ASCII digits and does not parse sign (+/-).
@ -2520,10 +2374,10 @@ public class TimeZoneFormat extends UFormat implements Freezable<TimeZoneFormat>
* @param sep The separator character
* @param minFields The minimum Fields to be parsed
* @param maxFields The maximum Fields to be parsed
* @param fixedHourWitdh true if hour field must be width of 2
* @param fixedHourWidth true if hour field must be width of 2
* @return Parsed offset, 0 or positive number.
*/
private int parseAsciiDigitOffsetWithSeparators(String text, ParsePosition pos,
private static int parseAsciiOffsetFields(String text, ParsePosition pos,
char sep, OffsetFields minFields, OffsetFields maxFields, boolean fixedHourWidth) {
int start = pos.getIndex();
int[] fieldVal = {0, 0, 0};