]> granicus.if.org Git - icu/commitdiff
ICU-10934 The tz database abbreviaion support in ICU4J
authorYoshito Umaoka <y.umaoka@gmail.com>
Wed, 23 Jul 2014 21:53:03 +0000 (21:53 +0000)
committerYoshito Umaoka <y.umaoka@gmail.com>
Wed, 23 Jul 2014 21:53:03 +0000 (21:53 +0000)
X-SVN-Rev: 36081

.gitattributes
icu4j/main/classes/core/src/com/ibm/icu/impl/TZDBTimeZoneNames.java [new file with mode: 0644]
icu4j/main/classes/core/src/com/ibm/icu/impl/TimeZoneNamesImpl.java
icu4j/main/classes/core/src/com/ibm/icu/text/TimeZoneFormat.java
icu4j/main/classes/core/src/com/ibm/icu/text/TimeZoneNames.java
icu4j/main/shared/data/icudata.jar
icu4j/main/shared/data/icutzdata.jar
icu4j/main/tests/core/src/com/ibm/icu/dev/test/format/TimeZoneFormatTest.java
icu4j/main/tests/core/src/com/ibm/icu/dev/test/serializable/FormatTests.java
icu4j/main/tests/core/src/com/ibm/icu/dev/test/serializable/SerializableTest.java

index a0ecae30e436205dbb814cbc50527dd90f0769d2..f735850c785bb69691f0395ff06a6f207f72d0e5 100644 (file)
@@ -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 (file)
index 0000000..22a8dd4
--- /dev/null
@@ -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<String, TZDBNames> TZDB_NAMES_MAP = 
+            new ConcurrentHashMap<String, TZDBNames>();
+
+    private static volatile TextTrieMap<TZDBNameInfo> 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<String> getAvailableMetaZoneIDs() {
+        return TimeZoneNamesImpl._getAvailableMetaZoneIDs();
+    }
+
+    /* (non-Javadoc)
+     * @see com.ibm.icu.text.TimeZoneNames#getAvailableMetaZoneIDs(java.lang.String)
+     */
+    @Override
+    public Set<String> 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<MatchInfo> find(CharSequence text, int start, EnumSet<NameType> 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<TZDBNameInfo> {
+        private EnumSet<NameType> _nameTypes;
+        private Collection<MatchInfo> _matches;
+        private String _region;
+
+        TZDBNameSearchHandler(EnumSet<NameType> 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<TZDBNameInfo> 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<MatchInfo>();
+                }
+                _matches.add(minfo);
+            }
+
+            return true;
+        }
+
+        /**
+         * Returns the match results
+         * @return the match results
+         */
+        public Collection<MatchInfo> 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<TZDBNameInfo>(true);
+                    Set<String> 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;
+    }
+}
index 120ebc41e7afa2955698552edc37019fb1e41c11..9381c0f6bc34716f82fa7b60df583edd7525bfaf 100644 (file)
@@ -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<String> METAZONE_IDS;
+    private static volatile Set<String> 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<String> getAvailableMetaZoneIDs() {
+    public Set<String> getAvailableMetaZoneIDs() {
+        return _getAvailableMetaZoneIDs();
+    }
+
+    static Set<String> _getAvailableMetaZoneIDs() {
         if (METAZONE_IDS == null) {
-            UResourceBundle bundle = UResourceBundle.getBundleInstance(ICUResourceBundle.ICU_BASE_NAME, "metaZones");
-            UResourceBundle mapTimezones = bundle.get("mapTimezones");
-            Set<String> 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<String> keys = mapTimezones.keySet();
+                    METAZONE_IDS = Collections.unmodifiableSet(keys);
+                }
+            }
         }
         return METAZONE_IDS;
     }
@@ -80,6 +88,10 @@ public class TimeZoneNamesImpl extends TimeZoneNames {
      */
     @Override
     public Set<String> getAvailableMetaZoneIDs(String tzID) {
+        return _getAvailableMetaZoneIDs(tzID);
+    }
+
+    static Set<String> _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;
         }
index 2219f756a8cf4010b400657b6d9f7f821411e966..874c6b7edb3ceb82d60fdabef180b22eb6033090 100644 (file)
@@ -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<TimeZoneFormat>
          * 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<TimeZoneFormat>
     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<TimeZoneFormat>
 
     private transient boolean _frozen;
 
+    private transient volatile TimeZoneNames _tzdbNames;
 
     /*
      * Static final fields
@@ -524,6 +536,23 @@ public class TimeZoneFormat extends UFormat implements Freezable<TimeZoneFormat>
         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<TimeZoneFormat>
      * @stable ICU 49
      */
     public TimeZoneFormat setDefaultParseOptions(EnumSet<ParseOption> 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<TimeZoneFormat>
      * @stable ICU 49
      */
     public EnumSet<ParseOption> 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<TimeZoneFormat>
             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<TimeZoneFormat>
                         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<MatchInfo> 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<TimeZoneFormat>
                     parsedTimeType = getTimeType(specificMatch.nameType());
                     parsedOffset = UNKNOWN_OFFSET;
                 }
-                
+            }
+            if (parseTZDBAbbrev && parsedPos < maxPos && (evaluated & Style.SPECIFIC_SHORT.flag) == 0) {
+                Collection<MatchInfo> 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) {
index 5aad466d7e32d6cc098052c05f6b2d0b0044c720..4caa6a77ca0f587ebb7b96ac2418d1e1e4d23af0 100644 (file)
@@ -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 <code>TimeZoneDisplayNames</code> for the specified locale.
+     * Returns an instance of <code>TimeZoneNames</code> for the specified locale.
      * 
      * @param locale
      *            The locale.
-     * @return An instance of <code>TimeZoneDisplayNames</code>
+     * @return An instance of <code>TimeZoneNames</code>
      * @stable ICU 49
      */
     public static TimeZoneNames getInstance(ULocale locale) {
@@ -173,7 +174,7 @@ public abstract class TimeZoneNames implements Serializable {
     }
 
     /**
-     * Returns an instance of <code>TimeZoneDisplayNames</code> for the specified JDK locale.
+     * Returns an instance of <code>TimeZoneNames</code> 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 <code>TimeZoneNames</code> 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).
+     * <br>
+     * 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.
index 3dfc24b5c57d8941d7733ed9c0304e5121a32dc7..510acc80f367a4568a141ec522ea5bd0054333f2 100755 (executable)
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:df42e127c7c6127bce3a0a44577d33d8457fc073f5fa131f6916c039b374932c
-size 10503267
+oid sha256:3d46efbf5fced8f73fd8e352902259aa0ba4c89fdf9fcbee24862044a09c56b8
+size 10505785
index ec6c1f9bd1439747781e7db54f7ec3896767027d..5d693364fedd039f82115bf02bfa307517dee85e 100755 (executable)
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:33523d17a26aa17b47fafbd6242592cd070436cd988b32d67230612a31432ca8
+oid sha256:d3137adf135a24c2bb8f5fcbbbdea86e07a79f3eb8f4dab3734e88afa7a2dc7a
 size 91046
index e11a6207f9c934bc4209dae63a9e9c81b5e7b4d4..a1114076d63b119f9d67269acf20765f2dbb8fb9 100644 (file)
@@ -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<ParseOption> options = (Boolean)test[4] ? EnumSet.of(ParseOption.ALL_STYLES) : null;
+            EnumSet<ParseOption> options = (EnumSet<ParseOption>)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> timeType = new Output<TimeType>();
+            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
index 97b9e8ea3e72262d33a0dff57e4f7cad3d0d902d..212ee40195a267cf87b577c10e9e1695dd41e124 100644 (file)
@@ -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";
 
index 493382f65425a6fb4ebf0e52b370c65d4eb72f20..bcf668e990a9cf2a214775f289b27272a8f7a184 100644 (file)
@@ -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());