From a53477ee99f30e49b5d4a719d1e1d3629d0271a5 Mon Sep 17 00:00:00 2001 From: Yoshito Umaoka Date: Wed, 23 Jul 2014 21:53:03 +0000 Subject: [PATCH] ICU-10934 The tz database abbreviaion support in ICU4J X-SVN-Rev: 36081 --- .gitattributes | 1 + .../com/ibm/icu/impl/TZDBTimeZoneNames.java | 396 ++++++++++++++++++ .../com/ibm/icu/impl/TimeZoneNamesImpl.java | 34 +- .../src/com/ibm/icu/text/TimeZoneFormat.java | 89 +++- .../src/com/ibm/icu/text/TimeZoneNames.java | 23 +- icu4j/main/shared/data/icudata.jar | 4 +- icu4j/main/shared/data/icutzdata.jar | 2 +- .../dev/test/format/TimeZoneFormatTest.java | 188 ++++++++- .../dev/test/serializable/FormatTests.java | 48 ++- .../test/serializable/SerializableTest.java | 1 + 10 files changed, 747 insertions(+), 39 deletions(-) create mode 100644 icu4j/main/classes/core/src/com/ibm/icu/impl/TZDBTimeZoneNames.java diff --git a/.gitattributes b/.gitattributes index a0ecae30e43..f735850c785 100644 --- a/.gitattributes +++ b/.gitattributes @@ -263,6 +263,7 @@ icu4j/main/classes/core/.settings/edu.umd.cs.findbugs.core.prefs -text icu4j/main/classes/core/.settings/org.eclipse.core.resources.prefs -text icu4j/main/classes/core/.settings/org.eclipse.jdt.core.prefs -text icu4j/main/classes/core/manifest.stub -text +icu4j/main/classes/core/src/com/ibm/icu/impl/TZDBTimeZoneNames.java -text icu4j/main/classes/currdata/.externalToolBuilders/copy-data-currdata.launch -text icu4j/main/classes/currdata/.settings/org.eclipse.core.resources.prefs -text icu4j/main/classes/currdata/.settings/org.eclipse.jdt.core.prefs -text diff --git a/icu4j/main/classes/core/src/com/ibm/icu/impl/TZDBTimeZoneNames.java b/icu4j/main/classes/core/src/com/ibm/icu/impl/TZDBTimeZoneNames.java new file mode 100644 index 00000000000..22a8dd47f63 --- /dev/null +++ b/icu4j/main/classes/core/src/com/ibm/icu/impl/TZDBTimeZoneNames.java @@ -0,0 +1,396 @@ +/* + ******************************************************************************* + * Copyright (C) 2014, International Business Machines Corporation and + * others. All Rights Reserved. + ******************************************************************************* + */ +package com.ibm.icu.impl; + +import java.util.Collection; +import java.util.Collections; +import java.util.EnumSet; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.MissingResourceException; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; + +import com.ibm.icu.impl.TextTrieMap.ResultHandler; +import com.ibm.icu.text.TimeZoneNames; +import com.ibm.icu.util.ULocale; +import com.ibm.icu.util.UResourceBundle; + +/** + * Yet another TimeZoneNames implementation based on the tz database. + * This implementation contains only tz abbreviations (short standard + * and daylight names) for each metazone. + * + * The data file $ICU4C_ROOT/source/data/zone/tzdbNames.txt contains + * the metazone - abbreviations mapping data (manually edited). + * + * Note: The abbreviations in the tz database are not necessarily + * unique. For example, parsing abbreviation "IST" is ambiguous + * (can be parsed as India Standard Time or Israel Standard Time). + * The data file (tzdbNames.txt) contains regional mapping, and + * the locale in the constructor is used as a hint for resolving + * these ambiguous names. + */ +public class TZDBTimeZoneNames extends TimeZoneNames { + private static final long serialVersionUID = 1L; + + private static final ConcurrentHashMap TZDB_NAMES_MAP = + new ConcurrentHashMap(); + + private static volatile TextTrieMap TZDB_NAMES_TRIE = null; + + private static final ICUResourceBundle ZONESTRINGS; + static { + UResourceBundle bundle = ICUResourceBundle + .getBundleInstance(ICUResourceBundle.ICU_ZONE_BASE_NAME, "tzdbNames"); + ZONESTRINGS = (ICUResourceBundle)bundle.get("zoneStrings"); + } + + private ULocale _locale; + private transient volatile String _region; + + public TZDBTimeZoneNames(ULocale loc) { + _locale = loc; + } + + /* (non-Javadoc) + * @see com.ibm.icu.text.TimeZoneNames#getAvailableMetaZoneIDs() + */ + @Override + public Set getAvailableMetaZoneIDs() { + return TimeZoneNamesImpl._getAvailableMetaZoneIDs(); + } + + /* (non-Javadoc) + * @see com.ibm.icu.text.TimeZoneNames#getAvailableMetaZoneIDs(java.lang.String) + */ + @Override + public Set getAvailableMetaZoneIDs(String tzID) { + return TimeZoneNamesImpl._getAvailableMetaZoneIDs(tzID); + } + + /* (non-Javadoc) + * @see com.ibm.icu.text.TimeZoneNames#getMetaZoneID(java.lang.String, long) + */ + @Override + public String getMetaZoneID(String tzID, long date) { + return TimeZoneNamesImpl._getMetaZoneID(tzID, date); + } + + /* (non-Javadoc) + * @see com.ibm.icu.text.TimeZoneNames#getReferenceZoneID(java.lang.String, java.lang.String) + */ + @Override + public String getReferenceZoneID(String mzID, String region) { + return TimeZoneNamesImpl._getReferenceZoneID(mzID, region); + } + + /* (non-Javadoc) + * @see com.ibm.icu.text.TimeZoneNames#getMetaZoneDisplayName(java.lang.String, + * com.ibm.icu.text.TimeZoneNames.NameType) + */ + @Override + public String getMetaZoneDisplayName(String mzID, NameType type) { + if (mzID == null || mzID.length() == 0 || + (type != NameType.SHORT_STANDARD && type != NameType.SHORT_DAYLIGHT)) { + return null; + } + return getMetaZoneNames(mzID).getName(type); + } + + /* (non-Javadoc) + * @see com.ibm.icu.text.TimeZoneNames#getTimeZoneDisplayName(java.lang.String, + * com.ibm.icu.text.TimeZoneNames.NameType) + */ + @Override + public String getTimeZoneDisplayName(String tzID, NameType type) { + // No abbreviations associated a zone directly for now. + return null; + } + +// /* (non-Javadoc) +// * @see com.ibm.icu.text.TimeZoneNames#getExemplarLocationName(java.lang.String) +// */ +// public String getExemplarLocationName(String tzID) { +// return super.getExemplarLocationName(tzID); +// } + + /* (non-Javadoc) + * @see com.ibm.icu.text.TimeZoneNames#find(java.lang.CharSequence, int, java.util.EnumSet) + */ + @Override + public Collection find(CharSequence text, int start, EnumSet nameTypes) { + if (text == null || text.length() == 0 || start < 0 || start >= text.length()) { + throw new IllegalArgumentException("bad input text or range"); + } + + prepareFind(); + TZDBNameSearchHandler handler = new TZDBNameSearchHandler(nameTypes, getTargetRegion()); + TZDB_NAMES_TRIE.find(text, start, handler); + return handler.getMatches(); + } + + private static class TZDBNames { + public static final TZDBNames EMPTY_TZDBNAMES = new TZDBNames(null, null); + + private String[] _names; + private String[] _parseRegions; + private static final String[] KEYS = {"ss", "sd"}; + + private TZDBNames(String[] names, String[] parseRegions) { + _names = names; + _parseRegions = parseRegions; + } + + static TZDBNames getInstance(ICUResourceBundle zoneStrings, String key) { + if (zoneStrings == null || key == null || key.length() == 0) { + return EMPTY_TZDBNAMES; + } + + ICUResourceBundle table = null; + try { + table = (ICUResourceBundle)zoneStrings.get(key); + } catch (MissingResourceException e) { + return EMPTY_TZDBNAMES; + } + + boolean isEmpty = true; + String[] names = new String[KEYS.length]; + for (int i = 0; i < names.length; i++) { + try { + names[i] = table.getString(KEYS[i]); + isEmpty = false; + } catch (MissingResourceException e) { + names[i] = null; + } + } + + if (isEmpty) { + return EMPTY_TZDBNAMES; + } + + String[] parseRegions = null; + try { + ICUResourceBundle regionsRes = (ICUResourceBundle)table.get("parseRegions"); + if (regionsRes.getType() == UResourceBundle.STRING) { + parseRegions = new String[1]; + parseRegions[0] = regionsRes.getString(); + } else if (regionsRes.getType() == UResourceBundle.ARRAY) { + parseRegions = regionsRes.getStringArray(); + } + } catch (MissingResourceException e) { + // fall through + } + + return new TZDBNames(names, parseRegions); + } + + String getName(NameType type) { + if (_names == null) { + return null; + } + String name = null; + switch (type) { + case SHORT_STANDARD: + name = _names[0]; + break; + case SHORT_DAYLIGHT: + name = _names[1]; + break; + } + + return name; + } + + String[] getParseRegions() { + return _parseRegions; + } + } + + private static class TZDBNameInfo { + String mzID; + NameType type; + boolean ambiguousType; + String[] parseRegions; + } + + private static class TZDBNameSearchHandler implements ResultHandler { + private EnumSet _nameTypes; + private Collection _matches; + private String _region; + + TZDBNameSearchHandler(EnumSet nameTypes, String region) { + _nameTypes = nameTypes; + assert region != null; + _region = region; + } + + /* (non-Javadoc) + * @see com.ibm.icu.impl.TextTrieMap.ResultHandler#handlePrefixMatch(int, + * java.util.Iterator) + */ + public boolean handlePrefixMatch(int matchLength, Iterator values) { + TZDBNameInfo match = null; + TZDBNameInfo defaultRegionMatch = null; + + while (values.hasNext()) { + TZDBNameInfo ninfo = values.next(); + + if (_nameTypes != null && !_nameTypes.contains(ninfo.type)) { + continue; + } + + // Some tz database abbreviations are ambiguous. For example, + // CST means either Central Standard Time or China Standard Time. + // Unlike CLDR time zone display names, this implementation + // does not use unique names. And TimeZoneFormat does not expect + // multiple results returned for the same time zone type. + // For this reason, this implementation resolve one among same + // zone type with a same name at this level. + if (ninfo.parseRegions == null) { + // parseRegions == null means this is the default metazone + // mapping for the abbreviation. + if (defaultRegionMatch == null) { + match = defaultRegionMatch = ninfo; + } + } else { + boolean matchRegion = false; + // non-default metazone mapping for an abbreviation + // comes with applicable regions. For example, the default + // metazone mapping for "CST" is America_Central, + // but if region is one of CN/MO/TW, "CST" is parsed + // as metazone China (China Standard Time). + for (String region : ninfo.parseRegions) { + if (_region.equals(region)) { + match = ninfo; + matchRegion = true; + break; + } + } + if (matchRegion) { + break; + } + if (match == null) { + match = ninfo; + } + } + } + + if (match != null) { + NameType ntype = match.type; + // Note: Workaround for duplicated standard/daylight names + // The tz database contains a few zones sharing a + // same name for both standard time and daylight saving + // time. For example, Australia/Sydney observes DST, + // but "EST" is used for both standard and daylight. + // When both SHORT_STANDARD and SHORT_DAYLIGHT are included + // in the find operation, we cannot tell which one was + // actually matched. + // TimeZoneFormat#parse returns a matched name type (standard + // or daylight) and DateFormat implementation uses the info to + // to adjust actual time. To avoid false type information, + // this implementation replaces the name type with SHORT_GENERIC. + if (match.ambiguousType + && (ntype == NameType.SHORT_STANDARD || ntype == NameType.SHORT_DAYLIGHT) + && _nameTypes.contains(NameType.SHORT_STANDARD) + && _nameTypes.contains(NameType.SHORT_DAYLIGHT)) { + ntype = NameType.SHORT_GENERIC; + } + MatchInfo minfo = new MatchInfo(ntype, null, match.mzID, matchLength); + if (_matches == null) { + _matches = new LinkedList(); + } + _matches.add(minfo); + } + + return true; + } + + /** + * Returns the match results + * @return the match results + */ + public Collection getMatches() { + if (_matches == null) { + return Collections.emptyList(); + } + return _matches; + } + } + + private static TZDBNames getMetaZoneNames(String mzID) { + TZDBNames names = TZDB_NAMES_MAP.get(mzID); + if (names == null) { + names = TZDBNames.getInstance(ZONESTRINGS, "meta:" + mzID); + mzID = mzID.intern(); + TZDBNames tmpNames = TZDB_NAMES_MAP.putIfAbsent(mzID, names); + names = (tmpNames == null) ? names : tmpNames; + } + return names; + } + + private static void prepareFind() { + if (TZDB_NAMES_TRIE == null) { + synchronized(TZDBTimeZoneNames.class) { + if (TZDB_NAMES_TRIE == null) { + // loading all names into trie + TZDB_NAMES_TRIE = new TextTrieMap(true); + Set mzIDs = TimeZoneNamesImpl._getAvailableMetaZoneIDs(); + for (String mzID : mzIDs) { + TZDBNames names = getMetaZoneNames(mzID); + String std = names.getName(NameType.SHORT_STANDARD); + String dst = names.getName(NameType.SHORT_DAYLIGHT); + if (std == null && dst == null) { + continue; + } + String[] parseRegions = names.getParseRegions(); + mzID = mzID.intern(); + + // The tz database contains a few zones sharing a + // same name for both standard time and daylight saving + // time. For example, Australia/Sydney observes DST, + // but "EST" is used for both standard and daylight. + // we need to store the information for later processing. + boolean ambiguousType = (std != null && dst != null && std.equals(dst)); + + if (std != null) { + TZDBNameInfo stdInf = new TZDBNameInfo(); + stdInf.mzID = mzID; + stdInf.type = NameType.SHORT_STANDARD; + stdInf.ambiguousType = ambiguousType; + stdInf.parseRegions = parseRegions; + TZDB_NAMES_TRIE.put(std, stdInf); + } + if (dst != null) { + TZDBNameInfo dstInf = new TZDBNameInfo(); + dstInf.mzID = mzID; + dstInf.type = NameType.SHORT_DAYLIGHT; + dstInf.ambiguousType = ambiguousType; + dstInf.parseRegions = parseRegions; + TZDB_NAMES_TRIE.put(dst, dstInf); + } + } + } + } + } + } + + private String getTargetRegion() { + if (_region == null) { + String region = _locale.getCountry(); + if (region.length() == 0) { + ULocale tmp = ULocale.addLikelySubtags(_locale); + region = tmp.getCountry(); + if (region.length() == 0) { + region = "001"; + } + } + _region = region; + } + return _region; + } +} diff --git a/icu4j/main/classes/core/src/com/ibm/icu/impl/TimeZoneNamesImpl.java b/icu4j/main/classes/core/src/com/ibm/icu/impl/TimeZoneNamesImpl.java index 120ebc41e7a..9381c0f6bc3 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/impl/TimeZoneNamesImpl.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/impl/TimeZoneNamesImpl.java @@ -1,6 +1,6 @@ /* ******************************************************************************* - * Copyright (C) 2011-2013, International Business Machines Corporation and * + * Copyright (C) 2011-2014, International Business Machines Corporation and * * others. All Rights Reserved. * ******************************************************************************* */ @@ -41,7 +41,7 @@ public class TimeZoneNamesImpl extends TimeZoneNames { private static final String ZONE_STRINGS_BUNDLE = "zoneStrings"; private static final String MZ_PREFIX = "meta:"; - private static Set METAZONE_IDS; + private static volatile Set METAZONE_IDS; private static final TZ2MZsCache TZ_TO_MZS_CACHE = new TZ2MZsCache(); private static final MZ2TZsCache MZ_TO_TZS_CACHE = new MZ2TZsCache(); @@ -65,12 +65,20 @@ public class TimeZoneNamesImpl extends TimeZoneNames { * @see com.ibm.icu.text.TimeZoneNames#getAvailableMetaZoneIDs() */ @Override - public synchronized Set getAvailableMetaZoneIDs() { + public Set getAvailableMetaZoneIDs() { + return _getAvailableMetaZoneIDs(); + } + + static Set _getAvailableMetaZoneIDs() { if (METAZONE_IDS == null) { - UResourceBundle bundle = UResourceBundle.getBundleInstance(ICUResourceBundle.ICU_BASE_NAME, "metaZones"); - UResourceBundle mapTimezones = bundle.get("mapTimezones"); - Set keys = mapTimezones.keySet(); - METAZONE_IDS = Collections.unmodifiableSet(keys); + synchronized (TimeZoneNamesImpl.class) { + if (METAZONE_IDS == null) { + UResourceBundle bundle = UResourceBundle.getBundleInstance(ICUResourceBundle.ICU_BASE_NAME, "metaZones"); + UResourceBundle mapTimezones = bundle.get("mapTimezones"); + Set keys = mapTimezones.keySet(); + METAZONE_IDS = Collections.unmodifiableSet(keys); + } + } } return METAZONE_IDS; } @@ -80,6 +88,10 @@ public class TimeZoneNamesImpl extends TimeZoneNames { */ @Override public Set getAvailableMetaZoneIDs(String tzID) { + return _getAvailableMetaZoneIDs(tzID); + } + + static Set _getAvailableMetaZoneIDs(String tzID) { if (tzID == null || tzID.length() == 0) { return Collections.emptySet(); } @@ -100,6 +112,10 @@ public class TimeZoneNamesImpl extends TimeZoneNames { */ @Override public String getMetaZoneID(String tzID, long date) { + return _getMetaZoneID(tzID, date); + } + + static String _getMetaZoneID(String tzID, long date) { if (tzID == null || tzID.length() == 0) { return null; } @@ -119,6 +135,10 @@ public class TimeZoneNamesImpl extends TimeZoneNames { */ @Override public String getReferenceZoneID(String mzID, String region) { + return _getReferenceZoneID(mzID, region); + } + + static String _getReferenceZoneID(String mzID, String region) { if (mzID == null || mzID.length() == 0) { return null; } diff --git a/icu4j/main/classes/core/src/com/ibm/icu/text/TimeZoneFormat.java b/icu4j/main/classes/core/src/com/ibm/icu/text/TimeZoneFormat.java index 2219f756a8c..874c6b7edb3 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/text/TimeZoneFormat.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/text/TimeZoneFormat.java @@ -30,6 +30,7 @@ import java.util.Set; import com.ibm.icu.impl.ICUResourceBundle; import com.ibm.icu.impl.SoftCache; +import com.ibm.icu.impl.TZDBTimeZoneNames; import com.ibm.icu.impl.TextTrieMap; import com.ibm.icu.impl.TimeZoneGenericNames; import com.ibm.icu.impl.TimeZoneGenericNames.GenericMatchInfo; @@ -309,8 +310,17 @@ public class TimeZoneFormat extends UFormat implements Freezable * by other styles. * @stable ICU 49 */ - ALL_STYLES; - } + ALL_STYLES, + /** + * When parsing a time zone display name in {@link Style#SPECIFIC_SHORT}, + * look for the IANA tz database compatible zone abbreviations in addition + * to the localized names coming from the {@link TimeZoneNames} currently + * used by the {@link TimeZoneFormat}. + * @draft ICU 54 + * @provisional This API might change or be removed in a future release. + */ + TZ_DATABASE_ABBREVIATIONS; + } /* * fields to be serialized @@ -322,6 +332,7 @@ public class TimeZoneFormat extends UFormat implements Freezable private String[] _gmtOffsetDigits; private String _gmtZeroFormat; private boolean _parseAllStyles; + private boolean _parseTZDBNames; /* * Transient fields @@ -338,6 +349,7 @@ public class TimeZoneFormat extends UFormat implements Freezable private transient boolean _frozen; + private transient volatile TimeZoneNames _tzdbNames; /* * Static final fields @@ -524,6 +536,23 @@ public class TimeZoneFormat extends UFormat implements Freezable return _gnames; } + /** + * Private method returning the instance of TZDBTimeZoneNames. + * The instance if used only for parsing when {@link ParseOption#TZ_DATABASE_ABBREVIATIONS} + * is enabled. + * @return an instance of TZDBTimeZoneNames. + */ + private TimeZoneNames getTZDBTimeZoneNames() { + if (_tzdbNames == null) { + synchronized(this) { + if (_tzdbNames == null) { + _tzdbNames = new TZDBTimeZoneNames(_locale); + } + } + } + return _tzdbNames; + } + /** * Sets the time zone display name data to this instance. * @@ -699,8 +728,8 @@ public class TimeZoneFormat extends UFormat implements Freezable * @stable ICU 49 */ public TimeZoneFormat setDefaultParseOptions(EnumSet options) { - // Currently, only ALL_STYLES is supported _parseAllStyles = options.contains(ParseOption.ALL_STYLES); + _parseTZDBNames = options.contains(ParseOption.TZ_DATABASE_ABBREVIATIONS); return this; } @@ -711,8 +740,12 @@ public class TimeZoneFormat extends UFormat implements Freezable * @stable ICU 49 */ public EnumSet getDefaultParseOptions() { - if (_parseAllStyles) { + if (_parseAllStyles && _parseTZDBNames) { + return EnumSet.of(ParseOption.ALL_STYLES, ParseOption.TZ_DATABASE_ABBREVIATIONS); + } else if (_parseAllStyles) { return EnumSet.of(ParseOption.ALL_STYLES); + } else if (_parseTZDBNames) { + return EnumSet.of(ParseOption.TZ_DATABASE_ABBREVIATIONS); } return EnumSet.noneOf(ParseOption.class); } @@ -1078,6 +1111,10 @@ public class TimeZoneFormat extends UFormat implements Freezable evaluated |= (Style.LOCALIZED_GMT.flag | Style.LOCALIZED_GMT_SHORT.flag); } + boolean parseTZDBAbbrev = (options == null) ? + getDefaultParseOptions().contains(ParseOption.TZ_DATABASE_ABBREVIATIONS) + : options.contains(ParseOption.TZ_DATABASE_ABBREVIATIONS); + // Try the specified style switch (style) { case LOCALIZED_GMT: @@ -1173,6 +1210,28 @@ public class TimeZoneFormat extends UFormat implements Freezable return TimeZone.getTimeZone(getTimeZoneID(specificMatch.tzID(), specificMatch.mzID())); } } + + if (parseTZDBAbbrev && style == Style.SPECIFIC_SHORT) { + assert nameTypes.contains(NameType.SHORT_STANDARD); + assert nameTypes.contains(NameType.SHORT_DAYLIGHT); + + Collection tzdbNameMatches = + getTZDBTimeZoneNames().find(text, startIdx, nameTypes); + if (tzdbNameMatches != null) { + MatchInfo tzdbNameMatch = null; + for (MatchInfo match : tzdbNameMatches) { + if (startIdx + match.matchLength() > parsedPos) { + tzdbNameMatch = match; + parsedPos = startIdx + match.matchLength(); + } + } + if (tzdbNameMatch != null) { + timeType.value = getTimeType(tzdbNameMatch.nameType()); + pos.setIndex(parsedPos); + return TimeZone.getTimeZone(getTimeZoneID(tzdbNameMatch.tzID(), tzdbNameMatch.mzID())); + } + } + } break; } case GENERIC_LONG: @@ -1367,7 +1426,27 @@ public class TimeZoneFormat extends UFormat implements Freezable parsedTimeType = getTimeType(specificMatch.nameType()); parsedOffset = UNKNOWN_OFFSET; } - + } + if (parseTZDBAbbrev && parsedPos < maxPos && (evaluated & Style.SPECIFIC_SHORT.flag) == 0) { + Collection tzdbNameMatches = + getTZDBTimeZoneNames().find(text, startIdx, ALL_SIMPLE_NAME_TYPES); + MatchInfo tzdbNameMatch = null; + int matchPos = -1; + if (tzdbNameMatches != null) { + for (MatchInfo match : tzdbNameMatches) { + if (startIdx + match.matchLength() > matchPos) { + tzdbNameMatch = match; + matchPos = startIdx + match.matchLength(); + } + } + if (parsedPos < matchPos) { + parsedPos = matchPos; + parsedID = getTimeZoneID(tzdbNameMatch.tzID(), tzdbNameMatch.mzID()); + parsedTimeType = getTimeType(tzdbNameMatch.nameType()); + parsedOffset = UNKNOWN_OFFSET; + } + } + } // Try generic names if (parsedPos < maxPos) { diff --git a/icu4j/main/classes/core/src/com/ibm/icu/text/TimeZoneNames.java b/icu4j/main/classes/core/src/com/ibm/icu/text/TimeZoneNames.java index 5aad466d7e3..4caa6a77ca0 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/text/TimeZoneNames.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/text/TimeZoneNames.java @@ -15,6 +15,7 @@ import java.util.Set; import com.ibm.icu.impl.ICUConfig; import com.ibm.icu.impl.SoftCache; +import com.ibm.icu.impl.TZDBTimeZoneNames; import com.ibm.icu.impl.TimeZoneNamesImpl; import com.ibm.icu.util.TimeZone; import com.ibm.icu.util.ULocale; @@ -160,11 +161,11 @@ public abstract class TimeZoneNames implements Serializable { } /** - * Returns an instance of TimeZoneDisplayNames for the specified locale. + * Returns an instance of TimeZoneNames for the specified locale. * * @param locale * The locale. - * @return An instance of TimeZoneDisplayNames + * @return An instance of TimeZoneNames * @stable ICU 49 */ public static TimeZoneNames getInstance(ULocale locale) { @@ -173,7 +174,7 @@ public abstract class TimeZoneNames implements Serializable { } /** - * Returns an instance of TimeZoneDisplayNames for the specified JDK locale. + * Returns an instance of TimeZoneNames for the specified JDK locale. * * @param locale * The JDK locale. @@ -185,6 +186,22 @@ public abstract class TimeZoneNames implements Serializable { return getInstance(ULocale.forLocale(locale)); } + /** + * Returns an instance of TimeZoneNames containing only short specific + * zone names ({@link NameType#SHORT_STANDARD} and {@link NameType#SHORT_DAYLIGHT}), + * compatible with the IANA tz database's zone abbreviations (not localized). + *
+ * Note: The input locale is used for resolving ambiguous names (e.g. "IST" is parsed + * as Israel Standard Time for Israel, while it is parsed as India Standard Time for + * all other regions). The zone names returned by this instance are not localized. + * + * @draft ICU 54 + * @provisional This API might change or be removed in a future release. + */ + public static TimeZoneNames getTZDBInstance(ULocale locale) { + return new TZDBTimeZoneNames(locale); + } + /** * Returns an immutable set of all available meta zone IDs. * @return An immutable set of all available meta zone IDs. diff --git a/icu4j/main/shared/data/icudata.jar b/icu4j/main/shared/data/icudata.jar index 3dfc24b5c57..510acc80f36 100755 --- a/icu4j/main/shared/data/icudata.jar +++ b/icu4j/main/shared/data/icudata.jar @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:df42e127c7c6127bce3a0a44577d33d8457fc073f5fa131f6916c039b374932c -size 10503267 +oid sha256:3d46efbf5fced8f73fd8e352902259aa0ba4c89fdf9fcbee24862044a09c56b8 +size 10505785 diff --git a/icu4j/main/shared/data/icutzdata.jar b/icu4j/main/shared/data/icutzdata.jar index ec6c1f9bd14..5d693364fed 100755 --- a/icu4j/main/shared/data/icutzdata.jar +++ b/icu4j/main/shared/data/icutzdata.jar @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:33523d17a26aa17b47fafbd6242592cd070436cd988b32d67230612a31432ca8 +oid sha256:d3137adf135a24c2bb8f5fcbbbdea86e07a79f3eb8f4dab3734e88afa7a2dc7a size 91046 diff --git a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/format/TimeZoneFormatTest.java b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/format/TimeZoneFormatTest.java index e11a6207f9c..a1114076d63 100644 --- a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/format/TimeZoneFormatTest.java +++ b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/format/TimeZoneFormatTest.java @@ -25,6 +25,7 @@ import com.ibm.icu.text.TimeZoneFormat; import com.ibm.icu.text.TimeZoneFormat.ParseOption; import com.ibm.icu.text.TimeZoneFormat.Style; import com.ibm.icu.text.TimeZoneFormat.TimeType; +import com.ibm.icu.text.TimeZoneNames; import com.ibm.icu.util.BasicTimeZone; import com.ibm.icu.util.Calendar; import com.ibm.icu.util.Output; @@ -498,25 +499,91 @@ public class TimeZoneFormatTest extends com.ibm.icu.dev.test.TestFmwk { public void TestParse() { final Object[][] DATA = { - // text inpos locale style parseAll? expected outpos time type - {"Z", 0, "en_US", Style.ISO_EXTENDED_FULL, false, "Etc/GMT", 1, TimeType.UNKNOWN}, - {"Z", 0, "en_US", Style.SPECIFIC_LONG, false, "Etc/GMT", 1, TimeType.UNKNOWN}, - {"Zambia time", 0, "en_US", Style.ISO_EXTENDED_FULL, true, "Etc/GMT", 1, TimeType.UNKNOWN}, - {"Zambia time", 0, "en_US", Style.GENERIC_LOCATION, false, "Africa/Lusaka", 11, TimeType.UNKNOWN}, - {"Zambia time", 0, "en_US", Style.ISO_BASIC_LOCAL_FULL, true, "Africa/Lusaka", 11, TimeType.UNKNOWN}, - {"+00:00", 0, "en_US", Style.ISO_EXTENDED_FULL, false, "Etc/GMT", 6, TimeType.UNKNOWN}, - {"-01:30:45", 0, "en_US", Style.ISO_EXTENDED_FULL, false, "GMT-01:30:45", 9, TimeType.UNKNOWN}, - {"-7", 0, "en_US", Style.ISO_BASIC_LOCAL_FULL, false, "GMT-07:00", 2, TimeType.UNKNOWN}, - {"-2222", 0, "en_US", Style.ISO_BASIC_LOCAL_FULL, false, "GMT-22:22", 5, TimeType.UNKNOWN}, - {"-3333", 0, "en_US", Style.ISO_BASIC_LOCAL_FULL, false, "GMT-03:33", 4, TimeType.UNKNOWN}, - {"XXX+01:30YYY", 3, "en_US", Style.LOCALIZED_GMT, false, "GMT+01:30", 9, TimeType.UNKNOWN}, - {"GMT0", 0, "en_US", Style.SPECIFIC_SHORT, false, "Etc/GMT", 3, TimeType.UNKNOWN}, - {"EST", 0, "en_US", Style.SPECIFIC_SHORT, false, "America/New_York", 3, TimeType.STANDARD}, - {"ESTx", 0, "en_US", Style.SPECIFIC_SHORT, false, "America/New_York", 3, TimeType.STANDARD}, - {"EDTx", 0, "en_US", Style.SPECIFIC_SHORT, false, "America/New_York", 3, TimeType.DAYLIGHT}, - {"EST", 0, "en_US", Style.SPECIFIC_LONG, false, null, 0, TimeType.UNKNOWN}, - {"EST", 0, "en_US", Style.SPECIFIC_LONG, true, "America/New_York", 3, TimeType.STANDARD}, - {"EST", 0, "en_CA", Style.SPECIFIC_SHORT, false, "America/Toronto", 3, TimeType.STANDARD}, + // text inpos locale style + // parseOptions expected outpos time type + {"Z", 0, "en_US", Style.ISO_EXTENDED_FULL, + null, "Etc/GMT", 1, TimeType.UNKNOWN}, + + {"Z", 0, "en_US", Style.SPECIFIC_LONG, + null, "Etc/GMT", 1, TimeType.UNKNOWN}, + + {"Zambia time", 0, "en_US", Style.ISO_EXTENDED_FULL, + EnumSet.of(ParseOption.ALL_STYLES), "Etc/GMT", 1, TimeType.UNKNOWN}, + + {"Zambia time", 0, "en_US", Style.GENERIC_LOCATION, + null, "Africa/Lusaka", 11, TimeType.UNKNOWN}, + + {"Zambia time", 0, "en_US", Style.ISO_BASIC_LOCAL_FULL, + EnumSet.of(ParseOption.ALL_STYLES), "Africa/Lusaka", 11, TimeType.UNKNOWN}, + + {"+00:00", 0, "en_US", Style.ISO_EXTENDED_FULL, + null, "Etc/GMT", 6, TimeType.UNKNOWN}, + + {"-01:30:45", 0, "en_US", Style.ISO_EXTENDED_FULL, + null, "GMT-01:30:45", 9, TimeType.UNKNOWN}, + + {"-7", 0, "en_US", Style.ISO_BASIC_LOCAL_FULL, + null, "GMT-07:00", 2, TimeType.UNKNOWN}, + + {"-2222", 0, "en_US", Style.ISO_BASIC_LOCAL_FULL, + null, "GMT-22:22", 5, TimeType.UNKNOWN}, + + {"-3333", 0, "en_US", Style.ISO_BASIC_LOCAL_FULL, + null, "GMT-03:33", 4, TimeType.UNKNOWN}, + + {"XXX+01:30YYY", 3, "en_US", Style.LOCALIZED_GMT, + null, "GMT+01:30", 9, TimeType.UNKNOWN}, + + {"GMT0", 0, "en_US", Style.SPECIFIC_SHORT, + null, "Etc/GMT", 3, TimeType.UNKNOWN}, + + {"EST", 0, "en_US", Style.SPECIFIC_SHORT, + null, "America/New_York", 3, TimeType.STANDARD}, + + {"ESTx", 0, "en_US", Style.SPECIFIC_SHORT, + null, "America/New_York", 3, TimeType.STANDARD}, + + {"EDTx", 0, "en_US", Style.SPECIFIC_SHORT, + null, "America/New_York", 3, TimeType.DAYLIGHT}, + + {"EST", 0, "en_US", Style.SPECIFIC_LONG, + null, null, 0, TimeType.UNKNOWN}, + + {"EST", 0, "en_US", Style.SPECIFIC_LONG, + EnumSet.of(ParseOption.ALL_STYLES), "America/New_York", 3, TimeType.STANDARD}, + + {"EST", 0, "en_CA", Style.SPECIFIC_SHORT, + null, "America/Toronto", 3, TimeType.STANDARD}, + + {"CST", 0, "en_US", Style.SPECIFIC_SHORT, + null, "America/Chicago", 3, TimeType.STANDARD}, + + {"CST", 0, "en_GB", Style.SPECIFIC_SHORT, + null, null, 0, TimeType.UNKNOWN}, + + {"CST", 0, "en_GB", Style.SPECIFIC_SHORT, + EnumSet.of(ParseOption.TZ_DATABASE_ABBREVIATIONS), "America/Chicago", 3, TimeType.STANDARD}, + + {"--CST--", 2, "en_GB", Style.SPECIFIC_SHORT, + EnumSet.of(ParseOption.TZ_DATABASE_ABBREVIATIONS), "America/Chicago", 5, TimeType.STANDARD}, + + {"CST", 0, "zh_CN", Style.SPECIFIC_SHORT, + EnumSet.of(ParseOption.TZ_DATABASE_ABBREVIATIONS), "Asia/Shanghai", 3, TimeType.STANDARD}, + + {"EST", 0, "en_AU", Style.SPECIFIC_SHORT, + EnumSet.of(ParseOption.TZ_DATABASE_ABBREVIATIONS), "Australia/Sydney", 3, TimeType.UNKNOWN}, + + {"AST", 0, "ar_SA", Style.SPECIFIC_SHORT, + EnumSet.of(ParseOption.TZ_DATABASE_ABBREVIATIONS), "Asia/Riyadh", 3, TimeType.STANDARD}, + + {"AQTST", 0, "en", Style.SPECIFIC_LONG, + null, null, 0, TimeType.UNKNOWN}, + + {"AQTST", 0, "en", Style.SPECIFIC_LONG, + EnumSet.of(ParseOption.ALL_STYLES), null, 0, TimeType.UNKNOWN}, + + {"AQTST", 0, "en", Style.SPECIFIC_LONG, + EnumSet.of(ParseOption.ALL_STYLES, ParseOption.TZ_DATABASE_ABBREVIATIONS), "Asia/Aqtobe", 5, TimeType.DAYLIGHT}, }; for (Object[] test : DATA) { @@ -524,7 +591,7 @@ public class TimeZoneFormatTest extends com.ibm.icu.dev.test.TestFmwk { int inPos = (Integer)test[1]; ULocale loc = new ULocale((String)test[2]); Style style = (Style)test[3]; - EnumSet options = (Boolean)test[4] ? EnumSet.of(ParseOption.ALL_STYLES) : null; + EnumSet options = (EnumSet)test[4]; String expID = (String)test[5]; int expPos = (Integer)test[6]; TimeType expType = (TimeType)test[7]; @@ -798,4 +865,85 @@ public class TimeZoneFormatTest extends com.ibm.icu.dev.test.TestFmwk { } } } + + public void TestFormatTZDBNames() { + final Date dateJan = new Date(1358208000000L); // 2013-01-15T00:00:00Z + final Date dateJul = new Date(1373846400000L); // 2013-07-15T00:00:00Z + + final Object[][] TESTDATA = { + { + "en", + "America/Chicago", + dateJan, + Style.SPECIFIC_SHORT, + "CST", + TimeType.STANDARD + }, + { + "en", + "Asia/Shanghai", + dateJan, + Style.SPECIFIC_SHORT, + "CST", + TimeType.STANDARD + }, + { + "zh_Hans", + "Asia/Shanghai", + dateJan, + Style.SPECIFIC_SHORT, + "CST", + TimeType.STANDARD + }, + { + "en", + "America/Los_Angeles", + dateJul, + Style.SPECIFIC_LONG, + "GMT-07:00", // No long display names + TimeType.DAYLIGHT + }, + { + "ja", + "America/Los_Angeles", + dateJul, + Style.SPECIFIC_SHORT, + "PDT", + TimeType.DAYLIGHT + }, + { + "en", + "Australia/Sydney", + dateJan, + Style.SPECIFIC_SHORT, + "EST", + TimeType.DAYLIGHT + }, + { + "en", + "Australia/Sydney", + dateJul, + Style.SPECIFIC_SHORT, + "EST", + TimeType.STANDARD + }, + }; + + for (Object[] testCase : TESTDATA) { + ULocale loc = new ULocale((String)testCase[0]); + TimeZoneFormat tzfmt = TimeZoneFormat.getInstance(loc).cloneAsThawed(); + TimeZoneNames tzdbNames = TimeZoneNames.getTZDBInstance(loc); + tzfmt.setTimeZoneNames(tzdbNames); + + TimeZone tz = TimeZone.getTimeZone((String)testCase[1]); + Output timeType = new Output(); + String out = tzfmt.format((Style)testCase[3], tz, ((Date)testCase[2]).getTime(), timeType); + + if (!out.equals((String)testCase[4]) || timeType.value != testCase[5]) { + errln("Format result for [locale=" + testCase[0] + ",tzid=" + testCase[1] + ",date=" + testCase[2] + + ",style=" + testCase[3] + "]: expected [output=" + testCase[4] + ",type=" + testCase[5] + + "]; actual [output=" + out + ",type=" + timeType.value + "]"); + } + } + } } \ No newline at end of file diff --git a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/serializable/FormatTests.java b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/serializable/FormatTests.java index 97b9e8ea3e7..212ee40195a 100644 --- a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/serializable/FormatTests.java +++ b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/serializable/FormatTests.java @@ -1,6 +1,6 @@ /* ******************************************************************************* - * Copyright (c) 2004-2013, International Business Machines + * Copyright (c) 2004-2014, International Business Machines * Corporation and others. All Rights Reserved. ******************************************************************************* * @@ -13,6 +13,7 @@ import java.util.HashMap; import java.util.Locale; import com.ibm.icu.impl.DateNumberFormat; +import com.ibm.icu.impl.TZDBTimeZoneNames; import com.ibm.icu.impl.TimeZoneGenericNames; import com.ibm.icu.impl.TimeZoneGenericNames.GenericNameType; import com.ibm.icu.impl.Utility; @@ -38,6 +39,7 @@ import com.ibm.icu.text.TimeUnitFormat; import com.ibm.icu.text.TimeZoneFormat; import com.ibm.icu.text.TimeZoneFormat.Style; import com.ibm.icu.text.TimeZoneNames; +import com.ibm.icu.text.TimeZoneNames.NameType; import com.ibm.icu.util.Calendar; import com.ibm.icu.util.DateInterval; import com.ibm.icu.util.GregorianCalendar; @@ -2297,6 +2299,50 @@ public class FormatTests } } + public static class TZDBTimeZoneNamesHandler implements SerializableTest.Handler { + public Object[] getTestObjects() { + return new Object[] { + TimeZoneNames.getTZDBInstance(ULocale.ENGLISH), + TimeZoneNames.getTZDBInstance(ULocale.JAPAN) + }; + } + public boolean hasSameBehavior(Object a, Object b) { + TZDBTimeZoneNames tzdbna = (TZDBTimeZoneNames)a; + TZDBTimeZoneNames tzdbnb = (TZDBTimeZoneNames)b; + + final String[] TZIDS = { + "America/Los_Angeles", + "America/Argentina/Buenos_Aires", + "Asia/Shanghai", + "Etc/GMT" + }; + + final long[] DATES = { + 1277942400000L, // 2010-07-01 00:00:00 GMT + 1293840000000L, // 2011-01-01 00:00:00 GMT + }; + + final NameType[] nTypes = { + NameType.SHORT_STANDARD, + NameType.SHORT_DAYLIGHT + }; + + for (String tzid : TZIDS) { + for (NameType nt : nTypes) { + for (long date : DATES) { + String nameA = tzdbna.getDisplayName(tzid, nt, date); + String nameB = tzdbnb.getDisplayName(tzid, nt, date); + if (!Utility.objectEquals(nameA, nameB)) { + return false; + } + } + } + } + + return true; + } + } + public static class TimeZoneFormatHandler implements SerializableTest.Handler { static final String CUSTOM_GMT_PATTERN = "Offset {0} from UTC"; diff --git a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/serializable/SerializableTest.java b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/serializable/SerializableTest.java index 493382f6542..bcf668e990a 100644 --- a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/serializable/SerializableTest.java +++ b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/serializable/SerializableTest.java @@ -736,6 +736,7 @@ public class SerializableTest extends TestFmwk.TestGroup map.put("com.ibm.icu.impl.TimeZoneNamesImpl", new FormatTests.TimeZoneNamesHandler()); map.put("com.ibm.icu.text.TimeZoneFormat", new FormatTests.TimeZoneFormatHandler()); map.put("com.ibm.icu.impl.TimeZoneGenericNames", new FormatTests.TimeZoneGenericNamesHandler()); + map.put("com.ibm.icu.impl.TZDBTimeZoneNames", new FormatTests.TZDBTimeZoneNamesHandler()); map.put("com.ibm.icu.util.Calendar", new CalendarTests.CalendarHandler()); map.put("com.ibm.icu.util.BuddhistCalendar", new CalendarTests.BuddhistCalendarHandler()); -- 2.40.0