/* | |
******************************************************************************* | |
* Copyright (C) 2011, International Business Machines Corporation and * | |
* others. All Rights Reserved. * | |
******************************************************************************* | |
*/ | |
package com.ibm.icu.text; | |
import java.io.IOException; | |
import java.io.ObjectInputStream; | |
import java.io.Serializable; | |
import java.text.AttributedCharacterIterator; | |
import java.text.AttributedString; | |
import java.text.FieldPosition; | |
import java.text.ParseException; | |
import java.text.ParsePosition; | |
import java.util.ArrayList; | |
import java.util.BitSet; | |
import java.util.Collection; | |
import java.util.Date; | |
import java.util.EnumSet; | |
import java.util.List; | |
import java.util.MissingResourceException; | |
import com.ibm.icu.impl.ICUResourceBundle; | |
import com.ibm.icu.impl.SoftCache; | |
import com.ibm.icu.impl.TimeZoneGenericNames; | |
import com.ibm.icu.impl.TimeZoneGenericNames.GenericMatchInfo; | |
import com.ibm.icu.impl.TimeZoneGenericNames.GenericNameType; | |
import com.ibm.icu.impl.ZoneMeta; | |
import com.ibm.icu.lang.UCharacter; | |
import com.ibm.icu.text.TimeZoneNames.MatchInfo; | |
import com.ibm.icu.text.TimeZoneNames.NameType; | |
import com.ibm.icu.util.Calendar; | |
import com.ibm.icu.util.Freezable; | |
import com.ibm.icu.util.Output; | |
import com.ibm.icu.util.TimeZone; | |
import com.ibm.icu.util.ULocale; | |
/** | |
* <code>TimeZoneFormat</code> supports time zone display name formatting and parsing. | |
* An instance of TimeZoneFormat works as a subformatter of {@link SimpleDateFormat}, | |
* but you can also directly get a new instance of <code>TimeZoneFormat</code> and | |
* formatting/parsing time zone display names. | |
* <p> | |
* ICU implements the time zone display names defined by <a href="http://www.unicode.org/reports/tr35/">UTS#35 | |
* Unicode Locale Data Markup Language (LDML)</a>. {@link TimeZoneNames} represents the | |
* time zone display name data model and this class implements the algorithm for actual | |
* formatting and parsing. | |
* | |
* @see SimpleDateFormat | |
* @see TimeZoneNames | |
* @internal ICU 4.8 technology preview | |
* @deprecated This API might change or be removed in a future release. | |
*/ | |
public class TimeZoneFormat extends UFormat implements Freezable<TimeZoneFormat>, Serializable { | |
private static final long serialVersionUID = 2281246852693575022L; | |
/** | |
* Time zone display format style enum used by format/parse APIs in <code>TimeZoneFormat</code>. | |
* | |
* @see TimeZoneFormat#format(Style, TimeZone, long) | |
* @see TimeZoneFormat#format(Style, TimeZone, long, Output) | |
* @see TimeZoneFormat#parse(Style, String, ParsePosition, Output) | |
* @internal ICU 4.8 technology preview | |
* @deprecated This API might change or be removed in a future release. | |
*/ | |
public enum Style { | |
/** | |
* Generic location format, such as "United States Time (New York)", "Italy Time" | |
* @internal ICU 4.8 technology preview | |
* @deprecated This API might change or be removed in a future release. | |
*/ | |
GENERIC_LOCATION, | |
/** | |
* Generic long non-location format, such as "Eastern Time". | |
* @internal ICU 4.8 technology preview | |
* @deprecated This API might change or be removed in a future release. | |
*/ | |
GENERIC_LONG, | |
/** | |
* Generic short non-location format, such as "ET". | |
* @internal ICU 4.8 technology preview | |
* @deprecated This API might change or be removed in a future release. | |
*/ | |
GENERIC_SHORT, | |
/** | |
* Specific long format, such as "Eastern Standard Time". | |
* @internal ICU 4.8 technology preview | |
* @deprecated This API might change or be removed in a future release. | |
*/ | |
SPECIFIC_LONG, | |
/** | |
* Specific short format, such as "EST", "PDT". | |
* @internal ICU 4.8 technology preview | |
* @deprecated This API might change or be removed in a future release. | |
*/ | |
SPECIFIC_SHORT, | |
/** | |
* RFC822 format, such as "-0500" | |
* @internal ICU 4.8 technology preview | |
* @deprecated This API might change or be removed in a future release. | |
*/ | |
RFC822, | |
/** | |
* Localized GMT offset format, such as "GMT-05:00", "UTC+0100" | |
* @internal ICU 4.8 technology preview | |
* @deprecated This API might change or be removed in a future release. | |
*/ | |
LOCALIZED_GMT, | |
/** | |
* Specific short format, such as "EST", "PDT". | |
* <p><b>Note</b>: This is a variant of {@link #SPECIFIC_SHORT}, but | |
* excluding short abbreviations not commonly recognized by people | |
* for the locale. | |
* @internal ICU 4.8 technology preview | |
* @deprecated This API might change or be removed in a future release. | |
*/ | |
SPECIFIC_SHORT_COMMONLY_USED; | |
} | |
/** | |
* Offset pattern type enum. | |
* | |
* @see TimeZoneFormat#getGMTOffsetPattern(GMTOffsetPatternType) | |
* @see TimeZoneFormat#setGMTOffsetPattern(GMTOffsetPatternType, String) | |
* @internal ICU 4.8 technology preview | |
* @deprecated This API might change or be removed in a future release. | |
*/ | |
public enum GMTOffsetPatternType { | |
/** | |
* Positive offset with hour and minute fields | |
* @internal ICU 4.8 technology preview | |
* @deprecated This API might change or be removed in a future release. | |
*/ | |
POSITIVE_HM ("+HH:mm", "Hm", true), | |
/** | |
* Positive offset with hour, minute and second fields | |
* @internal ICU 4.8 technology preview | |
* @deprecated This API might change or be removed in a future release. | |
*/ | |
POSITIVE_HMS ("+HH:mm:ss", "Hms", true), | |
/** | |
* Negative offset with hour and minute fields | |
* @internal ICU 4.8 technology preview | |
* @deprecated This API might change or be removed in a future release. | |
*/ | |
NEGATIVE_HM ("-HH:mm", "Hm", false), | |
/** | |
* Negative offset with hour, minute and second fields | |
* @internal ICU 4.8 technology preview | |
* @deprecated This API might change or be removed in a future release. | |
*/ | |
NEGATIVE_HMS ("-HH:mm:ss", "Hms", false); | |
private String _defaultPattern; | |
private String _required; | |
private boolean _isPositive; | |
private GMTOffsetPatternType(String defaultPattern, String required, boolean isPositive) { | |
_defaultPattern = defaultPattern; | |
_required = required; | |
_isPositive = isPositive; | |
} | |
private String defaultPattern() { | |
return _defaultPattern; | |
} | |
private String required() { | |
return _required; | |
} | |
private boolean isPositive() { | |
return _isPositive; | |
} | |
} | |
/** | |
* Time type enum used for receiving time type (standard time, daylight time or unknown) | |
* in <code>TimeZoneFormat</code> APIs. | |
* | |
* @internal ICU 4.8 technology preview | |
* @deprecated This API might change or be removed in a future release. | |
*/ | |
public enum TimeType { | |
/** | |
* Unknown | |
* @internal ICU 4.8 technology preview | |
* @deprecated This API might change or be removed in a future release. | |
*/ | |
UNKNOWN, | |
/** | |
* Standard time | |
* @internal ICU 4.8 technology preview | |
* @deprecated This API might change or be removed in a future release. | |
*/ | |
STANDARD, | |
/** | |
* Daylight saving time | |
* @internal ICU 4.8 technology preview | |
* @deprecated This API might change or be removed in a future release. | |
*/ | |
DAYLIGHT; | |
} | |
/* | |
* Serialized fields | |
*/ | |
private ULocale _locale; | |
private TimeZoneNames _tznames; | |
private TimeZoneGenericNames _gnames; | |
private String _gmtPattern; | |
private String[] _gmtOffsetPatterns; | |
private String[] _gmtOffsetDigits; | |
private String _gmtZeroFormat; | |
private boolean _parseAllStyles; | |
/* | |
* Transient fields | |
*/ | |
private transient String[] _gmtPatternTokens; | |
private transient Object[][] _gmtOffsetPatternItems; | |
private transient String _region; | |
private transient boolean _frozen; | |
/* | |
* Static final fields | |
*/ | |
private static final String TZID_GMT = "Etc/GMT"; // canonical tzid for GMT | |
private static final String[] ALT_GMT_STRINGS = {"GMT", "UTC", "UT"}; | |
private static final String DEFAULT_GMT_PATTERN = "GMT{0}"; | |
private static final String DEFAULT_GMT_ZERO = "GMT"; | |
private static final String[] DEFAULT_GMT_DIGITS = {"0", "1", "2", "3", "4", "5", "6", "7", "8", "9"}; | |
private static final char DEFAULT_GMT_OFFSET_SEP = ':'; | |
private static final String RFC822_DIGITS = "0123456789"; | |
// Order of GMT offset pattern parsing, *_HMS must be evaluated first | |
// because *_HM is most likely a substring of *_HMS | |
private static final GMTOffsetPatternType[] PARSE_GMT_OFFSET_TYPES = { | |
GMTOffsetPatternType.POSITIVE_HMS, GMTOffsetPatternType.NEGATIVE_HMS, | |
GMTOffsetPatternType.POSITIVE_HM, GMTOffsetPatternType.NEGATIVE_HM, | |
}; | |
// 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 TimeZoneFormatCache _tzfCache = new TimeZoneFormatCache(); | |
// The filter used for searching all specific names | |
private static final EnumSet<NameType> ALL_SPECIFIC_NAME_TYPES = EnumSet.of( | |
NameType.LONG_STANDARD, NameType.LONG_DAYLIGHT, | |
NameType.SHORT_STANDARD, NameType.SHORT_DAYLIGHT, | |
NameType.SHORT_STANDARD_COMMONLY_USED, NameType.SHORT_DAYLIGHT_COMMONLY_USED | |
); | |
// The filter used for searching all generic names | |
private static final EnumSet<GenericNameType> ALL_GENERIC_NAME_TYPES = EnumSet.of( | |
GenericNameType.LOCATION, GenericNameType.LONG, GenericNameType.SHORT | |
); | |
/** | |
* The protected constructor for subclassing. | |
* @param locale the locale | |
* @internal ICU 4.8 technology preview | |
* @deprecated This API might change or be removed in a future release. | |
*/ | |
protected TimeZoneFormat(ULocale locale) { | |
_locale = locale; | |
_tznames = TimeZoneNames.getInstance(locale); | |
_gnames = TimeZoneGenericNames.getInstance(locale); | |
String gmtPattern = null; | |
String hourFormats = null; | |
_gmtZeroFormat = DEFAULT_GMT_ZERO; | |
try { | |
ICUResourceBundle bundle = (ICUResourceBundle) ICUResourceBundle.getBundleInstance( | |
ICUResourceBundle.ICU_ZONE_BASE_NAME, locale); | |
try { | |
gmtPattern = bundle.getStringWithFallback("zoneStrings/gmtFormat"); | |
} catch (MissingResourceException e) { | |
// fall through | |
} | |
try { | |
hourFormats = bundle.getStringWithFallback("zoneStrings/hourFormat"); | |
} catch (MissingResourceException e) { | |
// fall through | |
} | |
try { | |
_gmtZeroFormat = bundle.getStringWithFallback("zoneStrings/gmtZeroFormat"); | |
} catch (MissingResourceException e) { | |
// fall through | |
} | |
} catch (MissingResourceException e) { | |
// fall through | |
} | |
if (gmtPattern == null) { | |
gmtPattern = DEFAULT_GMT_PATTERN; | |
} | |
initGMTPattern(gmtPattern); | |
String[] gmtOffsetPatterns = new String[GMTOffsetPatternType.values().length]; | |
if (hourFormats != null) { | |
String[] hourPatterns = hourFormats.split(";", 2); | |
gmtOffsetPatterns[GMTOffsetPatternType.POSITIVE_HM.ordinal()] = hourPatterns[0]; | |
gmtOffsetPatterns[GMTOffsetPatternType.POSITIVE_HMS.ordinal()] = expandOffsetPattern(hourPatterns[0]); | |
gmtOffsetPatterns[GMTOffsetPatternType.NEGATIVE_HM.ordinal()] = hourPatterns[1]; | |
gmtOffsetPatterns[GMTOffsetPatternType.NEGATIVE_HMS.ordinal()] = expandOffsetPattern(hourPatterns[1]); | |
} else { | |
for (GMTOffsetPatternType patType : GMTOffsetPatternType.values()) { | |
gmtOffsetPatterns[patType.ordinal()] = patType.defaultPattern(); | |
} | |
} | |
initGMTOffsetPatterns(gmtOffsetPatterns); | |
_gmtOffsetDigits = DEFAULT_GMT_DIGITS; | |
NumberingSystem ns = NumberingSystem.getInstance(locale); | |
if (!ns.isAlgorithmic()) { | |
// we do not support algorithmic numbering system for GMT offset for now | |
_gmtOffsetDigits = toCodePoints(ns.getDescription()); | |
} | |
} | |
/** | |
* Returns a frozen instance of <code>TimeZoneFormat</code> for the given locale. | |
* <p><b>Note</b>: The instance returned by this method is frozen. If you want to | |
* customize a TimeZoneFormat, you must use {@link #cloneAsThawed()} to get a | |
* thawed copy first. | |
* | |
* @param locale the locale. | |
* @return a frozen instance of <code>TimeZoneFormat</code> for the given locale. | |
* @internal ICU 4.8 technology preview | |
* @deprecated This API might change or be removed in a future release. | |
*/ | |
public static TimeZoneFormat getInstance(ULocale locale) { | |
if (locale == null) { | |
throw new NullPointerException("locale is null"); | |
} | |
return _tzfCache.getInstance(locale, locale); | |
} | |
/** | |
* Returns the time zone display name data used by this instance. | |
* | |
* @return the time zone display name data. | |
* @see #setTimeZoneNames(TimeZoneNames) | |
* @internal ICU 4.8 technology preview | |
* @deprecated This API might change or be removed in a future release. | |
*/ | |
public TimeZoneNames getTimeZoneNames() { | |
return _tznames; | |
} | |
/** | |
* Sets the time zone display name data to this instance. | |
* | |
* @param tznames the time zone display name data. | |
* @return this object. | |
* @throws UnsupportedOperationException when this object is frozen. | |
* @see #getTimeZoneNames() | |
* @internal ICU 4.8 technology preview | |
* @deprecated This API might change or be removed in a future release. | |
*/ | |
public TimeZoneFormat setTimeZoneNames(TimeZoneNames tznames) { | |
if (isFrozen()) { | |
throw new UnsupportedOperationException("Attempt to modify frozen object"); | |
} | |
_tznames = tznames; | |
// TimeZoneGenericNames must be changed to utilize the new TimeZoneNames instance. | |
_gnames = new TimeZoneGenericNames(_locale, _tznames); | |
return this; | |
} | |
/** | |
* Returns the localized GMT format pattern. | |
* | |
* @return the localized GMT format pattern. | |
* @see #setGMTPattern(String) | |
* @internal ICU 4.8 technology preview | |
* @deprecated This API might change or be removed in a future release. | |
*/ | |
public String getGMTPattern() { | |
return _gmtPattern; | |
} | |
/** | |
* Sets the localized GMT format pattern. The pattern must contain | |
* a single argument {0}, for example "GMT {0}". | |
* | |
* @param pattern the localized GMT format pattern string | |
* @return this object. | |
* @throws IllegalArgumentException when the pattern string does not contain "{0}" | |
* @throws UnsupportedOperationException when this object is frozen. | |
* @see #getGMTPattern() | |
* @internal ICU 4.8 technology preview | |
* @deprecated This API might change or be removed in a future release. | |
*/ | |
public TimeZoneFormat setGMTPattern(String pattern) { | |
if (isFrozen()) { | |
throw new UnsupportedOperationException("Attempt to modify frozen object"); | |
} | |
initGMTPattern(pattern); | |
return this; | |
} | |
/** | |
* Returns the offset pattern used for localized GMT format. | |
* | |
* @param type the offset pattern enum | |
* @return the offset pattern enum. | |
* @see #setGMTOffsetPattern(GMTOffsetPatternType, String) | |
* @internal ICU 4.8 technology preview | |
* @deprecated This API might change or be removed in a future release. | |
*/ | |
public String getGMTOffsetPattern(GMTOffsetPatternType type) { | |
return _gmtOffsetPatterns[type.ordinal()]; | |
} | |
/** | |
* Sets the offset pattern for the given offset type. | |
* | |
* @param type the offset pettern. | |
* @param pattern the pattern string. | |
* @return this object. | |
* @throws IllegalArgumentException when the pattern string does not have required time field letters. | |
* @throws UnsupportedOperationException when this object is frozen. | |
* @see #getGMTOffsetPattern(GMTOffsetPatternType) | |
* @internal ICU 4.8 technology preview | |
* @deprecated This API might change or be removed in a future release. | |
*/ | |
public TimeZoneFormat setGMTOffsetPattern(GMTOffsetPatternType type, String pattern) { | |
if (isFrozen()) { | |
throw new UnsupportedOperationException("Attempt to modify frozen object"); | |
} | |
if (pattern == null) { | |
throw new NullPointerException("Null GMT offset pattern"); | |
} | |
Object[] parsedItems = parseOffsetPattern(pattern, type.required()); | |
_gmtOffsetPatterns[type.ordinal()] = pattern; | |
_gmtOffsetPatternItems[type.ordinal()] = parsedItems; | |
return this; | |
} | |
/** | |
* Returns the decimal digit characters used for localized GMT format in a single string | |
* containing from 0 to 9 in the ascending order. | |
* | |
* @return the decimal digits for localized GMT format. | |
* @see #setGMTOffsetDigits(String) | |
* @internal ICU 4.8 technology preview | |
* @deprecated This API might change or be removed in a future release. | |
*/ | |
public String getGMTOffsetDigits() { | |
StringBuilder buf = new StringBuilder(_gmtOffsetDigits.length); | |
for (String digit : _gmtOffsetDigits) { | |
buf.append(digit); | |
} | |
return buf.toString(); | |
} | |
/** | |
* Sets the decimal digit characters used for localized GMT format. | |
* | |
* @param digits a string contains the decimal digit characters from 0 to 9 n the ascending order. | |
* @return this object. | |
* @throws IllegalArgumentException when the string did not contain ten characters. | |
* @throws UnsupportedOperationException when this object is frozen. | |
* @see #getGMTOffsetDigits() | |
* @internal ICU 4.8 technology preview | |
* @deprecated This API might change or be removed in a future release. | |
*/ | |
public TimeZoneFormat setGMTOffsetDigits(String digits) { | |
if (isFrozen()) { | |
throw new UnsupportedOperationException("Attempt to modify frozen object"); | |
} | |
if (digits == null) { | |
throw new NullPointerException("Null GMT offset digits"); | |
} | |
String[] digitArray = toCodePoints(digits); | |
if (digitArray.length != 10) { | |
throw new IllegalArgumentException("Length of digits must be 10"); | |
} | |
_gmtOffsetDigits = digitArray; | |
return this; | |
} | |
/** | |
* Returns the localized GMT format string for GMT(UTC) itself (GMT offset is 0). | |
* | |
* @return the localized GMT string string for GMT(UTC) itself. | |
* @see #setGMTZeroFormat(String) | |
* @internal ICU 4.8 technology preview | |
* @deprecated This API might change or be removed in a future release. | |
*/ | |
public String getGMTZeroFormat() { | |
return _gmtZeroFormat; | |
} | |
/** | |
* Returns the localized GMT format string for GMT(UTC) itself (GMT offset is 0). | |
* | |
* @param gmtZeroFormat the localized GMT format string for GMT(UTC). | |
* @return this object. | |
* @throws UnsupportedOperationException when this object is frozen. | |
* @see #getGMTZeroFormat() | |
* @internal ICU 4.8 technology preview | |
* @deprecated This API might change or be removed in a future release. | |
*/ | |
public TimeZoneFormat setGMTZeroFormat(String gmtZeroFormat) { | |
if (isFrozen()) { | |
throw new UnsupportedOperationException("Attempt to modify frozen object"); | |
} | |
if (gmtZeroFormat == null) { | |
throw new NullPointerException("Null GMT zero format"); | |
} | |
if (gmtZeroFormat.length() == 0) { | |
throw new IllegalArgumentException("Empty GMT zero format"); | |
} | |
_gmtZeroFormat = gmtZeroFormat; | |
return this; | |
} | |
/** | |
* Returns <code>true</code> when this <code>TimeZoneFormat</code> is configured for parsing | |
* display names including names that are only used by other styles by | |
* {@link #parse(Style, String, ParsePosition, Output)}. | |
* <p><b>Note</b>: An instance created by {@link #getInstance(ULocale)} is configured NOT | |
* parsing all styles (<code>false</code>). | |
* | |
* @return <code>true</code> when this instance is configure for parsing all available names. | |
* @see #setParseAllStyles(boolean) | |
* @internal ICU 4.8 technology preview | |
* @deprecated This API might change or be removed in a future release. | |
*/ | |
public boolean isParseAllStyles() { | |
return _parseAllStyles; | |
} | |
/** | |
* Sets if {@link #parse(Style, String, ParsePosition, Output)} to parse display | |
* names including names that are only used by other styles. | |
* | |
* @param parseAllStyles <code>true</code> to parse all available names. | |
* @return this object. | |
* @throws UnsupportedOperationException when this object is frozen. | |
* @see #isParseAllStyles() | |
* @internal ICU 4.8 technology preview | |
* @deprecated This API might change or be removed in a future release. | |
*/ | |
public TimeZoneFormat setParseAllStyles(boolean parseAllStyles) { | |
if (isFrozen()) { | |
throw new UnsupportedOperationException("Attempt to modify frozen object"); | |
} | |
_parseAllStyles = parseAllStyles; | |
return this; | |
} | |
/** | |
* Returns the RFC822 style time zone string for the given offset. | |
* For example, "-0800". | |
* | |
* @param offset the offset for GMT(UTC) in milliseconds. | |
* @return the RFC822 style GMT(UTC) offset format. | |
* @see #parseOffsetRFC822(String, ParsePosition) | |
* @internal ICU 4.8 technology preview | |
* @deprecated This API might change or be removed in a future release. | |
*/ | |
public final String formatOffsetRFC822(int offset) { | |
StringBuilder buf = new StringBuilder(); | |
char sign = '+'; | |
if (offset < 0) { | |
sign = '-'; | |
offset = -offset; | |
} | |
buf.append(sign); | |
int offsetH = offset / MILLIS_PER_HOUR; | |
offset = offset % MILLIS_PER_HOUR; | |
int offsetM = offset / MILLIS_PER_MINUTE; | |
offset = offset % MILLIS_PER_MINUTE; | |
int offsetS = offset / MILLIS_PER_SECOND; | |
assert(offsetH >= 0 && offsetH < 100); | |
assert(offsetM >= 0 && offsetM < 60); | |
assert(offsetS >= 0 && offsetS < 60); | |
int num = 0, denom = 0; | |
if (offsetS == 0) { | |
offset = offsetH * 100 + offsetM; // HHmm | |
num = offset % 10000; | |
denom = 1000; | |
} else { | |
offset = offsetH * 10000 + offsetM * 100 + offsetS; //HHmmss | |
num = offset % 1000000; | |
denom = 100000; | |
} | |
while (denom >= 1) { | |
char digit = (char)((num / denom) + '0'); | |
buf.append(digit); | |
num = num % denom; | |
denom /= 10; | |
} | |
return buf.toString(); | |
} | |
/** | |
* Returns the localized GMT(UTC) offset format for the given offset. | |
* The localized GMT offset is defined by; | |
* <ul> | |
* <li>GMT format pattern (e.g. "GMT {0}" - see {@link #getGMTPattern()}) | |
* <li>Offset time pattern (e.g. "+HH:mm" - see {@link #getGMTOffsetPattern(GMTOffsetPatternType)}) | |
* <li>Offset digits (e.g. "0123456789" - see {@link #getGMTOffsetDigits()}) | |
* <li>GMT zero format (e.g. "GMT" - see {@link #getGMTZeroFormat()}) | |
* </ul> | |
* @param offset the offset from GMT(UTC) in milliseconds. | |
* @return the localized GMT format string | |
* @see #parseOffsetLocalizedGMT(String, ParsePosition) | |
* @internal ICU 4.8 technology preview | |
* @deprecated This API might change or be removed in a future release. | |
*/ | |
public String formatOffsetLocalizedGMT(int offset) { | |
if (offset == 0) { | |
return _gmtZeroFormat; | |
} | |
StringBuilder buf = new StringBuilder(); | |
boolean positive = true; | |
if (offset < 0) { | |
offset = -offset; | |
positive = false; | |
} | |
int offsetH = offset / MILLIS_PER_HOUR; | |
offset = offset % MILLIS_PER_HOUR; | |
int offsetM = offset / MILLIS_PER_MINUTE; | |
offset = offset % MILLIS_PER_MINUTE; | |
int offsetS = offset / MILLIS_PER_SECOND; | |
if (offsetH > MAX_OFFSET_HOUR || offsetM > MAX_OFFSET_MINUTE || offsetS > MAX_OFFSET_SECOND) { | |
throw new IllegalArgumentException("Offset out of range :" + offset); | |
} | |
Object[] offsetPatternItems; | |
if (positive) { | |
offsetPatternItems = (offsetS == 0) ? | |
_gmtOffsetPatternItems[GMTOffsetPatternType.POSITIVE_HM.ordinal()] : | |
_gmtOffsetPatternItems[GMTOffsetPatternType.POSITIVE_HMS.ordinal()]; | |
} else { | |
offsetPatternItems = (offsetS == 0) ? | |
_gmtOffsetPatternItems[GMTOffsetPatternType.NEGATIVE_HM.ordinal()] : | |
_gmtOffsetPatternItems[GMTOffsetPatternType.NEGATIVE_HMS.ordinal()]; | |
} | |
// Building the GMT format string | |
buf.append(_gmtPatternTokens[0]); | |
for (Object item : offsetPatternItems) { | |
if (item instanceof String) { | |
// pattern literal | |
buf.append((String)item); | |
} else if (item instanceof GMTOffsetField) { | |
// Hour/minute/second field | |
GMTOffsetField field = (GMTOffsetField)item; | |
switch (field.getType()) { | |
case 'H': | |
appendOffsetDigits(buf, offsetH, field.getWidth()); | |
break; | |
case 'm': | |
appendOffsetDigits(buf, offsetM, field.getWidth()); | |
break; | |
case 's': | |
appendOffsetDigits(buf, offsetS, field.getWidth()); | |
break; | |
} | |
} | |
} | |
buf.append(_gmtPatternTokens[1]); | |
return buf.toString(); | |
} | |
/** | |
* Returns the display name of the time zone at the given date for | |
* the style. | |
* | |
* <p><b>Note</b>: A style may have fallback styles defined. For example, | |
* when <code>GENERIC_LONG</code> is requested, but there is no display name | |
* data available for <code>GENERIC_LONG</code> style, the implementation | |
* may use <code>GENERIC_LOCATION</code> or <code>LOCALIZED_GMT</code>. | |
* See UTS#35 UNICODE LOCALE DATA MARKUP LANGUAGE (LDML) | |
* <a href="http://www.unicode.org/reports/tr35/#Time_Zone_Fallback">Appendix J: Time Zone Display Name</a> | |
* for the details. | |
* | |
* @param style the style enum (e.g. <code>GENERIC_LONG</code>, <code>LOCALIZED_GMT</code>...) | |
* @param tz the time zone. | |
* @param date the date. | |
* @return the display name of the time zone. | |
* @see Style | |
* @see #format(Style, TimeZone, long, Output) | |
* @internal ICU 4.8 technology preview | |
* @deprecated This API might change or be removed in a future release. | |
*/ | |
public final String format(Style style, TimeZone tz, long date) { | |
return format(style, tz, date, null); | |
} | |
/** | |
* Returns the display name of the time zone at the given date for | |
* the style. This method takes an extra argument <code>Output<TimeType> timeType</code> | |
* in addition to the argument list of {@link #format(Style, TimeZone, long)}. | |
* The argument is used for receiving the time type (standard time | |
* or daylight saving time, or unknown) actually used for the display name. | |
* | |
* @param style the style enum (e.g. <code>GENERIC_LONG</code>, <code>LOCALIZED_GMT</code>...) | |
* @param tz the time zone. | |
* @param date the date. | |
* @param timeType the output argument for receiving the time type (standard/daylight/unknown) | |
* used for the display name, or specify null if the information is not necessary. | |
* @return the display name of the time zone. | |
* @see Style | |
* @see #format(Style, TimeZone, long) | |
* @internal ICU 4.8 technology preview | |
* @deprecated This API might change or be removed in a future release. | |
*/ | |
public String format(Style style, TimeZone tz, long date, Output<TimeType> timeType) { | |
String result = null; | |
if (timeType != null) { | |
timeType.value = TimeType.UNKNOWN; | |
} | |
switch (style) { | |
case GENERIC_LOCATION: | |
result = _gnames.getGenericLocationName(ZoneMeta.getCanonicalCLDRID(tz)); | |
break; | |
case GENERIC_LONG: | |
result = _gnames.getDisplayName(tz, GenericNameType.LONG, date); | |
break; | |
case GENERIC_SHORT: | |
result = _gnames.getDisplayName(tz, GenericNameType.SHORT, date); | |
break; | |
case SPECIFIC_LONG: | |
result = formatSpecific(tz, NameType.LONG_STANDARD, NameType.LONG_DAYLIGHT, date, timeType); | |
break; | |
case SPECIFIC_SHORT: | |
result = formatSpecific(tz, NameType.SHORT_STANDARD, NameType.SHORT_DAYLIGHT, date, timeType); | |
break; | |
case SPECIFIC_SHORT_COMMONLY_USED: | |
result = formatSpecific(tz, NameType.SHORT_STANDARD_COMMONLY_USED, NameType.SHORT_DAYLIGHT_COMMONLY_USED, date, timeType); | |
break; | |
case RFC822: | |
case LOCALIZED_GMT: | |
// will be handled below | |
break; | |
} | |
if (result == null) { | |
int[] offsets = {0, 0}; | |
tz.getOffset(date, false, offsets); | |
if (style == Style.RFC822) { | |
// RFC822 was requested | |
result = formatOffsetRFC822(offsets[0] + offsets[1]); | |
} else { | |
// LOCALIZED_GMT was requested, or fallback for other types | |
result = formatOffsetLocalizedGMT(offsets[0] + offsets[1]); | |
} | |
// time type | |
if (timeType != null) { | |
timeType.value = (offsets[1] != 0) ? TimeType.DAYLIGHT : TimeType.STANDARD; | |
} | |
} | |
assert(result != null); | |
return result; | |
} | |
/** | |
* Returns offset from GMT(UTC) in milliseconds for the given RFC822 | |
* style time zone string. When the given string is not an RFC822 time zone | |
* string, this method sets the current position as the error index | |
* to <code>ParsePosition pos</code> and returns 0. | |
* | |
* @param text the text contains RFC822 style time zone string (e.g. "-0800") | |
* at the position. | |
* @param pos the position. | |
* @return the offset from GMT(UTC) in milliseconds for the given RFC822 style | |
* time zone string. | |
* @see #formatOffsetRFC822(int) | |
* @internal ICU 4.8 technology preview | |
* @deprecated This API might change or be removed in a future release. | |
*/ | |
public final int parseOffsetRFC822(String text, ParsePosition pos) { | |
int start = pos.getIndex(); | |
if (start + 2 >= text.length()) { | |
// minimum 2 characters | |
pos.setErrorIndex(start); | |
return 0; | |
} | |
int len = 0; | |
int sign; | |
char signChar = text.charAt(start); | |
if (signChar == '+') { | |
sign = 1; | |
} else if (signChar == '-') { | |
sign = -1; | |
} else { | |
// Not an RFC822 offset string | |
pos.setErrorIndex(start); | |
return 0; | |
} | |
len++; | |
// Parse digits | |
// Possible format (excluding sign char) are: | |
// HHmmss | |
// HmmSS | |
// HHmm | |
// Hmm | |
// HH | |
// H | |
int idx = start + 1; | |
int numDigits = 0; | |
int[] digits = new int[6]; | |
while (numDigits < digits.length && idx < text.length()) { | |
int digit = RFC822_DIGITS.indexOf(text.charAt(idx)); | |
if (digit < 0) { | |
break; | |
} | |
digits[numDigits] = digit; | |
numDigits++; | |
idx++; | |
} | |
if (numDigits == 0) { | |
// Not an RFC822 offset string | |
pos.setErrorIndex(start); | |
return 0; | |
} | |
int hour = 0, min = 0, sec = 0; | |
switch (numDigits) { | |
case 1: //H | |
hour = digits[0]; | |
break; | |
case 2: //HH | |
hour = digits[0] * 10 + digits[1]; | |
break; | |
case 3: //Hmm | |
hour = digits[0]; | |
min = digits[1] * 10 + digits[2]; | |
break; | |
case 4: //HHmm | |
hour = digits[0] * 10 + digits[1]; | |
min = digits[2] * 10 + digits[3]; | |
break; | |
case 5: //Hmmss | |
hour = digits[0]; | |
min = digits[1] * 10 + digits[2]; | |
sec = digits[3] * 10 + digits[4]; | |
break; | |
case 6: //HHmmss | |
hour = digits[0] * 10 + digits[1]; | |
min = digits[2] * 10 + digits[3]; | |
sec = digits[4] * 10 + digits[5]; | |
break; | |
} | |
if (hour > MAX_OFFSET_HOUR || min > MAX_OFFSET_MINUTE || sec > MAX_OFFSET_SECOND) { | |
// Invalid value range | |
pos.setErrorIndex(start); | |
return 0; | |
} | |
pos.setIndex(1 + numDigits); | |
return ((((hour * 60) + min) * 60) + sec) * 1000 * sign; | |
} | |
/** | |
* Returns offset from GMT(UTC) in milliseconds for the given localized GMT | |
* offset format string. When the given string cannot be parsed, this method | |
* sets the current position as the error index to <code>ParsePosition pos</code> | |
* and returns 0. | |
* | |
* @param text the text contains a localized GMT offset string at the position. | |
* @param pos the position. | |
* @return the offset from GMT(UTC) in milliseconds for the given localized GMT | |
* offset format string. | |
* @see #formatOffsetLocalizedGMT(int) | |
* @internal ICU 4.8 technology preview | |
* @deprecated This API might change or be removed in a future release. | |
*/ | |
public int parseOffsetLocalizedGMT(String text, ParsePosition pos) { | |
return parseOffsetLocalizedGMT(text, pos, null); | |
} | |
/** | |
* Returns a <code>TimeZone</code> by parsing the time zone string according to | |
* the given parse position. | |
* | |
* <p><b>Note</b>: By default, this method supports 1) RFC822 style time zone format, | |
* 2) Localized GMT offset format and 3) all display names that are used for the | |
* given <code>style</code>. If you want to parse all display names including names that are | |
* only used for styles other than the specified <code>style</code>, then you should | |
* set true to {@link #setParseAllStyles(boolean)}. | |
* | |
* @param text the text contains a time zone string at the position. | |
* @param style the format style | |
* @param pos the position. | |
* @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 #format(Style, TimeZone, long, Output) | |
* @see #setParseAllStyles(boolean) | |
* @internal ICU 4.8 technology preview | |
* @deprecated This API might change or be removed in a future release. | |
*/ | |
public TimeZone parse(Style style, String text, ParsePosition pos, Output<TimeType> timeType) { | |
return parse(style, text, pos, _parseAllStyles, timeType); | |
} | |
/** | |
* Returns a <code>TimeZone</code> by parsing the time zone string according to | |
* the given parse position. | |
* | |
* <p><b>Note</b>: This method is equivalent to <code>parse(Style.GENERIC_LOCATION, | |
* text, pos, null)</code> with {@link #setParseAllStyles(boolean) setParseAllStyles(true)}. | |
* | |
* @param text the text contains a time zone string at the position. | |
* @param pos the position. | |
* @return A <code>TimeZone</code>, or null if the input could not be parsed. | |
* @see #parse(Style, String, ParsePosition, Output) | |
* @internal ICU 4.8 technology preview | |
* @deprecated This API might change or be removed in a future release. | |
*/ | |
public final TimeZone parse(String text, ParsePosition pos) { | |
return parse(Style.GENERIC_LOCATION, text, pos, true, null); | |
} | |
/** | |
* Returns a <code>TimeZone</code> for the given text. | |
* @param text the time zone string | |
* @return A <code>TimeZone</code>. | |
* @throws ParseException when the input could not be parsed as a time zone string. | |
* @see #parse(String, ParsePosition) | |
* @see #parse(Style, String, ParsePosition, Output) | |
* @internal ICU 4.8 technology preview | |
* @deprecated This API might change or be removed in a future release. | |
*/ | |
public final TimeZone parse(String text) throws ParseException { | |
ParsePosition pos = new ParsePosition(0); | |
TimeZone tz = parse(text, pos); | |
if (pos.getErrorIndex() >= 0) { | |
throw new ParseException("Unparseable time zone: \"" + text + "\"" , 0); | |
} | |
assert(tz != null); | |
return tz; | |
} | |
/** | |
* {@inheritDoc} | |
* | |
* @internal ICU 4.8 technology preview | |
* @deprecated This API might change or be removed in a future release. | |
*/ | |
@Override | |
public StringBuffer format(Object obj, StringBuffer toAppendTo, FieldPosition pos) { | |
TimeZone tz = null; | |
long date = System.currentTimeMillis(); | |
if (obj instanceof TimeZone) { | |
tz = (TimeZone)obj; | |
} else if (obj instanceof Calendar) { | |
tz = ((Calendar)obj).getTimeZone(); | |
date = ((Calendar)obj).getTimeInMillis(); | |
} else { | |
throw new IllegalArgumentException("Cannot format given Object (" + | |
obj.getClass().getName() + ") as a time zone"); | |
} | |
assert(tz != null); | |
String result = formatOffsetLocalizedGMT(tz.getOffset(date)); | |
toAppendTo.append(result); | |
if (pos.getFieldAttribute() == DateFormat.Field.TIME_ZONE | |
|| pos.getField() == DateFormat.TIMEZONE_FIELD) { | |
pos.setBeginIndex(0); | |
pos.setEndIndex(result.length()); | |
} | |
return toAppendTo; | |
} | |
/** | |
* {@inheritDoc} | |
* | |
* @internal ICU 4.8 technology preview | |
* @deprecated This API might change or be removed in a future release. | |
*/ | |
@Override | |
public AttributedCharacterIterator formatToCharacterIterator(Object obj) { | |
StringBuffer toAppendTo = new StringBuffer(); | |
FieldPosition pos = new FieldPosition(0); | |
toAppendTo = format(obj, toAppendTo, pos); | |
// supporting only DateFormat.Field.TIME_ZONE | |
AttributedString as = new AttributedString(toAppendTo.toString()); | |
as.addAttribute(DateFormat.Field.TIME_ZONE, DateFormat.Field.TIME_ZONE); | |
return as.getIterator(); | |
} | |
/** | |
* {@inheritDoc} | |
* | |
* @internal ICU 4.8 technology preview | |
* @deprecated This API might change or be removed in a future release. | |
*/ | |
@Override | |
public Object parseObject(String source, ParsePosition pos) { | |
return parse(source, pos); | |
} | |
/** | |
* Private method returning the time zone's specific format string. | |
* | |
* @param tz the time zone | |
* @param stdType the name type used for standard time | |
* @param dstType the name type used for daylight time | |
* @param date the date | |
* @param timeType when null, actual time type is set | |
* @return the time zone's specific format name string | |
*/ | |
private String formatSpecific(TimeZone tz, NameType stdType, NameType dstType, long date, Output<TimeType> timeType) { | |
assert(stdType == NameType.LONG_STANDARD || stdType == NameType.SHORT_STANDARD || stdType == NameType.SHORT_STANDARD_COMMONLY_USED); | |
assert(dstType == NameType.LONG_DAYLIGHT || dstType == NameType.SHORT_DAYLIGHT || dstType == NameType.SHORT_DAYLIGHT_COMMONLY_USED); | |
boolean isDaylight = tz.inDaylightTime(new Date(date)); | |
String name = isDaylight? | |
getTimeZoneNames().getDisplayName(ZoneMeta.getCanonicalCLDRID(tz), dstType, date) : | |
getTimeZoneNames().getDisplayName(ZoneMeta.getCanonicalCLDRID(tz), stdType, date); | |
if (name != null && timeType != null) { | |
timeType.value = isDaylight ? TimeType.DAYLIGHT : TimeType.STANDARD; | |
} | |
return name; | |
} | |
/** | |
* Private method implementing the parse logic | |
* | |
* @param style the preferred style. | |
* @param text the input text. | |
* @param pos the parse position. | |
* @param parseAllStyles true if parse other names when a match is not found within names | |
* used by the preferred style. | |
* @param timeType receiving parsed time type (unknown/standard/daylight). If not necessary, specify null. | |
* @return the result time zone | |
*/ | |
private TimeZone parse(Style style, String text, ParsePosition pos, boolean parseAllStyles, Output<TimeType> timeType) { | |
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()); | |
boolean[] isGMTZero = {false}; | |
offset = parseOffsetLocalizedGMT(text, tmpPos, isGMTZero); | |
if (tmpPos.getErrorIndex() < 0) { | |
if (!isGMTZero[0] || 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 || style == Style.SPECIFIC_SHORT_COMMONLY_USED) { | |
// 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; | |
case SPECIFIC_SHORT_COMMONLY_USED: | |
nameTypes = EnumSet.of(NameType.SHORT_STANDARD_COMMONLY_USED, NameType.SHORT_DAYLIGHT_COMMONLY_USED); | |
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 = _gnames.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 = _gnames.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; | |
} | |
/** | |
* Private method returns a time zone ID. If tzID is not null, the value of tzID is returned. | |
* If tzID is null, then this method look up a time zone ID for the current region. This is a | |
* small helper method used by the parse implementation method | |
* | |
* @param tzID | |
* the time zone ID or null | |
* @param mzID | |
* the meta zone ID or null | |
* @return A time zone ID | |
* @throws IllegalArgumentException | |
* when both tzID and mzID are null | |
*/ | |
private String getTimeZoneID(String tzID, String mzID) { | |
String id = tzID; | |
if (id == null) { | |
assert (mzID != null); | |
id = _tznames.getReferenceZoneID(mzID, getTargetRegion()); | |
if (id == null) { | |
throw new IllegalArgumentException("Invalid mzID: " + mzID); | |
} | |
} | |
return id; | |
} | |
/** | |
* Private method returning the target region. The target regions is determined by | |
* the locale of this instance. When a generic name is coming from | |
* a meta zone, this region is used for checking if the time zone | |
* is a reference zone of the meta zone. | |
* | |
* @return the target region | |
*/ | |
private synchronized String getTargetRegion() { | |
if (_region == null) { | |
_region = _locale.getCountry(); | |
if (_region.length() == 0) { | |
ULocale tmp = ULocale.addLikelySubtags(_locale); | |
_region = tmp.getCountry(); | |
if (_region.length() == 0) { | |
_region = "001"; | |
} | |
} | |
} | |
return _region; | |
} | |
/** | |
* Returns the time type for the given name type | |
* @param nameType the name type | |
* @return the time type (unknown/standard/daylight) | |
*/ | |
private TimeType getTimeType(NameType nameType) { | |
switch (nameType) { | |
case LONG_STANDARD: | |
case SHORT_STANDARD: | |
case SHORT_STANDARD_COMMONLY_USED: | |
return TimeType.STANDARD; | |
case LONG_DAYLIGHT: | |
case SHORT_DAYLIGHT: | |
case SHORT_DAYLIGHT_COMMONLY_USED: | |
return TimeType.DAYLIGHT; | |
} | |
return TimeType.UNKNOWN; | |
} | |
/** | |
* Parses the localized GMT pattern string and initialize | |
* localized gmt pattern fields including {{@link #_gmtPatternTokens}. | |
* This method must be also called at deserialization time. | |
* | |
* @param gmtPattern the localized GMT pattern string such as "GMT {0}" | |
* @throws IllegalArgumentException when the pattern string does not contain "{0}" | |
*/ | |
private void initGMTPattern(String gmtPattern) { | |
// This implementation not perfect, but sufficient practically. | |
int idx = gmtPattern.indexOf("{0}"); | |
if (idx < 0) { | |
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)); | |
} | |
/** | |
* Unquotes the message format style pattern | |
* | |
* @param s the pattern | |
* @return the unquoted pattern string | |
*/ | |
private static String unquote(String s) { | |
if (s.indexOf('\'') < 0) { | |
return s; | |
} | |
boolean isPrevQuote = false; | |
boolean inQuote = false; | |
StringBuilder buf = new StringBuilder(); | |
for (int i = 0; i < s.length(); i++) { | |
char c = s.charAt(i); | |
if (c == '\'') { | |
if (isPrevQuote) { | |
buf.append(c); | |
isPrevQuote = false; | |
} else { | |
isPrevQuote = true; | |
} | |
inQuote = !inQuote; | |
} else { | |
isPrevQuote = false; | |
buf.append(c); | |
} | |
} | |
return buf.toString(); | |
} | |
/** | |
* Initialize localized GMT format offset hour/min/sec patterns. | |
* This method parses patterns into optimized run-time format. | |
* This method must be called at deserialization time. | |
* | |
* @param gmtOffsetPatterns patterns, String[4] | |
* @throws IllegalArgumentException when patterns are not valid | |
*/ | |
private void initGMTOffsetPatterns(String[] gmtOffsetPatterns) { | |
int size = GMTOffsetPatternType.values().length; | |
if (gmtOffsetPatterns.length < size) { | |
throw new IllegalArgumentException("Insufficient number of elements in gmtOffsetPatterns"); | |
} | |
Object[][] gmtOffsetPatternItems = new Object[size][]; | |
for (GMTOffsetPatternType t : GMTOffsetPatternType.values()) { | |
int idx = t.ordinal(); | |
// Note: parseOffsetPattern will validate the given pattern and throws | |
// IllegalArgumentException when pattern is not valid | |
Object[] parsedItems = parseOffsetPattern(gmtOffsetPatterns[idx], t.required()); | |
gmtOffsetPatternItems[idx] = parsedItems; | |
} | |
_gmtOffsetPatterns = new String[size]; | |
System.arraycopy(gmtOffsetPatterns, 0, _gmtOffsetPatterns, 0, size); | |
_gmtOffsetPatternItems = gmtOffsetPatternItems; | |
} | |
/** | |
* Used for representing localized GMT time fields in the parsed pattern object. | |
* @see TimeZoneFormat#parseOffsetPattern(String, String) | |
*/ | |
private static class GMTOffsetField { | |
final char _type; | |
final int _width; | |
GMTOffsetField(char type, int width) { | |
_type = type; | |
_width = width; | |
} | |
char getType() { | |
return _type; | |
} | |
int getWidth() { | |
return _width; | |
} | |
static boolean isValid(char type, int width) { | |
switch (type) { | |
case 'H': | |
return (width == 1 || width == 2); | |
case 'm': | |
case 's': | |
return (width == 2); | |
} | |
return false; | |
} | |
} | |
/** | |
* Parse the GMT offset pattern into runtime optimized format | |
* | |
* @param pattern the offset pattern string | |
* @param letters the required pattern letters such as "Hm" | |
* @return An array of Object. Each array entry is either String (representing | |
* pattern literal) or GMTOffsetField (hour/min/sec field) | |
*/ | |
private static Object[] parseOffsetPattern(String pattern, String letters) { | |
boolean isPrevQuote = false; | |
boolean inQuote = false; | |
StringBuilder text = new StringBuilder(); | |
char itemType = 0; // 0 for string literal, otherwise time pattern character | |
int itemLength = 1; | |
boolean invalidPattern = false; | |
List<Object> items = new ArrayList<Object>(); | |
BitSet checkBits = new BitSet(letters.length()); | |
for (int i = 0; i < pattern.length(); i++) { | |
char ch = pattern.charAt(i); | |
if (ch == '\'') { | |
if (isPrevQuote) { | |
text.append('\''); | |
isPrevQuote = false; | |
} else { | |
isPrevQuote = true; | |
if (itemType != 0) { | |
if (GMTOffsetField.isValid(itemType, itemLength)) { | |
items.add(new GMTOffsetField(itemType, itemLength)); | |
} else { | |
invalidPattern = true; | |
break; | |
} | |
itemType = 0; | |
} | |
} | |
inQuote = !inQuote; | |
} else { | |
isPrevQuote = false; | |
if (inQuote) { | |
text.append(ch); | |
} else { | |
int patFieldIdx = letters.indexOf(ch); | |
if (patFieldIdx >= 0) { | |
// an offset time pattern character | |
if (ch == itemType) { | |
itemLength++; | |
} else { | |
if (itemType == 0) { | |
if (text.length() > 0) { | |
items.add(text.toString()); | |
text.setLength(0); | |
} | |
} else { | |
if (GMTOffsetField.isValid(itemType, itemLength)) { | |
items.add(new GMTOffsetField(itemType, itemLength)); | |
} else { | |
invalidPattern = true; | |
break; | |
} | |
} | |
itemType = ch; | |
itemLength = 1; | |
} | |
checkBits.set(patFieldIdx); | |
} else { | |
// a string literal | |
if (itemType != 0) { | |
if (GMTOffsetField.isValid(itemType, itemLength)) { | |
items.add(new GMTOffsetField(itemType, itemLength)); | |
} else { | |
invalidPattern = true; | |
break; | |
} | |
itemType = 0; | |
} | |
text.append(ch); | |
} | |
} | |
} | |
} | |
// handle last item | |
if (!invalidPattern) { | |
if (itemType == 0) { | |
if (text.length() > 0) { | |
items.add(text.toString()); | |
text.setLength(0); | |
} | |
} else { | |
if (GMTOffsetField.isValid(itemType, itemLength)) { | |
items.add(new GMTOffsetField(itemType, itemLength)); | |
} else { | |
invalidPattern = true; | |
} | |
} | |
} | |
if (invalidPattern || checkBits.cardinality() != letters.length()) { | |
throw new IllegalStateException("Bad localized GMT offset pattern: " + pattern); | |
} | |
return items.toArray(new Object[items.size()]); | |
} | |
/** | |
* Appends second field to the offset pattern with hour/minute | |
* | |
* @param offsetHM the offset pattern including hour and minute fields | |
* @return the offset pattern including hour, minute and second fields | |
*/ | |
//TODO This code will be obsoleted once we add hour-minute-second pattern data in CLDR | |
private static String expandOffsetPattern(String offsetHM) { | |
int idx_mm = offsetHM.indexOf("mm"); | |
if (idx_mm < 0) { | |
// we cannot do anything with this... | |
return offsetHM + ":ss"; | |
} | |
String sep = ":"; | |
int idx_H = offsetHM.substring(0, idx_mm).lastIndexOf("H"); | |
if (idx_H >= 0) { | |
sep = offsetHM.substring(idx_H + 1, idx_mm); | |
} | |
return offsetHM.substring(0, idx_mm + 2) + sep + "ss" + offsetHM.substring(idx_mm + 2); | |
} | |
/** | |
* Appends localized digits to the buffer. | |
* <p> | |
* Note: This code assumes that the input number is 0 - 59 | |
* | |
* @param buf the target buffer | |
* @param n the integer number | |
* @param minDigits the minimum digits width | |
*/ | |
private void appendOffsetDigits(StringBuilder buf, int n, int minDigits) { | |
assert(n >= 0 && n < 60); | |
int numDigits = n >= 10 ? 2 : 1; | |
for (int i = 0; i < minDigits - numDigits; i++) { | |
buf.append(_gmtOffsetDigits[0]); | |
} | |
if (numDigits == 2) { | |
buf.append(_gmtOffsetDigits[n / 10]); | |
} | |
buf.append(_gmtOffsetDigits[n % 10]); | |
} | |
/** | |
* Creates an instance of TimeZone for the given offset | |
* @param offset the offset | |
* @return A TimeZone with the given offset | |
*/ | |
private TimeZone getTimeZoneForOffset(int offset) { | |
if (offset == 0) { | |
// when offset is 0, we should use "Etc/GMT" | |
return TimeZone.getTimeZone(TZID_GMT); | |
} | |
return ZoneMeta.getCustomTimeZone(offset); | |
} | |
/** | |
* Returns offset from GMT(UTC) in milliseconds for the given localized GMT | |
* offset format string. When the given string cannot be parsed, this method | |
* sets the current position as the error index to <code>ParsePosition pos</code> | |
* and returns 0. | |
* | |
* @param text the text contains a localized GMT offset string at the position. | |
* @param pos the position. | |
* @param isGMTZero receiving if the GMT zero format was detected. Note that | |
* the string with offset digits is not a GMT zero format. For example, when "GMT+00:00" | |
* is found, this method won't set true to isGMTZero[0]. | |
* @return the offset from GMT(UTC) in milliseconds for the given localized GMT | |
* offset format string. | |
*/ | |
private int parseOffsetLocalizedGMT(String text, ParsePosition pos, boolean[] isGMTZero) { | |
int start = pos.getIndex(); | |
int idx = start; | |
boolean parsed = false; | |
int offset = 0; | |
if (isGMTZero != null && isGMTZero.length > 0) { | |
isGMTZero[0] = false; | |
} | |
do { | |
// Prefix part | |
int len = _gmtPatternTokens[0].length(); | |
if (len > 0 && !text.regionMatches(true, idx, _gmtPatternTokens[0], 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) { | |
// offset field match failed | |
break; | |
} | |
offset = tmpOffset[0]; | |
idx += offsetLen; | |
// Suffix part | |
len = _gmtPatternTokens[1].length(); | |
if (len > 0 && !text.regionMatches(true, idx, _gmtPatternTokens[1], 0, len)) { | |
// no suffix match | |
break; | |
} | |
idx += len; | |
parsed = true; | |
} while (false); | |
if (parsed) { | |
pos.setIndex(idx); | |
return offset; | |
} | |
// Try the default patterns | |
int[] parsedLength = {0}; | |
offset = parseDefaultGMT(text, start, parsedLength); | |
if (parsedLength[0] > 0) { | |
pos.setIndex(start + parsedLength[0]); | |
return offset; | |
} | |
// Check if this is a GMT zero format | |
if (text.regionMatches(true, start, _gmtZeroFormat, 0, _gmtZeroFormat.length())) { | |
pos.setIndex(start + _gmtZeroFormat.length()); | |
if (isGMTZero != null && isGMTZero.length > 0) { | |
isGMTZero[0] = true; | |
} | |
return 0; | |
} | |
// Check if this is a default GMT zero format | |
for (String defGMTZero : ALT_GMT_STRINGS) { | |
if (text.regionMatches(true, start, defGMTZero, 0, defGMTZero.length())) { | |
pos.setIndex(start + defGMTZero.length()); | |
if (isGMTZero != null && isGMTZero.length > 0) { | |
isGMTZero[0] = true; | |
} | |
return 0; | |
} | |
} | |
// Nothing matched | |
pos.setErrorIndex(start); | |
return 0; | |
} | |
/** | |
* Parses localized GMT string 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 | |
*/ | |
private int parseGMTOffset(String text, int start, boolean minimumHourWidth, int[] offset) { | |
int parsedLen = 0; | |
int[] tmpParsedLen = new int[1]; | |
offset[0] = 0; | |
boolean sawVarHourAndAbuttingField = false; | |
for (GMTOffsetPatternType gmtPatType : PARSE_GMT_OFFSET_TYPES) { | |
int offsetH = 0, offsetM = 0, offsetS = 0; | |
int idx = start; | |
Object[] items = _gmtOffsetPatternItems[gmtPatType.ordinal()]; | |
boolean failed = false; | |
for (int i = 0; i < items.length; i++) { | |
if (items[i] instanceof String) { | |
String patStr = (String)items[i]; | |
int len = patStr.length(); | |
if (!text.regionMatches(true, idx, patStr, 0, len)) { | |
failed = true; | |
break; | |
} | |
idx += len; | |
} else { | |
assert(items[i] instanceof GMTOffsetField); | |
GMTOffsetField field = (GMTOffsetField)items[i]; | |
char fieldType = field.getType(); | |
if (fieldType == 'H') { | |
int minDigits = 1; | |
int maxDigits = minimumHourWidth ? 1 : 2; | |
if (!minimumHourWidth && !sawVarHourAndAbuttingField) { | |
if (i + 1 < items.length && (items[i] instanceof GMTOffsetField)) { | |
sawVarHourAndAbuttingField = true; | |
} | |
} | |
offsetH = parseOffsetDigits(text, idx, minDigits, maxDigits, 0, MAX_OFFSET_HOUR, tmpParsedLen); | |
} else if (fieldType == 'm') { | |
offsetM = parseOffsetDigits(text, idx, 2, 2, 0, MAX_OFFSET_MINUTE, tmpParsedLen); | |
} else if (fieldType == 's') { | |
offsetS = parseOffsetDigits(text, idx, 2, 2, 0, MAX_OFFSET_SECOND, tmpParsedLen); | |
} | |
if (tmpParsedLen[0] == 0) { | |
failed = true; | |
break; | |
} | |
idx += tmpParsedLen[0]; | |
} | |
} | |
if (!failed) { | |
int sign = gmtPatType.isPositive() ? 1 : -1; | |
offset[0] = ((((offsetH * 60) + offsetM) * 60) + offsetS) * 1000 * sign; | |
parsedLen = idx - start; | |
break; | |
} | |
} | |
if (parsedLen == 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", | |
// the hour is parsed as -10 and fails to parse minute field. | |
// | |
// If this is the case, try parsing the text one more time with the arg | |
// minimumHourWidth = true | |
// | |
// Note: This fallback is not applicable when quitAtHourField is true, because | |
// 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 parsedLen; | |
} | |
private int parseDefaultGMT(String text, int start, int[] parsedLength) { | |
int idx = start;; | |
int offset = 0; | |
int parsed = 0; | |
do { | |
// check global default GMT alternatives | |
int gmtLen = 0; | |
for (String gmt : ALT_GMT_STRINGS) { | |
int len = gmt.length(); | |
if (text.regionMatches(true, idx, gmt, 0, len)) { | |
gmtLen = len; | |
break; | |
} | |
} | |
if (gmtLen == 0) { | |
break; | |
} | |
idx += gmtLen; | |
// offset needs a sign char and a digit at minimum | |
if (idx + 1 >= text.length()) { | |
break; | |
} | |
// parse sign | |
int sign = 1; | |
char c = text.charAt(idx); | |
if (c == '+') { | |
sign = 1; | |
} else if (c == '-') { | |
sign = -1; | |
} else { | |
break; | |
} | |
idx++; | |
// offset part | |
// try the default pattern with the separator first | |
int[] lenWithSep = {0}; | |
int offsetWithSep = parseDefaultOffsetFields(text, idx, DEFAULT_GMT_OFFSET_SEP, lenWithSep); | |
if (lenWithSep[0] == text.length() - idx) { | |
// maximum match | |
offset = offsetWithSep * sign; | |
idx += lenWithSep[0]; | |
} else { | |
// try abutting field pattern | |
int[] lenAbut = {0}; | |
int offsetAbut = parseAbuttingOffsetFields(text, idx, lenAbut); | |
if (lenWithSep[0] > lenAbut[0]) { | |
offset = offsetWithSep * sign; | |
idx += lenWithSep[0]; | |
} else { | |
offset = offsetAbut * sign; | |
idx += lenAbut[0]; | |
} | |
} | |
parsed = idx - start; | |
} while (false); | |
parsedLength[0] = parsed; | |
return offset; | |
} | |
private int parseDefaultOffsetFields(String text, int start, char separator, int[] parsedLength) { | |
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); | |
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); | |
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); | |
if (len[0] == 0) { | |
break; | |
} | |
idx += (1 + len[0]); | |
} | |
} | |
} while (false); | |
if (idx == start) { | |
parsedLength[0] = 0; | |
return 0; | |
} | |
parsedLength[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) { | |
final int MAXDIGITS = 6; | |
int[] digits = new int[MAXDIGITS]; | |
int[] parsed = new int[MAXDIGITS]; // accumulative offsets | |
// Parse digits into int[] | |
int idx = start; | |
int[] len = {0}; | |
int numDigits = 0; | |
for (int i = 0; i < MAXDIGITS; i++) { | |
digits[i] = parseSingleDigit(text, idx, len); | |
if (digits[i] < 0) { | |
break; | |
} | |
idx += len[0]; | |
parsed[i] = idx - start; | |
numDigits++; | |
} | |
if (numDigits == 0) { | |
parsedLength[0] = 0; | |
return 0; | |
} | |
int offset = 0; | |
while (numDigits > 0) { | |
int hour = 0; | |
int min = 0; | |
int sec = 0; | |
assert(numDigits > 0 && numDigits <= 6); | |
switch (numDigits) { | |
case 1: // H | |
hour = digits[0]; | |
break; | |
case 2: // HH | |
hour = digits[0] * 10 + digits[1]; | |
break; | |
case 3: // Hmm | |
hour = digits[0]; | |
min = digits[1] * 10 + digits[2]; | |
break; | |
case 4: // HHmm | |
hour = digits[0] * 10 + digits[1]; | |
min = digits[2] * 10 + digits[3]; | |
break; | |
case 5: // Hmmss | |
hour = digits[0]; | |
min = digits[1] * 10 + digits[2]; | |
sec = digits[3] * 10 + digits[4]; | |
break; | |
case 6: // HHmmss | |
hour = digits[0] * 10 + digits[1]; | |
min = digits[2] * 10 + digits[3]; | |
sec = digits[4] * 10 + digits[5]; | |
break; | |
} | |
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]; | |
break; | |
} | |
numDigits--; | |
} | |
return offset; | |
} | |
/** | |
* Read an offset field number. This method will stop parsing when | |
* 1) number of digits reaches <code>maxDigits</code> | |
* 2) just before already parsed number exceeds <code>maxVal</code> | |
* | |
* @param text the text | |
* @param start the start offset | |
* @param minDigits the minimum number of required digits | |
* @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. | |
* @return the integer value parsed | |
*/ | |
private int parseOffsetDigits(String text, int start, int minDigits, int maxDigits, | |
int minVal, int maxVal, int[] parsedLength) { | |
parsedLength[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); | |
if (digit < 0) { | |
break; | |
} | |
int tmpVal = decVal * 10 + digit; | |
if (tmpVal > maxVal) { | |
break; | |
} | |
decVal = tmpVal; | |
numDigits++; | |
idx += digitLen[0]; | |
} | |
// Note: maxVal is checked in the while loop | |
if (numDigits < minDigits || decVal < minVal) { | |
decVal = -1; | |
numDigits = 0; | |
} else { | |
parsedLength[0] = idx - start; | |
} | |
return decVal; | |
} | |
private int parseSingleDigit(String text, int offset, int[] len) { | |
int digit = -1; | |
len[0] = 0; | |
if (offset < text.length()) { | |
int cp = Character.codePointAt(text, offset); | |
// First, try digits configured for this instance | |
for (int i = 0; i < _gmtOffsetDigits.length; i++) { | |
if (cp == _gmtOffsetDigits[i].codePointAt(0)) { | |
digit = i; | |
break; | |
} | |
} | |
// If failed, check if this is a Unicode digit | |
if (digit < 0) { | |
digit = UCharacter.digit(cp); | |
} | |
if (digit >= 0) { | |
len[0] = Character.charCount(cp); | |
} | |
} | |
return digit; | |
} | |
/** | |
* Break input String into String[]. Each array element represents | |
* a code point. This method is used for parsing localized digit | |
* characters and support characters in Unicode supplemental planes. | |
* | |
* @param str the string | |
* @return the array of code points in String[] | |
*/ | |
private static String[] toCodePoints(String str) { | |
int len = str.codePointCount(0, str.length()); | |
String[] codePoints = new String[len]; | |
for (int i = 0, offset = 0; i < len; i++) { | |
int code = str.codePointAt(offset); | |
int codeLen = Character.charCount(code); | |
codePoints[i] = str.substring(offset, offset + codeLen); | |
offset += codeLen; | |
} | |
return codePoints; | |
} | |
/** | |
* Custom readObject for initializing transient fields. | |
* | |
* @param ois the object input stream | |
* @throws ClassNotFoundException | |
* @throws IOException | |
*/ | |
private void readObject(ObjectInputStream ois) throws ClassNotFoundException, IOException { | |
ois.defaultReadObject(); | |
initGMTPattern(_gmtPattern); | |
initGMTOffsetPatterns(_gmtOffsetPatterns); | |
} | |
/** | |
* Implements <code>TimeZoneFormat</code> object cache | |
*/ | |
private static class TimeZoneFormatCache extends SoftCache<ULocale, TimeZoneFormat, ULocale> { | |
/* (non-Javadoc) | |
* @see com.ibm.icu.impl.CacheBase#createInstance(java.lang.Object, java.lang.Object) | |
*/ | |
@Override | |
protected TimeZoneFormat createInstance(ULocale key, ULocale data) { | |
TimeZoneFormat fmt = new TimeZoneFormat(data); | |
fmt.freeze(); | |
return fmt; | |
} | |
} | |
/** | |
* {@inheritDoc} | |
* @internal ICU 4.8 technology preview | |
* @deprecated This API might change or be removed in a future release. | |
*/ | |
public boolean isFrozen() { | |
return _frozen; | |
} | |
/** | |
* {@inheritDoc} | |
* @internal ICU 4.8 technology preview | |
* @deprecated This API might change or be removed in a future release. | |
*/ | |
public TimeZoneFormat freeze() { | |
_frozen = true; | |
return this; | |
} | |
/** | |
* {@inheritDoc} | |
* @internal ICU 4.8 technology preview | |
* @deprecated This API might change or be removed in a future release. | |
*/ | |
public TimeZoneFormat cloneAsThawed() { | |
TimeZoneFormat copy = (TimeZoneFormat)super.clone(); | |
copy._frozen = false; | |
return copy; | |
} | |
} | |