ICU-8342 TimeZoneNames/TimeZoneFormat APIs as 4.8 technology preview. Also, including Output<T> (#8475), GMT zero format support, fallback region format support.

X-SVN-Rev: 29982
This commit is contained in:
Yoshito Umaoka 2011-05-03 13:36:47 +00:00
parent 04435370a7
commit 49c4bc01ff
23 changed files with 5236 additions and 2583 deletions

6
.gitattributes vendored
View File

@ -244,6 +244,12 @@ icu4j/main/classes/core/.project -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/TimeZoneGenericNames.java -text
icu4j/main/classes/core/src/com/ibm/icu/impl/TimeZoneNamesFactoryImpl.java -text
icu4j/main/classes/core/src/com/ibm/icu/impl/TimeZoneNamesImpl.java -text
icu4j/main/classes/core/src/com/ibm/icu/text/TimeZoneFormat.java -text
icu4j/main/classes/core/src/com/ibm/icu/text/TimeZoneNames.java -text
icu4j/main/classes/core/src/com/ibm/icu/util/Output.java -text
icu4j/main/classes/core/src/com/ibm/icu/util/Region.java -text
icu4j/main/classes/currdata/.externalToolBuilders/copy-data-currdata.launch -text
icu4j/main/classes/currdata/.settings/org.eclipse.core.resources.prefs -text

View File

@ -22,12 +22,6 @@ com.ibm.icu.util.TimeZone.DefaultTimeZoneType = ICU
#
com.ibm.icu.text.DecimalFormat.SkipExtendedSeparatorParsing = false
# Sets the default MessageFormat apostrophe-quoting behavior.
# See the com.ibm.icu.text.MessagePattern.ApostropheMode enum documentation.
# Values: DOUBLE_OPTIONAL or DOUBLE_REQUIRED.
# This is new in ICU 4.8.
# DOUBLE_OPTIONAL is the ICU default behavior.
com.ibm.icu.text.MessagePattern.ApostropheMode = DOUBLE_OPTIONAL
#
# [Internal Use Only]
@ -35,3 +29,9 @@ com.ibm.icu.text.MessagePattern.ApostropheMode = DOUBLE_OPTIONAL
# at run time.
#
com.ibm.icu.impl.ICUResourceBundle.skipRuntimeLocaleResourceScan = false
#
# [Internal Use Only]
# Time zone names service factory
#
# com.ibm.icu.text.TimeZoneNames.Factory.impl = com.ibm.icu.impl.TimeZoneNamesFactoryImpl

View File

@ -1,6 +1,6 @@
/*
*******************************************************************************
* Copyright (C) 2007-2010, International Business Machines
* Copyright (C) 2007-2011, International Business Machines
* Corporation and others. All Rights Reserved.
*******************************************************************************
*/
@ -11,6 +11,7 @@ import java.io.ObjectInputStream;
import java.math.BigInteger;
import java.text.FieldPosition;
import java.text.ParsePosition;
import java.util.Arrays;
import java.util.MissingResourceException;
import com.ibm.icu.lang.UCharacter;
@ -77,9 +78,11 @@ public final class DateNumberFormat extends NumberFormat {
elems[10] = minusString.charAt(0);
CACHE.put(loc, elems);
}
digits = new char[10];
System.arraycopy(elems, 0, digits, 0, 10);
zeroDigit = digits[0];
minusSign = elems[10];
}
@ -105,24 +108,22 @@ public final class DateNumberFormat extends NumberFormat {
}
public char getZeroDigit() {
if ( digits != null ) {
return digits[0];
} else {
return zeroDigit;
}
return zeroDigit;
}
public void setZeroDigit(char zero) {
if ( digits == null ) {
zeroDigit = zero;
if (digits == null) {
digits = new char[10];
}
digits[0] = zero;
if (Character.digit(zero,10) == 0) {
for ( int i = 1 ; i < 10 ; i++ ) {
digits[i] = (char)(zero+i);
}
for ( int i = 1 ; i < 10 ; i++ ) {
digits[i] = (char)(zero+i);
}
}
public char[] getDigits() {
return digits;
}
public StringBuffer format(double number, StringBuffer toAppendTo,
@ -136,6 +137,7 @@ public final class DateNumberFormat extends NumberFormat {
if (numberL < 0) {
// negative
toAppendTo.append(minusSign);
numberL = -numberL;
}
// Note: NumberFormat used by DateFormat only uses int numbers.
@ -235,33 +237,18 @@ public final class DateNumberFormat extends NumberFormat {
return false;
}
DateNumberFormat other = (DateNumberFormat)obj;
for (int i = 0 ; i < 10 ; i++) {
char check1, check2;
if ( digits != null ) {
check1 = digits[i];
} else {
check1 = (char)(zeroDigit+i);
}
if ( other.digits != null ) {
check2 = other.digits[i];
} else {
check2 = (char)(other.zeroDigit+i);
}
if (check1 != check2) {
return false;
}
}
return (this.maxIntDigits == other.maxIntDigits
&& this.minIntDigits == other.minIntDigits
&& this.minusSign == other.minusSign
&& this.positiveOnly == other.positiveOnly);
&& this.positiveOnly == other.positiveOnly
&& Arrays.equals(this.digits, other.digits));
}
private void readObject(ObjectInputStream stream) throws IOException, ClassNotFoundException {
stream.defaultReadObject();
if (digits == null) {
setZeroDigit(zeroDigit);
}
// re-allocate the work buffer
decimalBuf = new char[20];
}

View File

@ -1,6 +1,6 @@
/*
*******************************************************************************
* Copyright (C) 2005-2010, International Business Machines Corporation and *
* Copyright (C) 2005-2011, International Business Machines Corporation and *
* others. All Rights Reserved. *
*******************************************************************************
*/
@ -401,6 +401,26 @@ public class OlsonTimeZone extends BasicTimeZone {
return true;
}
/**
* Returns the canonical ID of this system time zone
*/
public String getCanonicalID() {
if (canonicalID == null) {
synchronized(this) {
if (canonicalID == null) {
canonicalID = getCanonicalID(getID());
assert(canonicalID != null);
if (canonicalID == null) {
// This should never happen...
canonicalID = getID();
}
}
}
}
return canonicalID;
}
/**
* Construct a GMT+0 zone with no transitions. This is done when a
* constructor fails so the resultant object is well-behaved.
@ -589,7 +609,21 @@ public class OlsonTimeZone extends BasicTimeZone {
super.setID(id);
}
/* (non-Javadoc)
* @see com.ibm.icu.util.TimeZone#setID(java.lang.String)
*/
@Override
public void setID(String id){
// Before updating the ID, preserve the original ID's canonical ID.
if (canonicalID == null) {
canonicalID = getCanonicalID(getID());
assert(canonicalID != null);
if (canonicalID == null) {
// This should never happen...
canonicalID = getID();
}
}
if (finalZone != null){
finalZone.setID(id);
}
@ -796,6 +830,12 @@ public class OlsonTimeZone extends BasicTimeZone {
*/
private SimpleTimeZone finalZone = null; // owned, may be NULL
/**
* The canonical ID of this zone. Initialized when {@link #getCanonicalID()}
* is invoked first time, or {@link #setID(String)} is called.
*/
private volatile String canonicalID = null;
private static final String ZONEINFORES = "zoneinfo64";
private static final boolean DEBUG = ICUDebug.enabled("olson");

View File

@ -1,49 +1,46 @@
/*
* ********************************************************************************
* Copyright (C) 2007-2009, International Business Machines Corporation and others.
* Copyright (C) 2007-2011, International Business Machines Corporation and others.
* All Rights Reserved.
* ********************************************************************************
*/
package com.ibm.icu.impl;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.ListIterator;
import com.ibm.icu.lang.UCharacter;
import com.ibm.icu.text.UTF16;
/**
* TextTrieMap is a trie implementation for supporting
* fast prefix match for the key.
*/
public class TextTrieMap<V> {
private Node _root = new Node();
boolean _ignoreCase;
/**
* Constructs a TextTrieMap object.
*
* @param ignoreCase true to use case insensitive match
* @param ignoreCase true to use simple case insensitive match
*/
public TextTrieMap(boolean ignoreCase) {
this.ignoreCase = ignoreCase;
_ignoreCase = ignoreCase;
}
/**
* Adds the text key and its associated object in this object.
*
* @param text The text.
* @param o The object associated with the text.
* @param val The value object associated with the text.
*/
public synchronized void put(String text, V o) {
CharacterNode node = root;
for (int i = 0; i < text.length(); i++) {
int ch = UTF16.charAt(text, i);
node = node.addChildNode(ch);
if (UTF16.getCharCount(ch) == 2) {
i++;
}
}
node.addObject(o);
public TextTrieMap<V> put(CharSequence text, V val) {
CharIterator chitr = new CharIterator(text, 0, _ignoreCase);
_root.add(chitr, val);
return this;
}
/**
@ -71,8 +68,15 @@ public class TextTrieMap<V> {
* matching entry is found.
*/
public Iterator<V> get(String text, int start) {
return get(text, start, null);
}
public Iterator<V> get(String text, int start, int[] matchLen) {
LongestMatchHandler<V> handler = new LongestMatchHandler<V>();
find(text, start, handler);
if (matchLen != null && matchLen.length > 0) {
matchLen[0] = handler.getMatchLength();
}
return handler.getMatches();
}
@ -80,164 +84,94 @@ public class TextTrieMap<V> {
find(text, 0, handler);
}
public void find(String text, int start, ResultHandler<V> handler) {
find(root, text, start, start, handler);
public void find(String text, int offset, ResultHandler<V> handler) {
CharIterator chitr = new CharIterator(text, offset, _ignoreCase);
find(_root, chitr, handler);
}
/*
* Find an iterator of the objects associated with the
* longest prefix matching string key under the specified node.
*
* @param node The character node in this trie.
* @param text The text to be matched with prefixes.
* @param start The start index within the text.
* @param index The current index within the text.
* @param handler The result handler, ResultHandler#handlePrefixMatch
* is called when any prefix match is found.
*/
private synchronized void find(CharacterNode node, String text,
int start, int index, ResultHandler<V> handler) {
Iterator<V> itr = node.iterator();
if (itr != null) {
if (!handler.handlePrefixMatch(index - start, itr)) {
private synchronized void find(Node node, CharIterator chitr, ResultHandler<V> handler) {
Iterator<V> values = node.values();
if (values != null) {
if (!handler.handlePrefixMatch(chitr.processedLength(), values)) {
return;
}
}
if (index < text.length()) {
List<CharacterNode> childNodes = node.getChildNodes();
if (childNodes == null) {
return;
}
int ch = UTF16.charAt(text, index);
int chLen = UTF16.getCharCount(ch);
for (int i = 0; i < childNodes.size(); i++) {
CharacterNode child = childNodes.get(i);
if (compare(ch, child.getCharacter())) {
find(child, text, start, index + chLen, handler);
break;
}
}
Node nextMatch = node.findMatch(chitr);
if (nextMatch != null) {
find(nextMatch, chitr, handler);
}
}
/**
* A private method used for comparing two characters.
*
* @param ch1 The first character.
* @param ch2 The second character.
* @return true if the first character matches the second.
*/
private boolean compare(int ch1, int ch2) {
if (ch1 == ch2) {
public static class CharIterator implements Iterator<Character> {
private boolean _ignoreCase;
private CharSequence _text;
private int _nextIdx;
private int _startIdx;
private Character _remainingChar;
CharIterator(CharSequence text, int offset, boolean ignoreCase) {
_text = text;
_nextIdx = _startIdx = offset;
_ignoreCase = ignoreCase;
}
/* (non-Javadoc)
* @see java.util.Iterator#hasNext()
*/
public boolean hasNext() {
if (_nextIdx == _text.length() && _remainingChar == null) {
return false;
}
return true;
}
else if (ignoreCase) {
if (UCharacter.toLowerCase(ch1) == UCharacter.toLowerCase(ch2)) {
return true;
}
else if (UCharacter.toUpperCase(ch1) == UCharacter.toUpperCase(ch2)) {
return true;
}
}
return false;
}
// The root node of this trie
private CharacterNode root = new CharacterNode(0);
// Character matching option
boolean ignoreCase;
/**
* Inner class representing a character node in the trie.
*/
private class CharacterNode {
int character;
List<CharacterNode> children;
List<V> objlist;
/**
* Constructs a node for the character.
*
* @param ch The character associated with this node.
/* (non-Javadoc)
* @see java.util.Iterator#next()
*/
public CharacterNode(int ch) {
character = ch;
}
/**
* Gets the character associated with this node.
*
* @return The character
*/
public int getCharacter() {
return character;
}
/**
* Adds the object to the node.
*
* @param obj The object set in the leaf node.
*/
public void addObject(V obj) {
if (objlist == null) {
objlist = new LinkedList<V>();
}
objlist.add(obj);
}
/**
* Gets an iterator of the objects associated with
* the leaf node.
*
* @return The iterator or null if no objects are
* associated with this node.
*/
public Iterator<V> iterator() {
if (objlist == null) {
public Character next() {
if (_nextIdx == _text.length() && _remainingChar == null) {
return null;
}
return objlist.iterator();
Character next;
if (_remainingChar != null) {
next = _remainingChar;
_remainingChar = null;
} else {
if (_ignoreCase) {
int cp = UCharacter.foldCase(Character.codePointAt(_text, _nextIdx), true);
_nextIdx = _nextIdx + Character.charCount(cp);
char[] chars = Character.toChars(cp);
next = chars[0];
if (chars.length == 2) {
_remainingChar = chars[1];
}
} else {
next = _text.charAt(_nextIdx);
_nextIdx++;
}
}
return next;
}
/**
* Adds a child node for the character under this character
* node in the trie. When the matching child node already
* exists, the reference of the existing child node is
* returned.
*
* @param ch The character associated with a child node.
* @return The child node.
/* (non-Javadoc)
* @see java.util.Iterator#remove()
*/
public CharacterNode addChildNode(int ch) {
if (children == null) {
children = new ArrayList<CharacterNode>();
CharacterNode newNode = new CharacterNode(ch);
children.add(newNode);
return newNode;
}
CharacterNode node = null;
for (int i = 0; i < children.size(); i++) {
CharacterNode cur = children.get(i);
if (compare(ch, cur.getCharacter())) {
node = cur;
break;
}
}
if (node == null) {
node = new CharacterNode(ch);
children.add(node);
}
return node;
public void remove() {
throw new UnsupportedOperationException("remove() not supproted");
}
/**
* Gets the list of child nodes under this node.
*
* @return The list of child nodes.
*/
public List<CharacterNode> getChildNodes() {
return children;
public int nextIndex() {
return _nextIdx;
}
public int processedLength() {
if (_remainingChar != null) {
throw new IllegalStateException("In the middle of surrogate pair");
}
return _nextIdx - _startIdx;
}
}
@ -271,5 +205,181 @@ public class TextTrieMap<V> {
public Iterator<V> getMatches() {
return matches;
}
public int getMatchLength() {
return length;
}
}
/**
* Inner class representing a text node in the trie.
*/
private class Node {
private char[] _text;
private List<V> _values;
private List<Node> _children;
private Node() {
}
private Node(char[] text, List<V> values, List<Node> children) {
_text = text;
_values = values;
_children = children;
}
public Iterator<V> values() {
if (_values == null) {
return null;
}
return _values.iterator();
}
public void add(CharIterator chitr, V value) {
StringBuilder buf = new StringBuilder();
while (chitr.hasNext()) {
buf.append(chitr.next());
}
add(toCharArray(buf), 0, value);
}
public Node findMatch(CharIterator chitr) {
if (_children == null) {
return null;
}
if (!chitr.hasNext()) {
return null;
}
Node match = null;
Character ch = chitr.next();
for (Node child : _children) {
if (ch < child._text[0]) {
break;
}
if (ch == child._text[0]) {
if (child.matchFollowing(chitr)) {
match = child;
}
break;
}
}
return match;
}
private void add(char[] text, int offset, V value) {
if (text.length == offset) {
_values = addValue(_values, value);
return;
}
if (_children == null) {
_children = new LinkedList<Node>();
Node child = new Node(subArray(text, offset), addValue(null, value), null);
_children.add(child);
return;
}
// walk through children
ListIterator<Node> litr = _children.listIterator();
while (litr.hasNext()) {
Node next = litr.next();
if (text[offset] < next._text[0]) {
litr.previous();
break;
}
if (text[offset] == next._text[0]) {
int matchLen = next.lenMatches(text, offset);
if (matchLen == next._text.length) {
// full match
next.add(text, offset + matchLen, value);
} else {
// partial match, create a branch
next.split(matchLen);
next.add(text, offset + matchLen, value);
}
return;
}
}
// add a new child to this node
litr.add(new Node(subArray(text, offset), addValue(null, value), null));
}
private boolean matchFollowing(CharIterator chitr) {
boolean matched = true;
int idx = 1;
while (idx < _text.length) {
if(!chitr.hasNext()) {
matched = false;
break;
}
Character ch = chitr.next();
if (ch != _text[idx]) {
matched = false;
break;
}
idx++;
}
return matched;
}
private int lenMatches(char[] text, int offset) {
int textLen = text.length - offset;
int limit = _text.length < textLen ? _text.length : textLen;
int len = 0;
while (len < limit) {
if (_text[len] != text[offset + len]) {
break;
}
len++;
}
return len;
}
private void split(int offset) {
// split the current node at the offset
char[] childText = subArray(_text, offset);
_text = subArray(_text, 0, offset);
// add the Node representing after the offset as a child
Node child = new Node(childText, _values, _children);
_values = null;
_children = new LinkedList<Node>();
_children.add(child);
}
private List<V> addValue(List<V> list, V value) {
if (list == null) {
list = new LinkedList<V>();
}
list.add(value);
return list;
}
}
private static char[] toCharArray(CharSequence text) {
char[] array = new char[text.length()];
for (int i = 0; i < array.length; i++) {
array[i] = text.charAt(i);
}
return array;
}
private static char[] subArray(char[] array, int start) {
if (start == 0) {
return array;
}
char[] sub = new char[array.length - start];
System.arraycopy(array, start, sub, 0, sub.length);
return sub;
}
private static char[] subArray(char[] array, int start, int limit) {
if (start == 0 && limit == array.length) {
return array;
}
char[] sub = new char[limit - start];
System.arraycopy(array, start, sub, 0, limit - start);
return sub;
}
}

View File

@ -1,94 +0,0 @@
/*
*******************************************************************************
* Copyright (C) 2010, International Business Machines Corporation and *
* others. All Rights Reserved. *
*******************************************************************************
*/
package com.ibm.icu.impl;
import com.ibm.icu.util.TimeZone;
import com.ibm.icu.util.ULocale;
/**
* @author JCEmmons
*
*/
public class TimeZoneFormat {
public ZoneStringFormat zsf;
public static TimeZoneFormat createInstance ( ULocale loc ) {
TimeZoneFormat tzf = new TimeZoneFormat();
tzf.zsf = ZoneStringFormat.getInstance(loc);
return tzf;
}
public String format ( TimeZone tz, long date, int style ) {
String result = null;
switch ( style ) {
case TimeZone.SHORT :
case TimeZone.SHORT_COMMONLY_USED :
result = zsf.getSpecificShortString(tz, date, style == TimeZone.SHORT_COMMONLY_USED);
break;
case TimeZone.LONG :
result = zsf.getSpecificLongString(tz, date);
break;
case TimeZone.SHORT_GENERIC :
result = zsf.getGenericShortString(tz, date, true);
break;
case TimeZone.LONG_GENERIC :
result = zsf.getGenericLongString(tz, date);
break;
case TimeZone.SHORT_GMT :
result = zsf.getShortGMTString(tz,date);
break;
case TimeZone.LONG_GMT :
result = zsf.getLongGMTString(tz, date);
break;
case TimeZone.GENERIC_LOCATION :
result = zsf.getGenericLocationString(tz, date);
}
return result;
}
public String format ( TimeZone tz, int style, boolean daylight ) {
return format (tz, System.currentTimeMillis(), style, daylight);
}
public String format ( TimeZone tz, long date , int style , boolean daylight ) {
String result = null;
switch ( style ) {
case TimeZone.LONG :
if ( daylight ) {
result = zsf.getLongDaylight(tz.getID(), date);
} else {
result = zsf.getLongStandard(tz.getID(),date);
}
break;
case TimeZone.SHORT :
case TimeZone.SHORT_COMMONLY_USED :
if ( daylight ) {
result = zsf.getShortDaylight(tz.getID(), date, style == TimeZone.SHORT_COMMONLY_USED);
} else {
result = zsf.getShortStandard(tz.getID(), date, style == TimeZone.SHORT_COMMONLY_USED);
}
break;
case TimeZone.SHORT_GMT :
result = zsf.getShortGMTString(tz, date, daylight);
break;
case TimeZone.LONG_GMT:
result = zsf.getLongGMTString(tz, date, daylight);
break;
default :
result = format ( tz, date, style );
break;
}
return result;
}
}

View File

@ -0,0 +1,903 @@
/*
*******************************************************************************
* Copyright (C) 2011, International Business Machines Corporation and *
* others. All Rights Reserved. *
*******************************************************************************
*/
package com.ibm.icu.impl;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.Serializable;
import java.lang.ref.WeakReference;
import java.text.MessageFormat;
import java.util.Collection;
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.LocaleDisplayNames;
import com.ibm.icu.text.TimeZoneFormat.TimeType;
import com.ibm.icu.text.TimeZoneNames;
import com.ibm.icu.text.TimeZoneNames.MatchInfo;
import com.ibm.icu.text.TimeZoneNames.NameType;
import com.ibm.icu.util.BasicTimeZone;
import com.ibm.icu.util.Freezable;
import com.ibm.icu.util.TimeZone;
import com.ibm.icu.util.TimeZone.SystemTimeZoneType;
import com.ibm.icu.util.TimeZoneTransition;
import com.ibm.icu.util.ULocale;
/**
* This class interact with TimeZoneNames and LocaleDisplayNames
* to format and parse time zone's generic display names.
* It is not recommended to use this class directly, instead
* use com.ibm.icu.text.TimeZoneFormat.
*/
public class TimeZoneGenericNames implements Serializable, Freezable<TimeZoneGenericNames> {
private static final long serialVersionUID = 2729910342063468417L;
/**
* Generic name type enum
*/
public enum GenericNameType {
LOCATION ("LONG", "SHORT"),
LONG (),
SHORT ();
String[] _fallbackTypeOf;
GenericNameType(String... fallbackTypeOf) {
_fallbackTypeOf = fallbackTypeOf;
}
public boolean isFallbackTypeOf(GenericNameType type) {
String typeStr = type.toString();
for (String t : _fallbackTypeOf) {
if (t.equals(typeStr)) {
return true;
}
}
return false;
}
}
/**
* Format pattern enum used for composing location and partial location names
*/
public enum Pattern {
// The format pattern such as "{0} Time", where {0} is the country.
REGION_FORMAT("regionFormat", "({0})"),
// The format pattern such as "{1} Time ({0})", where {1} is the country and {0} is a city.
FALLBACK_REGION_FORMAT("fallbackRegionFormat", "{1} ({0})"),
// The format pattern such as "{1} ({0})", where {1} is the metazone, and {0} is the country or city.
FALLBACK_FORMAT("fallbackFormat", "{1} ({0})");
String _key;
String _defaultVal;
Pattern(String key, String defaultVal) {
_key = key;
_defaultVal = defaultVal;
}
String key() {
return _key;
}
String defaultValue() {
return _defaultVal;
}
}
private ULocale _locale;
private TimeZoneNames _tznames;
private transient boolean _frozen;
private transient String _region;
private transient WeakReference<LocaleDisplayNames> _localeDisplayNamesRef;
private transient MessageFormat[] _patternFormatters;
private transient ConcurrentHashMap<String, String> _genericLocationNamesMap;
private transient ConcurrentHashMap<String, String> _genericPartialLocationNamesMap;
private transient TextTrieMap<NameInfo> _gnamesTrie;
private transient boolean _gnamesTrieFullyLoaded;
private static Cache GENERIC_NAMES_CACHE = new Cache();
// Window size used for DST check for a zone in a metazone (about a half year)
private static final long DST_CHECK_RANGE = 184L*(24*60*60*1000);
private static final NameType[] GENERIC_NON_LOCATION_TYPES =
{NameType.LONG_GENERIC, NameType.SHORT_GENERIC};
/**
* Constructs a <code>TimeZoneGenericNames</code> with the given locale
* and the <code>TimeZoneNames</code>.
* @param locale the locale
* @param tznames the TimeZoneNames
*/
public TimeZoneGenericNames(ULocale locale, TimeZoneNames tznames) {
_locale = locale;
_tznames = tznames;
init();
}
/**
* Private method initializing the instance of <code>TimeZoneGenericName</code>.
* This method should be called from a constructor and readObject.
*/
private void init() {
if (_tznames == null) {
_tznames = TimeZoneNames.getInstance(_locale);
}
_genericLocationNamesMap = new ConcurrentHashMap<String, String>();
_genericPartialLocationNamesMap = new ConcurrentHashMap<String, String>();
_gnamesTrie = new TextTrieMap<NameInfo>(true);
_gnamesTrieFullyLoaded = false;
// Preload zone strings for the default time zone
TimeZone tz = TimeZone.getDefault();
String tzCanonicalID = ZoneMeta.getCanonicalCLDRID(tz);
if (tzCanonicalID != null) {
loadStrings(tzCanonicalID);
}
}
/**
* Constructs a <code>TimeZoneGenericNames</code> with the given locale.
* This constructor is private and called from {@link #getInstance(ULocale)}.
* @param locale the locale
*/
private TimeZoneGenericNames(ULocale locale) {
this(locale, null);
}
/**
* The factory method of <code>TimeZoneGenericNames</code>. This static method
* returns a frozen instance of cached <code>TimeZoneGenericNames</code>.
* @param locale the locale
* @return A frozen <code>TimeZoneGenericNames</code>.
*/
public static TimeZoneGenericNames getInstance(ULocale locale) {
String key = locale.getBaseName();
return GENERIC_NAMES_CACHE.getInstance(key, locale);
}
/**
* Returns the display name of the time zone for the given name type
* at the given date, or null if the display name is not available.
*
* @param tz the time zone
* @param type the generic name type - see {@link GenericNameType}
* @param date the date
* @return the display name of the time zone for the given name type
* at the given date, or null.
*/
public String getDisplayName(TimeZone tz, GenericNameType type, long date) {
String name = null;
String tzCanonicalID = null;
switch (type) {
case LOCATION:
tzCanonicalID = ZoneMeta.getCanonicalCLDRID(tz);
if (tzCanonicalID != null) {
name = getGenericLocationName(tzCanonicalID);
}
break;
case LONG:
case SHORT:
name = formatGenericNonLocationName(tz, type, date);
if (name == null) {
tzCanonicalID = ZoneMeta.getCanonicalCLDRID(tz);
if (tzCanonicalID != null) {
name = getGenericLocationName(tzCanonicalID);
}
}
break;
}
return name;
}
/**
* Returns the generic location name for the given canonical time zone ID.
*
* @param canonicalTzID the canonical time zone ID
* @return the generic location name for the given canonical time zone ID.
*/
public String getGenericLocationName(String canonicalTzID) {
if (canonicalTzID == null || canonicalTzID.length() == 0) {
return null;
}
String name = _genericLocationNamesMap.get(canonicalTzID);
if (name != null) {
if (name.length() == 0) {
// empty string to indicate the name is not available
return null;
}
return name;
}
String countryCode = ZoneMeta.getCanonicalCountry(canonicalTzID);
if (countryCode != null) {
String country = getLocaleDisplayNames().regionDisplayName(countryCode);
if (ZoneMeta.getSingleCountry(canonicalTzID) != null) {
// If the zone is only one zone in the country, do not add city
name = formatPattern(Pattern.REGION_FORMAT, country);
} else {
// getExemplarLocationName should return non-empty String
// if the time zone is associated with a location
String city = _tznames.getExemplarLocationName(canonicalTzID);
name = formatPattern(Pattern.FALLBACK_REGION_FORMAT, city, country);
}
}
if (name == null) {
_genericLocationNamesMap.putIfAbsent(canonicalTzID.intern(), "");
} else {
synchronized (this) { // we have to sync the name map and the trie
canonicalTzID = canonicalTzID.intern();
String tmp = _genericLocationNamesMap.putIfAbsent(canonicalTzID, name.intern());
if (tmp == null) {
// Also put the name info the to trie
NameInfo info = new NameInfo();
info.tzID = canonicalTzID;
info.type = GenericNameType.LOCATION;
_gnamesTrie.put(name, info);
} else {
name = tmp;
}
}
}
return name;
}
/**
* Sets the pattern string for the pattern type.
* Note: This method is designed for CLDR ST - not for common use.
* @param patType the pattern type
* @param patStr the pattern string
* @return this object.
*/
public TimeZoneGenericNames setFormatPattern(Pattern patType, String patStr) {
if (isFrozen()) {
throw new UnsupportedOperationException("Attempt to modify frozen object");
}
// Changing pattern will invalidates cached names
if (!_genericLocationNamesMap.isEmpty()) {
_genericLocationNamesMap = new ConcurrentHashMap<String, String>();
}
if (!_genericPartialLocationNamesMap.isEmpty()) {
_genericPartialLocationNamesMap = new ConcurrentHashMap<String, String>();
}
_gnamesTrie = null;
_gnamesTrieFullyLoaded = false;
if (_patternFormatters == null) {
_patternFormatters = new MessageFormat[Pattern.values().length];
}
_patternFormatters[patType.ordinal()] = new MessageFormat(patStr);
return this;
}
/**
* Private method to get a generic string, with fallback logics involved,
* that is,
*
* 1. If a generic non-location string is available for the zone, return it.
* 2. If a generic non-location string is associated with a meta zone and
* the zone never use daylight time around the given date, use the standard
* string (if available).
* 3. If a generic non-location string is associated with a meta zone and
* the offset at the given time is different from the preferred zone for the
* current locale, then return the generic partial location string (if available)
* 4. If a generic non-location string is not available, use generic location
* string.
*
* @param tz the requested time zone
* @param date the date
* @param type the generic name type, either LONG or SHORT
* @return the name used for a generic name type, which could be the
* generic name, or the standard name (if the zone does not observes DST
* around the date), or the partial location name.
*/
private String formatGenericNonLocationName(TimeZone tz, GenericNameType type, long date) {
assert(type == GenericNameType.LONG || type == GenericNameType.SHORT);
String tzID = ZoneMeta.getCanonicalCLDRID(tz);
if (tzID == null) {
return null;
}
// Try to get a name from time zone first
NameType nameType = (type == GenericNameType.LONG) ? NameType.LONG_GENERIC : NameType.SHORT_GENERIC;
String name = _tznames.getTimeZoneDisplayName(tzID, nameType);
if (name != null) {
return name;
}
// Try meta zone
String mzID = _tznames.getMetaZoneID(tzID, date);
if (mzID != null) {
boolean useStandard = false;
int[] offsets = {0, 0};
tz.getOffset(date, false, offsets);
if (offsets[1] == 0) {
useStandard = true;
// Check if the zone actually uses daylight saving time around the time
if (tz instanceof BasicTimeZone) {
BasicTimeZone btz = (BasicTimeZone)tz;
TimeZoneTransition before = btz.getPreviousTransition(date, true);
if (before != null
&& (date - before.getTime() < DST_CHECK_RANGE)
&& before.getFrom().getDSTSavings() != 0) {
useStandard = false;
} else {
TimeZoneTransition after = btz.getNextTransition(date, false);
if (after != null
&& (after.getTime() - date < DST_CHECK_RANGE)
&& after.getTo().getDSTSavings() != 0) {
useStandard = false;
}
}
} else {
// If not BasicTimeZone... only if the instance is not an ICU's implementation.
// We may get a wrong answer in edge case, but it should practically work OK.
int[] tmpOffsets = new int[2];
tz.getOffset(date - DST_CHECK_RANGE, false, tmpOffsets);
if (tmpOffsets[1] != 0) {
useStandard = false;
} else {
tz.getOffset(date + DST_CHECK_RANGE, false, tmpOffsets);
if (tmpOffsets[1] != 0){
useStandard = false;
}
}
}
}
if (useStandard) {
NameType stdNameType = (nameType == NameType.LONG_GENERIC) ?
NameType.LONG_STANDARD : NameType.SHORT_STANDARD_COMMONLY_USED;
String stdName = _tznames.getDisplayName(tzID, stdNameType, date);
if (stdName != null) {
name = stdName;
// TODO: revisit this issue later
// In CLDR, a same display name is used for both generic and standard
// for some meta zones in some locales. This looks like a data bugs.
// For now, we check if the standard name is different from its generic
// name below.
String mzGenericName = _tznames.getMetaZoneDisplayName(mzID, nameType);
if (stdName.equalsIgnoreCase(mzGenericName)) {
name = null;
}
}
}
if (name == null) {
// Get a name from meta zone
String mzName = _tznames.getMetaZoneDisplayName(mzID, nameType);
if (mzName != null) {
// Check if we need to use a partial location format.
// This check is done by comparing offset with the meta zone's
// golden zone at the given date.
String goldenID = _tznames.getReferenceZoneID(mzID, getTargetRegion());
if (goldenID != null && !goldenID.equals(tzID)) {
TimeZone goldenZone = TimeZone.getTimeZone(goldenID);
int[] offsets1 = {0, 0};
// Check offset in the golden zone with wall time.
// With getOffset(date, false, offsets1),
// you may get incorrect results because of time overlap at DST->STD
// transition.
goldenZone.getOffset(date + offsets[0] + offsets[1], true, offsets1);
if (offsets[0] != offsets1[0] || offsets[1] != offsets1[1]) {
// Now we need to use a partial location format.
name = getPartialLocationName(tzID, mzID, (nameType == NameType.LONG_GENERIC), mzName);
} else {
name = mzName;
}
} else {
name = mzName;
}
}
}
}
return name;
}
/**
* Private simple pattern formatter used for formatting generic location names
* and partial location names. We intentionally use JDK MessageFormat
* for performance reason.
*
* @param pat the message pattern enum
* @param args the format argument(s)
* @return the formatted string
*/
private synchronized String formatPattern(Pattern pat, String... args) {
if (_patternFormatters == null) {
_patternFormatters = new MessageFormat[Pattern.values().length];
}
int idx = pat.ordinal();
if (_patternFormatters[idx] == null) {
String patText;
try {
ICUResourceBundle bundle = (ICUResourceBundle) ICUResourceBundle.getBundleInstance(
ICUResourceBundle.ICU_ZONE_BASE_NAME, _locale);
patText = bundle.getStringWithFallback("zoneStrings/" + pat.key());
} catch (MissingResourceException e) {
patText = pat.defaultValue();
}
_patternFormatters[idx] = new MessageFormat(patText);
}
return _patternFormatters[idx].format(args);
}
/**
* Private method returning LocaleDisplayNames instance for the locale of this
* instance. Because LocaleDisplayNames is only used for generic
* location formant and partial location format, the LocaleDisplayNames
* is instantiated lazily.
*
* @return the instance of LocaleDisplayNames for the locale of this object.
*/
private synchronized LocaleDisplayNames getLocaleDisplayNames() {
LocaleDisplayNames locNames = null;
if (_localeDisplayNamesRef != null) {
locNames = _localeDisplayNamesRef.get();
}
if (locNames == null) {
locNames = LocaleDisplayNames.getInstance(_locale);
_localeDisplayNamesRef = new WeakReference<LocaleDisplayNames>(locNames);
}
return locNames;
}
private synchronized void loadStrings(String tzCanonicalID) {
if (tzCanonicalID == null || tzCanonicalID.length() == 0) {
return;
}
// getGenericLocationName() formats a name and put it into the trie
getGenericLocationName(tzCanonicalID);
// Generic partial location format
Set<String> mzIDs = _tznames.getAvailableMetaZoneIDs(tzCanonicalID);
for (String mzID : mzIDs) {
// if this time zone is not the golden zone of the meta zone,
// partial location name (such as "PT (Los Angeles)") might be
// available.
String goldenID = _tznames.getReferenceZoneID(mzID, getTargetRegion());
if (!tzCanonicalID.equals(goldenID)) {
for (NameType genNonLocType : GENERIC_NON_LOCATION_TYPES) {
String mzGenName = _tznames.getMetaZoneDisplayName(mzID, genNonLocType);
if (mzGenName != null) {
// getPartialLocationName() formats a name and put it into the trie
getPartialLocationName(tzCanonicalID, mzID, (genNonLocType == NameType.LONG_GENERIC), mzGenName);
}
}
}
}
}
/**
* 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;
}
/**
* Private method for formatting partial location names. This format
* is used when a generic name of a meta zone is available, but the given
* time zone is not a reference zone (golden zone) of the meta zone.
*
* @param tzID the canonical time zone ID
* @param mzID the meta zone ID
* @param isLong true when long generic name
* @param mzDisplayName the meta zone generic display name
* @return the partial location format string
*/
private String getPartialLocationName(String tzID, String mzID, boolean isLong, String mzDisplayName) {
String letter = isLong ? "L" : "S";
String key = tzID + "&" + mzID + "#" + letter;
String name = _genericPartialLocationNamesMap.get(key);
if (name != null) {
return name;
}
String location = null;
String countryCode = ZoneMeta.getSingleCountry(tzID);
if (countryCode != null) {
location = getLocaleDisplayNames().regionDisplayName(countryCode);
} else {
location = _tznames.getExemplarLocationName(tzID);
if (location == null) {
// This could happen when the time zone is not associated with a country,
// and its ID is not hierarchical, for example, CST6CDT.
// We use the canonical ID itself as the location for this case.
location = tzID;
}
}
name = formatPattern(Pattern.FALLBACK_FORMAT, location, mzDisplayName);
synchronized (this) { // we have to sync the name map and the trie
String tmp = _genericPartialLocationNamesMap.putIfAbsent(key.intern(), name.intern());
if (tmp == null) {
NameInfo info = new NameInfo();
info.tzID = tzID.intern();
info.type = isLong ? GenericNameType.LONG : GenericNameType.SHORT;
_gnamesTrie.put(name, info);
} else {
name = tmp;
}
}
return name;
}
/**
* A private class used for storing the name information in the local trie.
*/
private static class NameInfo {
String tzID;
GenericNameType type;
}
/**
* A class used for returning the name search result used by
* {@link TimeZoneGenericNames#find(String, int, EnumSet)}.
*/
public static class GenericMatchInfo {
GenericNameType nameType;
String tzID;
int matchLength;
TimeType timeType = TimeType.UNKNOWN;
public GenericNameType nameType() {
return nameType;
}
public String tzID() {
return tzID;
}
public TimeType timeType() {
return timeType;
}
public int matchLength() {
return matchLength;
}
}
/**
* A private class implementing the search callback interface in
* <code>TextTrieMap</code> for collecting match results.
*/
private static class GenericNameSearchHandler implements ResultHandler<NameInfo> {
private EnumSet<GenericNameType> _types;
private Collection<GenericMatchInfo> _matches;
private int _maxMatchLen;
GenericNameSearchHandler(EnumSet<GenericNameType> types) {
_types = types;
}
/* (non-Javadoc)
* @see com.ibm.icu.impl.TextTrieMap.ResultHandler#handlePrefixMatch(int, java.util.Iterator)
*/
public boolean handlePrefixMatch(int matchLength, Iterator<NameInfo> values) {
while (values.hasNext()) {
NameInfo info = values.next();
if (_types != null && !_types.contains(info.type)) {
continue;
}
GenericMatchInfo matchInfo = new GenericMatchInfo();
matchInfo.tzID = info.tzID;
matchInfo.nameType = info.type;
matchInfo.matchLength = matchLength;
//matchInfo.timeType = TimeType.UNKNOWN;
if (_matches == null) {
_matches = new LinkedList<GenericMatchInfo>();
}
_matches.add(matchInfo);
if (matchLength > _maxMatchLen) {
_maxMatchLen = matchLength;
}
}
return true;
}
/**
* Returns the match results
* @return the match results
*/
public Collection<GenericMatchInfo> getMatches() {
return _matches;
}
/**
* Returns the maximum match length, or 0 if no match was found
* @return the maximum match length
*/
public int getMaxMatchLen() {
return _maxMatchLen;
}
/**
* Resets the match results
*/
public void resetResults() {
_matches = null;
_maxMatchLen = 0;
}
}
/**
* Returns the best match of time zone display name for the specified types in the
* given text at the given offset.
* @param text the text
* @param start the start offset in the text
* @param genericTypes the set of name types.
* @return the best matching name info.
*/
public GenericMatchInfo findBestMatch(String text, int start, EnumSet<GenericNameType> genericTypes) {
if (text == null || text.length() == 0 || start < 0 || start >= text.length()) {
throw new IllegalArgumentException("bad input text or range");
}
GenericMatchInfo bestMatch = null;
// Find matches in the TimeZoneNames first
Collection<MatchInfo> tznamesMatches = findTimeZoneNames(text, start, genericTypes);
if (tznamesMatches != null) {
MatchInfo longestMatch = null;
for (MatchInfo match : tznamesMatches) {
if (longestMatch == null || match.matchLength() > longestMatch.matchLength()) {
longestMatch = match;
}
}
if (longestMatch != null) {
bestMatch = createGenericMatchInfo(longestMatch);
if (bestMatch.matchLength() == (text.length() - start)) {
// Full match
//return bestMatch;
// TODO Some time zone uses a same name for the long standard name
// and the location name. When the match is a long standard name,
// then we need to check if the name is same with the location name.
// This is probably a data error or a design bug.
if (bestMatch.nameType != GenericNameType.LONG || bestMatch.timeType != TimeType.STANDARD) {
return bestMatch;
}
}
}
}
// Find matches in the local trie
Collection<GenericMatchInfo> localMatches = findLocal(text, start, genericTypes);
if (localMatches != null) {
for (GenericMatchInfo match : localMatches) {
// TODO See the above TODO. We use match.matchLength() >= bestMatch.matcheLength()
// for the reason described above.
//if (bestMatch == null || match.matchLength() > bestMatch.matchLength()) {
if (bestMatch == null || match.matchLength() >= bestMatch.matchLength()) {
bestMatch = match;
}
}
}
return bestMatch;
}
/**
* Returns a collection of time zone display name matches for the specified types in the
* given text at the given offset.
* @param text the text
* @param start the start offset in the text
* @param genericTypes the set of name types.
* @return A collection of match info.
*/
public Collection<GenericMatchInfo> find(String text, int start, EnumSet<GenericNameType> genericTypes) {
if (text == null || text.length() == 0 || start < 0 || start >= text.length()) {
throw new IllegalArgumentException("bad input text or range");
}
// Find matches in the local trie
Collection<GenericMatchInfo> results = findLocal(text, start, genericTypes);
// Also find matches in the TimeZoneNames
Collection<MatchInfo> tznamesMatches = findTimeZoneNames(text, start, genericTypes);
if (tznamesMatches != null) {
// transform matches and append them to local matches
for (MatchInfo match : tznamesMatches) {
if (results == null) {
results = new LinkedList<GenericMatchInfo>();
}
results.add(createGenericMatchInfo(match));
}
}
return results;
}
/**
* Returns a <code>GenericMatchInfo</code> for the given <code>MatchInfo</code>.
* @param matchInfo the MatchInfo
* @return A GenericMatchInfo
*/
private GenericMatchInfo createGenericMatchInfo(MatchInfo matchInfo) {
GenericNameType nameType = null;
TimeType timeType = TimeType.UNKNOWN;
switch (matchInfo.nameType()) {
case LONG_STANDARD:
timeType = TimeType.STANDARD;
//$FALL-THROUGH$
case LONG_GENERIC:
nameType = GenericNameType.LONG;
break;
case SHORT_STANDARD_COMMONLY_USED:
timeType = TimeType.STANDARD;
//$FALL-THROUGH$
case SHORT_GENERIC:
nameType = GenericNameType.SHORT;
break;
}
assert(nameType != null);
String tzID = matchInfo.tzID();
if (tzID == null) {
String mzID = matchInfo.mzID();
assert(mzID != null);
tzID = _tznames.getReferenceZoneID(mzID, getTargetRegion());
}
assert(tzID != null);
GenericMatchInfo gmatch = new GenericMatchInfo();
gmatch.nameType = nameType;
gmatch.tzID = tzID;
gmatch.matchLength = matchInfo.matchLength();
gmatch.timeType = timeType;
return gmatch;
}
/**
* Returns a collection of time zone display name matches for the specified types in the
* given text at the given offset. This method only finds matches from the TimeZoneNames
* used by this object.
* @param text the text
* @param start the start offset in the text
* @param types the set of name types.
* @return A collection of match info.
*/
private Collection<MatchInfo> findTimeZoneNames(String text, int start, EnumSet<GenericNameType> types) {
Collection<MatchInfo> tznamesMatches = null;
// Check if the target name type is really in the TimeZoneNames
EnumSet<NameType> nameTypes = EnumSet.noneOf(NameType.class);
if (types.contains(GenericNameType.LONG)) {
nameTypes.add(NameType.LONG_GENERIC);
nameTypes.add(NameType.LONG_STANDARD);
}
if (types.contains(GenericNameType.SHORT)) {
nameTypes.add(NameType.SHORT_GENERIC);
nameTypes.add(NameType.SHORT_STANDARD_COMMONLY_USED);
}
if (!nameTypes.isEmpty()) {
// Find matches in the TimeZoneNames
tznamesMatches = _tznames.find(text, start, nameTypes);
}
return tznamesMatches;
}
/**
* Returns a collection of time zone display name matches for the specified types in the
* given text at the given offset. This method only finds matches from the local trie,
* that contains 1) generic location names and 2) long/short generic partial location names,
* used by this object.
* @param text the text
* @param start the start offset in the text
* @param types the set of name types.
* @return A collection of match info.
*/
private synchronized Collection<GenericMatchInfo> findLocal(String text, int start, EnumSet<GenericNameType> types) {
GenericNameSearchHandler handler = new GenericNameSearchHandler(types);
_gnamesTrie.find(text, start, handler);
if (handler.getMaxMatchLen() == (text.length() - start) || _gnamesTrieFullyLoaded) {
// perfect match
return handler.getMatches();
}
// All names are not yet loaded into the local trie.
// Load all available names into the trie. This could be very heavy.
Set<String> tzIDs = TimeZone.getAvailableIDs(SystemTimeZoneType.CANONICAL, null, null);
for (String tzID : tzIDs) {
loadStrings(tzID);
}
_gnamesTrieFullyLoaded = true;
// now, try it again
handler.resetResults();
_gnamesTrie.find(text, start, handler);
return handler.getMatches();
}
/**
* <code>TimeZoneGenericNames</code> cache implementation.
*/
private static class Cache extends SoftCache<String, TimeZoneGenericNames, ULocale> {
/* (non-Javadoc)
* @see com.ibm.icu.impl.CacheBase#createInstance(java.lang.Object, java.lang.Object)
*/
@Override
protected TimeZoneGenericNames createInstance(String key, ULocale data) {
return new TimeZoneGenericNames(data).freeze();
}
}
/*
* The custom deserialization method.
* This implementation only read locale used by the object.
*/
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
in.defaultReadObject();
init();
}
/**
* {@inheritDoc}
*/
public boolean isFrozen() {
return _frozen;
}
/**
* {@inheritDoc}
*/
public TimeZoneGenericNames freeze() {
_frozen = true;
return this;
}
/**
* {@inheritDoc}
*/
public TimeZoneGenericNames cloneAsThawed() {
TimeZoneGenericNames copy = null;
try {
copy = (TimeZoneGenericNames)super.clone();
copy._frozen = false;
} catch (Throwable t) {
// This should never happen
}
return copy;
}
}

View File

@ -0,0 +1,26 @@
/*
*******************************************************************************
* Copyright (C) 2011, International Business Machines Corporation and *
* others. All Rights Reserved. *
*******************************************************************************
*/
package com.ibm.icu.impl;
import com.ibm.icu.text.TimeZoneNames;
import com.ibm.icu.text.TimeZoneNames.Factory;
import com.ibm.icu.util.ULocale;
/**
* The implementation class of <code>TimeZoneNames.Factory</code>
*/
public class TimeZoneNamesFactoryImpl extends Factory {
/* (non-Javadoc)
* @see com.ibm.icu.text.TimeZoneNames.Factory#getTimeZoneNames(com.ibm.icu.util.ULocale)
*/
@Override
public TimeZoneNames getTimeZoneNames(ULocale locale) {
return new TimeZoneNamesImpl(locale);
}
}

View File

@ -0,0 +1,728 @@
/*
*******************************************************************************
* Copyright (C) 2011, International Business Machines Corporation and *
* others. All Rights Reserved. *
*******************************************************************************
*/
package com.ibm.icu.impl;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
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.TimeZone;
import com.ibm.icu.util.TimeZone.SystemTimeZoneType;
import com.ibm.icu.util.ULocale;
import com.ibm.icu.util.UResourceBundle;
/**
* The standard ICU implementation of TimeZoneNames
*/
public class TimeZoneNamesImpl extends TimeZoneNames {
private static final long serialVersionUID = -2179814848495897472L;
private static final String ZONE_STRINGS_BUNDLE = "zoneStrings";
private static final String MZ_PREFIX = "meta:";
private static Set<String> METAZONE_IDS;
private static final TZ2MZsCache TZ_TO_MZS_CACHE = new TZ2MZsCache();
private static final MZ2TZsCache MZ_TO_TZS_CACHE = new MZ2TZsCache();
private transient ICUResourceBundle _zoneStrings;
// These are hard cache. We create only one TimeZoneNamesImpl per locale
// and it's stored in SoftCache, so we do not need to worry about the
// footprint much.
private transient ConcurrentHashMap<String, ZNames> _mzNamesMap;
private transient ConcurrentHashMap<String, TZNames> _tzNamesMap;
private transient TextTrieMap<NameInfo> _namesTrie;
private transient boolean _namesTrieFullyLoaded;
public TimeZoneNamesImpl(ULocale locale) {
initialize(locale);
}
/* (non-Javadoc)
* @see com.ibm.icu.text.TimeZoneNames#getAvailableMetaZoneIDs()
*/
@Override
public synchronized Set<String> getAvailableMetaZoneIDs() {
if (METAZONE_IDS == null) {
try {
UResourceBundle bundle = UResourceBundle.getBundleInstance(ICUResourceBundle.ICU_BASE_NAME, "metaZones");
UResourceBundle mapTimezones = bundle.get("mapTimezones");
Set<String> keys = mapTimezones.keySet();
METAZONE_IDS = Collections.unmodifiableSet(keys);
} catch (MissingResourceException e) {
METAZONE_IDS = Collections.emptySet();
}
}
return METAZONE_IDS;
}
/* (non-Javadoc)
* @see com.ibm.icu.text.TimeZoneNames#getAvailableMetaZoneIDs(java.lang.String)
*/
@Override
public Set<String> getAvailableMetaZoneIDs(String tzID) {
if (tzID == null || tzID.length() == 0) {
return Collections.emptySet();
}
List<MZMapEntry> maps = TZ_TO_MZS_CACHE.getInstance(tzID, tzID);
if (maps.isEmpty()) {
return Collections.emptySet();
}
Set<String> mzIDs = new HashSet<String>(maps.size());
for (MZMapEntry map : maps) {
mzIDs.add(map.mzID());
}
// make it unmodifiable because of the API contract. We may cache the results in futre.
return Collections.unmodifiableSet(mzIDs);
}
/* (non-Javadoc)
* @see com.ibm.icu.text.TimeZoneNames#getMetaZoneID(java.lang.String, long)
*/
@Override
public String getMetaZoneID(String tzID, long date) {
if (tzID == null || tzID.length() == 0) {
return null;
}
String mzID = null;
List<MZMapEntry> maps = TZ_TO_MZS_CACHE.getInstance(tzID, tzID);
for (MZMapEntry map : maps) {
if (date >= map.from() && date < map.to()) {
mzID = map.mzID();
break;
}
}
return mzID;
}
/* (non-Javadoc)
* @see com.ibm.icu.text.TimeZoneNames#getReferenceZoneID(java.lang.String, java.lang.String)
*/
@Override
public String getReferenceZoneID(String mzID, String region) {
if (mzID == null || mzID.length() == 0) {
return null;
}
String refID = null;
Map<String, String> regionTzMap = MZ_TO_TZS_CACHE.getInstance(mzID, mzID);
if (!regionTzMap.isEmpty()) {
refID = regionTzMap.get(region);
if (refID == null) {
refID = regionTzMap.get("001");
}
}
return refID;
}
/*
* (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) {
return null;
}
return loadMetaZoneNames(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) {
if (tzID == null || tzID.length() == 0) {
return null;
}
return loadTimeZoneNames(tzID).getName(type);
}
/* (non-Javadoc)
* @see com.ibm.icu.text.TimeZoneNames#getExemplarLocationName(java.lang.String)
*/
@Override
public String getExemplarLocationName(String tzID) {
if (tzID == null || tzID.length() == 0) {
return null;
}
String locName = loadTimeZoneNames(tzID).getLocationName();
if (locName == null) {
locName = super.getExemplarLocationName(tzID);
}
return locName;
}
/* (non-Javadoc)
* @see com.ibm.icu.text.TimeZoneNames#find(java.lang.String, int, java.util.Set)
*/
@Override
public synchronized Collection<MatchInfo> find(String 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");
}
NameSearchHandler handler = new NameSearchHandler(nameTypes);
_namesTrie.find(text, start, handler);
if (handler.getMaxMatchLen() == (text.length() - start) || _namesTrieFullyLoaded) {
// perfect match
return handler.getMatches();
}
// All names are not yet loaded into the trie
// time zone names
Set<String> tzIDs = TimeZone.getAvailableIDs(SystemTimeZoneType.CANONICAL, null, null);
for (String tzID : tzIDs) {
loadTimeZoneNames(tzID);
}
// meta zone names
Set<String> mzIDs = getAvailableMetaZoneIDs();
for (String mzID : mzIDs) {
loadMetaZoneNames(mzID);
}
_namesTrieFullyLoaded = true;
// now, try it again
handler.resetResults();
_namesTrie.find(text, start, handler);
return handler.getMatches();
}
/**
* Initialize the transient fields, called from the constructor and
* readObject.
*
* @param locale The locale
*/
private void initialize(ULocale locale) {
if (locale == null) {
return;
}
try {
ICUResourceBundle bundle = (ICUResourceBundle)ICUResourceBundle.getBundleInstance(
ICUResourceBundle.ICU_ZONE_BASE_NAME, locale);
_zoneStrings = (ICUResourceBundle)bundle.get(ZONE_STRINGS_BUNDLE);
} catch (MissingResourceException mre) {
_zoneStrings = null;
}
_tzNamesMap = new ConcurrentHashMap<String, TZNames>();
_mzNamesMap = new ConcurrentHashMap<String, ZNames>();
_namesTrie = new TextTrieMap<NameInfo>(true);
_namesTrieFullyLoaded = false;
// Preload zone strings for the default time zone
TimeZone tz = TimeZone.getDefault();
String tzCanonicalID = ZoneMeta.getCanonicalCLDRID(tz);
if (tzCanonicalID != null) {
loadStrings(tzCanonicalID);
}
}
/**
* Load all strings used by the specified time zone.
* This is called from the initializer to load default zone's
* strings.
* @param tzCanonicalID the canonical time zone ID
*/
private synchronized void loadStrings(String tzCanonicalID) {
if (tzCanonicalID == null || tzCanonicalID.length() == 0) {
return;
}
loadTimeZoneNames(tzCanonicalID);
Set<String> mzIDs = getAvailableMetaZoneIDs(tzCanonicalID);
for (String mzID : mzIDs) {
loadMetaZoneNames(mzID);
}
}
/*
* The custom serialization method.
* This implementation only preserve locale used for the names.
*/
private void writeObject(ObjectOutputStream out) throws IOException {
ULocale locale = _zoneStrings == null ? null : _zoneStrings.getULocale();
out.writeObject(locale);
}
/*
* The custom deserialization method.
* This implementation only read locale used by the object.
*/
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
ULocale locale = (ULocale)in.readObject();
initialize(locale);
}
/**
* Returns a set of names for the given meta zone ID. This method loads
* the set of names into the internal map and trie for future references.
* @param mzID the meta zone ID
* @return An instance of ZNames that includes a set of meta zone display names.
*/
private synchronized ZNames loadMetaZoneNames(String mzID) {
ZNames znames = _mzNamesMap.get(mzID);
if (znames == null) {
znames = ZNames.getInstance(_zoneStrings, MZ_PREFIX + mzID);
// put names into the trie
mzID = mzID.intern();
for (NameType t : NameType.values()) {
String name = znames.getName(t);
if (name != null) {
NameInfo info = new NameInfo();
info.mzID = mzID;
info.type = t;
_namesTrie.put(name, info);
}
}
_mzNamesMap.put(mzID, znames);
}
return znames;
}
/**
* Returns a set of names for the given time zone ID. This method loads
* the set of names into the internal map and trie for future references.
* @param tzID the canonical time zone ID
* @return An instance of TZNames that includes a set of time zone display names.
*/
private synchronized TZNames loadTimeZoneNames(String tzID) {
TZNames tznames = _tzNamesMap.get(tzID);
if (tznames == null) {
tznames = TZNames.getInstance(_zoneStrings, tzID.replace('/', ':'));
// put names into the trie
tzID = tzID.intern();
for (NameType t : NameType.values()) {
String name = tznames.getName(t);
if (name != null) {
NameInfo info = new NameInfo();
info.tzID = tzID;
info.type = t;
_namesTrie.put(name, info);
}
}
_tzNamesMap.put(tzID, tznames);
}
return tznames;
}
/**
* An instance of NameInfo is stored in the zone names trie.
*/
private static class NameInfo {
String tzID;
String mzID;
NameType type;
}
/**
* NameSearchHandler is used for collecting name matches.
*/
private static class NameSearchHandler implements ResultHandler<NameInfo> {
private EnumSet<NameType> _nameTypes;
private Collection<MatchInfo> _matches;
private int _maxMatchLen;
NameSearchHandler(EnumSet<NameType> nameTypes) {
_nameTypes = nameTypes;
}
/* (non-Javadoc)
* @see com.ibm.icu.impl.TextTrieMap.ResultHandler#handlePrefixMatch(int, java.util.Iterator)
*/
public boolean handlePrefixMatch(int matchLength, Iterator<NameInfo> values) {
while (values.hasNext()) {
NameInfo ninfo = values.next();
if (_nameTypes != null && !_nameTypes.contains(ninfo.type)) {
continue;
}
MatchInfo minfo;
if (ninfo.tzID != null) {
minfo = new MatchInfo(ninfo.type, ninfo.tzID, null, matchLength);
} else {
assert(ninfo.mzID != null);
minfo = new MatchInfo(ninfo.type, null, ninfo.mzID, matchLength);
}
if (_matches == null) {
_matches = new LinkedList<MatchInfo>();
}
_matches.add(minfo);
if (matchLength > _maxMatchLen) {
_maxMatchLen = matchLength;
}
}
return true;
}
/**
* Returns the match results
* @return the match results
*/
public Collection<MatchInfo> getMatches() {
if (_matches == null) {
return Collections.emptyList();
}
return _matches;
}
/**
* Returns the maximum match length, or 0 if no match was found
* @return the maximum match length
*/
public int getMaxMatchLen() {
return _maxMatchLen;
}
/**
* Resets the match results
*/
public void resetResults() {
_matches = null;
_maxMatchLen = 0;
}
}
/**
* This class stores name data for a meta zone
*/
private static class ZNames {
private static final ZNames EMPTY_ZNAMES = new ZNames(null, false);
private String[] _names;
private boolean _shortCommonlyUsed;
private static final String[] KEYS = {"lg", "ls", "ld", "sg", "ss", "sd"};
protected ZNames(String[] names, boolean shortCommonlyUsed) {
_names = names;
_shortCommonlyUsed = shortCommonlyUsed;
}
public static ZNames getInstance(ICUResourceBundle zoneStrings, String key) {
boolean[] cu = new boolean[1];
String[] names = loadData(zoneStrings, key, cu);
if (names == null) {
return EMPTY_ZNAMES;
}
return new ZNames(names, cu[0]);
}
public String getName(NameType type) {
if (_names == null) {
return null;
}
String name = null;
switch (type) {
case LONG_GENERIC:
name = _names[0];
break;
case LONG_STANDARD:
name = _names[1];
break;
case LONG_DAYLIGHT:
name = _names[2];
break;
case SHORT_GENERIC:
if (_shortCommonlyUsed) {
name = _names[3];
}
break;
case SHORT_STANDARD:
name = _names[4];
break;
case SHORT_DAYLIGHT:
name = _names[5];
break;
case SHORT_STANDARD_COMMONLY_USED:
if (_shortCommonlyUsed) {
name = _names[4];
}
break;
case SHORT_DAYLIGHT_COMMONLY_USED:
if (_shortCommonlyUsed) {
name = _names[5];
}
break;
}
return name;
}
protected static String[] loadData(ICUResourceBundle zoneStrings, String key, boolean[] shortCommonlyUsed) {
if (zoneStrings == null || key == null || key.length() == 0) {
return null;
}
shortCommonlyUsed[0] = false;
ICUResourceBundle table = null;
try {
table = zoneStrings.getWithFallback(key);
} catch (MissingResourceException e) {
return null;
}
boolean isEmpty = true;
String[] names = new String[KEYS.length];
for (int i = 0; i < names.length; i++) {
try {
names[i] = table.getStringWithFallback(KEYS[i]);
isEmpty = false;
} catch (MissingResourceException e) {
names[i] = null;
}
}
if (isEmpty) {
return null;
}
try {
ICUResourceBundle cuRes = table.getWithFallback("cu");
int cu = cuRes.getInt();
shortCommonlyUsed[0] = (cu != 0);
} catch (MissingResourceException e) {
// cu is optional
}
return names;
}
}
/**
* This class stores name data for a single time zone
*/
private static class TZNames extends ZNames {
private String _locationName;
private static final TZNames EMPTY_TZNAMES = new TZNames(null, false, null);
public static TZNames getInstance(ICUResourceBundle zoneStrings, String key) {
if (zoneStrings == null || key == null || key.length() == 0) {
return EMPTY_TZNAMES;
}
ICUResourceBundle table = null;
try {
table = zoneStrings.getWithFallback(key);
} catch (MissingResourceException e) {
return EMPTY_TZNAMES;
}
String locationName = null;
try {
locationName = table.getStringWithFallback("ec");
} catch (MissingResourceException e) {
// location name is optional
}
boolean[] cu = new boolean[1];
String[] names = loadData(zoneStrings, key, cu);
if (locationName == null && names == null) {
return EMPTY_TZNAMES;
}
return new TZNames(names, cu[0], locationName);
}
public String getLocationName() {
return _locationName;
}
private TZNames(String[] names, boolean shortCommonlyUsed, String locationName) {
super(names, shortCommonlyUsed);
_locationName = locationName;
}
}
//
// Canonical time zone ID -> meta zone ID
//
private static class MZMapEntry {
private String _mzID;
private long _from;
private long _to;
MZMapEntry(String mzID, long from, long to) {
_mzID = mzID;
_from = from;
_to = to;
}
String mzID() {
return _mzID;
}
long from() {
return _from;
}
long to() {
return _to;
}
}
private static class TZ2MZsCache extends SoftCache<String, List<MZMapEntry>, String> {
/* (non-Javadoc)
* @see com.ibm.icu.impl.CacheBase#createInstance(java.lang.Object, java.lang.Object)
*/
@Override
protected List<MZMapEntry> createInstance(String key, String data) {
List<MZMapEntry> mzMaps = null;
try {
UResourceBundle bundle = UResourceBundle.getBundleInstance(ICUResourceBundle.ICU_BASE_NAME, "metaZones");
UResourceBundle metazoneInfoBundle = bundle.get("metazoneInfo");
String tzkey = data.replace('/', ':');
UResourceBundle zoneBundle = metazoneInfoBundle.get(tzkey);
mzMaps = new ArrayList<MZMapEntry>(zoneBundle.getSize());
for (int idx = 0; idx < zoneBundle.getSize(); idx++) {
UResourceBundle mz = zoneBundle.get(idx);
String mzid = mz.getString(0);
String fromStr = "1970-01-01 00:00";
String toStr = "9999-12-31 23:59";
if (mz.getSize() == 3) {
fromStr = mz.getString(1);
toStr = mz.getString(2);
}
long from, to;
from = parseDate(fromStr);
to = parseDate(toStr);
mzMaps.add(new MZMapEntry(mzid, from, to));
}
} catch (MissingResourceException mre) {
// fall through
}
if (mzMaps == null) {
mzMaps = Collections.emptyList();
}
return mzMaps;
}
/**
* Private static method parsing the date text used by meta zone to
* time zone mapping data in locale resource.
*
* @param text the UTC date text in the format of "yyyy-MM-dd HH:mm",
* for example - "1970-01-01 00:00"
* @return the date
*/
private static long parseDate (String text) {
int year = 0, month = 0, day = 0, hour = 0, min = 0;
int idx;
int n;
// "yyyy" (0 - 3)
for (idx = 0; idx <= 3; idx++) {
n = text.charAt(idx) - '0';
if (n >= 0 && n < 10) {
year = 10*year + n;
} else {
throw new IllegalArgumentException("Bad year");
}
}
// "MM" (5 - 6)
for (idx = 5; idx <= 6; idx++) {
n = text.charAt(idx) - '0';
if (n >= 0 && n < 10) {
month = 10*month + n;
} else {
throw new IllegalArgumentException("Bad month");
}
}
// "dd" (8 - 9)
for (idx = 8; idx <= 9; idx++) {
n = text.charAt(idx) - '0';
if (n >= 0 && n < 10) {
day = 10*day + n;
} else {
throw new IllegalArgumentException("Bad day");
}
}
// "HH" (11 - 12)
for (idx = 11; idx <= 12; idx++) {
n = text.charAt(idx) - '0';
if (n >= 0 && n < 10) {
hour = 10*hour + n;
} else {
throw new IllegalArgumentException("Bad hour");
}
}
// "mm" (14 - 15)
for (idx = 14; idx <= 15; idx++) {
n = text.charAt(idx) - '0';
if (n >= 0 && n < 10) {
min = 10*min + n;
} else {
throw new IllegalArgumentException("Bad minute");
}
}
long date = Grego.fieldsToDay(year, month - 1, day) * Grego.MILLIS_PER_DAY
+ hour * Grego.MILLIS_PER_HOUR + min * Grego.MILLIS_PER_MINUTE;
return date;
}
}
//
// Meta zone ID -> time zone ID
//
private static class MZ2TZsCache extends SoftCache<String, Map<String, String>, String> {
/* (non-Javadoc)
* @see com.ibm.icu.impl.CacheBase#createInstance(java.lang.Object, java.lang.Object)
*/
@Override
protected Map<String, String> createInstance(String key, String data) {
Map<String, String> map = null;
try {
UResourceBundle bundle = UResourceBundle.getBundleInstance(ICUResourceBundle.ICU_BASE_NAME, "metaZones");
UResourceBundle mapTimezones = bundle.get("mapTimezones");
UResourceBundle regionMap = mapTimezones.get(key);
Set<String> regions = regionMap.keySet();
map = new HashMap<String, String>(regions.size());
for (String region : regions) {
String tzID = regionMap.getString(region).intern();
map.put(region.intern(), tzID);
}
} catch (MissingResourceException e) {
// fall through
}
if (map == null) {
map = Collections.emptyMap();
}
return map;
}
}
}

View File

@ -13,20 +13,15 @@ package com.ibm.icu.impl;
import java.lang.ref.SoftReference;
import java.text.ParsePosition;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Locale;
import java.util.MissingResourceException;
import java.util.Set;
import java.util.TreeSet;
import com.ibm.icu.text.MessageFormat;
import com.ibm.icu.text.NumberFormat;
import com.ibm.icu.util.SimpleTimeZone;
import com.ibm.icu.util.TimeZone;
import com.ibm.icu.util.TimeZone.SystemTimeZoneType;
import com.ibm.icu.util.ULocale;
import com.ibm.icu.util.UResourceBundle;
/**
@ -179,12 +174,16 @@ public final class ZoneMeta {
return baseSet;
}
if (region != null) {
region = region.toUpperCase(Locale.US);
}
// Filter by region/rawOffset
Set<String> result = new TreeSet<String>();
for (String id : baseSet) {
if (region != null) {
String r = getRegion(id);
if (!region.equalsIgnoreCase(r)) {
if (!region.equals(r)) {
continue;
}
}
@ -336,11 +335,17 @@ public final class ZoneMeta {
return zoneIdx;
}
private static ICUCache<String, String> CANONICAL_ID_CACHE = new SimpleCache<String, String>();
private static ICUCache<String, String> REGION_CACHE = new SimpleCache<String, String>();
private static ICUCache<String, Boolean> SINGLE_COUNTRY_CACHE = new SimpleCache<String, Boolean>();
public static String getCanonicalCLDRID(TimeZone tz) {
if (tz instanceof OlsonTimeZone) {
return ((OlsonTimeZone)tz).getCanonicalID();
}
return getCanonicalCLDRID(tz.getID());
}
/**
* Return the canonical id for this tzid defined by CLDR, which might be
* the id itself. If the given tzid is not known, return null.
@ -449,101 +454,6 @@ public final class ZoneMeta {
return country;
}
/**
* Returns a time zone location(region) format string defined by UTR#35.
* e.g. "Italy Time", "United States (Los Angeles) Time"
*/
public static String getLocationFormat(String tzid, String city, ULocale locale) {
String country_code = getCanonicalCountry(tzid);
if (country_code == null) {
// no location is associated
return null;
}
String country = null;
try {
ICUResourceBundle rb =
(ICUResourceBundle)UResourceBundle.getBundleInstance(ICUResourceBundle.ICU_REGION_BASE_NAME, locale);
//
// TODO: There is a design bug in UResourceBundle and getLoadingStatus() does not work well.
//
// if (rb.getLoadingStatus() != ICUResourceBundle.FROM_ROOT && rb.getLoadingStatus() != ICUResourceBundle.FROM_DEFAULT) {
// country = ULocale.getDisplayCountry("xx_" + country_code, locale);
// }
// START WORKAROUND
ULocale rbloc = rb.getULocale();
if (!rbloc.equals(ULocale.ROOT) && rbloc.getLanguage().equals(locale.getLanguage())) {
country = ULocale.getDisplayCountry("xx_" + country_code, locale);
}
// END WORKAROUND
} catch (MissingResourceException e) {
// fall through
}
if (country == null || country.length() == 0) {
country = country_code;
}
// This is not behavior specified in tr35, but behavior added by Mark.
// TR35 says to display the country _only_ if there is a localization.
if (getSingleCountry(tzid) != null) { // single country
String regPat = getTZLocalizationInfo(locale, REGION_FORMAT);
if (regPat == null) {
regPat = DEF_REGION_FORMAT;
}
MessageFormat mf = new MessageFormat(regPat);
return mf.format(new Object[] { country });
}
if (city == null) {
city = tzid.substring(tzid.lastIndexOf('/')+1).replace('_',' ');
}
String flbPat = getTZLocalizationInfo(locale, FALLBACK_FORMAT);
if (flbPat == null) {
flbPat = DEF_FALLBACK_FORMAT;
}
MessageFormat mf = new MessageFormat(flbPat);
return mf.format(new Object[] { city, country });
}
private static final String DEF_REGION_FORMAT = "{0}";
private static final String DEF_FALLBACK_FORMAT = "{1} ({0})";
public static final String
HOUR = "hourFormat",
GMT = "gmtFormat",
REGION_FORMAT = "regionFormat",
FALLBACK_FORMAT = "fallbackFormat",
ZONE_STRINGS = "zoneStrings",
FORWARD_SLASH = "/";
/**
* Get the index'd tz datum for this locale. Index must be one of the
* values PREFIX, HOUR, GMT, REGION_FORMAT, FALLBACK_FORMAT
*/
public static String getTZLocalizationInfo(ULocale locale, String format) {
String result = null;
try {
ICUResourceBundle bundle = (ICUResourceBundle) ICUResourceBundle.getBundleInstance(
ICUResourceBundle.ICU_ZONE_BASE_NAME, locale);
result = bundle.getStringWithFallback(ZONE_STRINGS+FORWARD_SLASH+format);
} catch (MissingResourceException e) {
result = null;
}
return result;
}
// private static Set getValidIDs() {
// // Construct list of time zones that are valid, according
// // to the current underlying core JDK. We have to do this
// // at runtime since we don't know what we're running on.
// Set valid = new TreeSet();
// valid.addAll(Arrays.asList(java.util.TimeZone.getAvailableIDs()));
// return valid;
// }
/**
* Given an ID and the top-level resource of the zoneinfo resource,
* open the appropriate resource for the given time zone.
@ -827,182 +737,4 @@ public final class ZoneMeta {
}
return zid.toString();
}
/**
* Returns a CLDR metazone ID for the given Olson tzid and time.
*/
public static String getMetazoneID(String olsonID, long date) {
String mzid = null;
List<OlsonToMetaMappingEntry> mappings = getOlsonToMatazones(olsonID);
if (mappings != null) {
for (int i = 0; i < mappings.size(); i++) {
OlsonToMetaMappingEntry mzm = mappings.get(i);
if (date >= mzm.from && date < mzm.to) {
mzid = mzm.mzid;
break;
}
}
}
return mzid;
}
private static ICUCache<String, List<OlsonToMetaMappingEntry>> OLSON_TO_META_CACHE =
new SimpleCache<String, List<OlsonToMetaMappingEntry>>();
static class OlsonToMetaMappingEntry {
String mzid;
long from;
long to;
}
static List<OlsonToMetaMappingEntry> getOlsonToMatazones(String tzid) {
List<OlsonToMetaMappingEntry> mzMappings = OLSON_TO_META_CACHE.get(tzid);
if (mzMappings == null) {
try {
UResourceBundle bundle = UResourceBundle.getBundleInstance(ICUResourceBundle.ICU_BASE_NAME, "metaZones");
UResourceBundle metazoneInfoBundle = bundle.get("metazoneInfo");
String canonicalID = getCanonicalCLDRID(tzid);
if (canonicalID == null) {
return null;
}
String tzkey = canonicalID.replace('/', ':');
UResourceBundle zoneBundle = metazoneInfoBundle.get(tzkey);
mzMappings = new LinkedList<OlsonToMetaMappingEntry>();
for (int idx = 0; idx < zoneBundle.getSize(); idx++) {
UResourceBundle mz = zoneBundle.get(idx);
String mzid = mz.getString(0);
String from = "1970-01-01 00:00";
String to = "9999-12-31 23:59";
if (mz.getSize() == 3) {
from = mz.getString(1);
to = mz.getString(2);
}
OlsonToMetaMappingEntry mzmap = new OlsonToMetaMappingEntry();
mzmap.mzid = mzid.intern();
try {
mzmap.from = parseDate(from);
mzmap.to = parseDate(to);
} catch (IllegalArgumentException baddate) {
// skip this
continue;
}
// Add this mapping to the list
mzMappings.add(mzmap);
}
} catch (MissingResourceException mre) {
// fall through
}
if (mzMappings != null) {
OLSON_TO_META_CACHE.put(tzid, mzMappings);
}
}
return mzMappings;
}
/*
* Convert a date string used by metazone mappings to long.
* The format used by CLDR metazone mapping is "yyyy-MM-dd HH:mm".
* We do not want to use SimpleDateFormat to parse the metazone
* mapping range strings in createOlsonToMeta, because it might be
* called from SimpleDateFormat initialization code.
*/
static long parseDate (String text) throws IllegalArgumentException {
int year = 0, month = 0, day = 0, hour = 0, min = 0;
int idx;
int n;
// "yyyy" (0 - 3)
for (idx = 0; idx <= 3; idx++) {
n = text.charAt(idx) - '0';
if (n >= 0 && n < 10) {
year = 10*year + n;
} else {
throw new IllegalArgumentException("Bad year");
}
}
// "MM" (5 - 6)
for (idx = 5; idx <= 6; idx++) {
n = text.charAt(idx) - '0';
if (n >= 0 && n < 10) {
month = 10*month + n;
} else {
throw new IllegalArgumentException("Bad month");
}
}
// "dd" (8 - 9)
for (idx = 8; idx <= 9; idx++) {
n = text.charAt(idx) - '0';
if (n >= 0 && n < 10) {
day = 10*day + n;
} else {
throw new IllegalArgumentException("Bad day");
}
}
// "HH" (11 - 12)
for (idx = 11; idx <= 12; idx++) {
n = text.charAt(idx) - '0';
if (n >= 0 && n < 10) {
hour = 10*hour + n;
} else {
throw new IllegalArgumentException("Bad hour");
}
}
// "mm" (14 - 15)
for (idx = 14; idx <= 15; idx++) {
n = text.charAt(idx) - '0';
if (n >= 0 && n < 10) {
min = 10*min + n;
} else {
throw new IllegalArgumentException("Bad minute");
}
}
long date = Grego.fieldsToDay(year, month - 1, day) * Grego.MILLIS_PER_DAY
+ hour * Grego.MILLIS_PER_HOUR + min * Grego.MILLIS_PER_MINUTE;
return date;
}
private static ICUCache<String, Map<String, String>> META_TO_OLSON_CACHE =
new SimpleCache<String, Map<String, String>>();
/**
* Returns an Olson ID for the ginve metazone and region
*/
public static String getZoneIdByMetazone(String metazoneID, String region) {
String tzid = null;
// look up in the cache first
Map<String, String> zoneMap = META_TO_OLSON_CACHE.get(metazoneID);
if (zoneMap == null) {
try {
// Create zone mappings for the metazone
UResourceBundle bundle = UResourceBundle.getBundleInstance(ICUResourceBundle.ICU_BASE_NAME, "metaZones");
UResourceBundle mapTimezones = bundle.get("mapTimezones");
UResourceBundle territoryMap = mapTimezones.get(metazoneID);
zoneMap = new HashMap<String, String>();
Set<String> territories = territoryMap.keySet();
for (String territory : territories) {
String zone = territoryMap.getString(territory);
zoneMap.put(territory, zone);
}
// cache this
META_TO_OLSON_CACHE.put(metazoneID, zoneMap);
} catch (MissingResourceException e) {
// ignore
}
}
if (zoneMap != null) {
tzid = zoneMap.get(region);
if (tzid == null) {
tzid = zoneMap.get(kWorld); // use the mapping for world as fallback
}
}
return tzid;
}
}

View File

@ -19,11 +19,10 @@ import com.ibm.icu.impl.CalendarUtil;
import com.ibm.icu.impl.ICUCache;
import com.ibm.icu.impl.ICUResourceBundle;
import com.ibm.icu.impl.SimpleCache;
import com.ibm.icu.impl.TimeZoneFormat;
import com.ibm.icu.impl.Utility;
import com.ibm.icu.impl.ZoneMeta;
import com.ibm.icu.impl.ZoneStringFormat;
import com.ibm.icu.text.TimeZoneNames.NameType;
import com.ibm.icu.util.Calendar;
import com.ibm.icu.util.TimeZone;
import com.ibm.icu.util.ULocale;
import com.ibm.icu.util.UResourceBundle;
@ -409,23 +408,6 @@ public class DateFormatSymbols implements Serializable, Cloneable {
*/
String standaloneQuarters[] = null;
/**
* Pattern string used for localized time zone GMT format. For example, "GMT{0}"
* @serial
*/
String gmtFormat = null;
/**
* Pattern strings used for formatting zone offset in a localized time zone GMT string.
* This is 2x2 String array holding followings
* [0][0] Negative H + m + s
* [0][1] Negative H + m
* [1][0] Positive H + m + s
* [1][1] Positive H + m
* @serial
*/
String gmtHourFormats[][] = null;
/**
* Localized names of time zones in this locale. This is a
* two-dimensional array of strings of size <em>n</em> by <em>m</em>,
@ -461,14 +443,6 @@ public class DateFormatSymbols implements Serializable, Cloneable {
*/
private String zoneStrings[][] = null;
/**
* Since ICU 3.8.1, we use ZoneStringFormat to access localized
* zone names. This field remains null unless setZoneStrings is
* called.
*/
private transient ZoneStringFormat zsformat = null;
private transient TimeZoneFormat tzformat = null;
/**
* Unlocalized date-time pattern characters. For example: 'y', 'd', etc.
* All locales use the same unlocalized pattern characters.
@ -864,28 +838,74 @@ public class DateFormatSymbols implements Serializable, Cloneable {
}
/**
* Returns timezone strings.
* @return the timezone strings.
* Returns time zone strings.
* <p>
* The array returned by this API is a two dimensional String array and
* each row contains at least following strings:
* <ul>
* <li>ZoneStrings[n][0] - System time zone ID
* <li>ZoneStrings[n][1] - Long standard time display name
* <li>ZoneStrings[n][2] - Short standard time display name
* <li>ZoneStrings[n][3] - Long daylight saving time display name
* <li>ZoneStrings[n][4] - Short daylight saving time display name
* </ul>
* When a localized display name is not available, the corresponding
* array element will be <code>null</code>.
* <p>
* <b>Note</b>: ICU implements time zone display name formatting algorithm
* specified by <a href="http://www.unicode.org/reports/tr35/">UTS#35 Unicode
* Locale Data Markup Language(LDML)</a>. The algorithm supports historic
* display name changes and various different type of names not available in
* JDK. For accessing the full set of time zone string data used by ICU implementation,
* you should use {@link TimeZoneNames} APIs instead.
*
* @return the time zone strings.
* @stable ICU 2.0
*/
public String[][] getZoneStrings() {
if (zoneStrings != null) {
return duplicate(zoneStrings);
}
return ZoneStringFormat.getInstance(requestedLocale).getZoneStrings();
String[] tzIDs = TimeZone.getAvailableIDs();
TimeZoneNames tznames = TimeZoneNames.getInstance(validLocale);
long now = System.currentTimeMillis();
String[][] array = new String[tzIDs.length][5];
for (int i = 0; i < tzIDs.length; i++) {
String canonicalID = TimeZone.getCanonicalID(tzIDs[i]);
if (canonicalID == null) {
canonicalID = tzIDs[i];
}
array[i][0] = tzIDs[i];
array[i][1] = tznames.getDisplayName(canonicalID, NameType.LONG_STANDARD, now);
array[i][2] = tznames.getDisplayName(canonicalID, NameType.SHORT_STANDARD, now);
array[i][3] = tznames.getDisplayName(canonicalID, NameType.LONG_DAYLIGHT, now);
array[i][4] = tznames.getDisplayName(canonicalID, NameType.SHORT_DAYLIGHT, now);
}
zoneStrings = array;
return zoneStrings;
}
/**
* Sets timezone strings.
* @param newZoneStrings the new timezone strings.
* Sets time zone strings.
* <p>
* <b>Note</b>: {@link SimpleDateFormat} no longer uses the
* zone strings stored in a <code>DateFormatSymbols</code>.
* Therefore, the time zone strings set by this method have
* no effects in an instance of <code>SimpleDateFormat</code>
* for formatting time zones. If you want to customize time
* zone display names formatted by <code>SimpleDateFormat</code>,
* you should customize {@link TimeZoneFormat} and set the
* instance by {@link SimpleDateFormat#setTimeZoneFormat(TimeZoneFormat)}
* instead.
*
* @param newZoneStrings the new time zone strings.
* @stable ICU 2.0
*/
public void setZoneStrings(String[][] newZoneStrings) {
zoneStrings = duplicate(newZoneStrings);
if ( tzformat == null ) {
tzformat = TimeZoneFormat.createInstance(requestedLocale);
}
tzformat.zsf = new ZoneStringFormat(zoneStrings);
}
/**
@ -961,8 +981,6 @@ public class DateFormatSymbols implements Serializable, Cloneable {
&& Utility.arrayEquals(standaloneShortWeekdays, that.standaloneShortWeekdays)
&& Utility.arrayEquals(standaloneNarrowWeekdays, that.standaloneNarrowWeekdays)
&& Utility.arrayEquals(ampms, that.ampms)
&& gmtFormat.equals(that.gmtFormat)
&& arrayOfArrayEquals(gmtHourFormats, that.gmtHourFormats)
&& arrayOfArrayEquals(zoneStrings, that.zoneStrings)
// getDiplayName maps deprecated country and language codes to the current ones
// too bad there is no way to get the current codes!
@ -1033,9 +1051,6 @@ public class DateFormatSymbols implements Serializable, Cloneable {
this.standaloneShortQuarters = dfs.standaloneShortQuarters;
this.standaloneQuarters = dfs.standaloneQuarters;
this.gmtFormat = dfs.gmtFormat;
this.gmtHourFormats = dfs.gmtHourFormats;
this.zoneStrings = dfs.zoneStrings; // always null at initialization time for now
this.localPatternChars = dfs.localPatternChars;
@ -1123,9 +1138,6 @@ public class DateFormatSymbols implements Serializable, Cloneable {
standaloneQuarters = calData.getStringArray("quarters", "stand-alone", "wide");
standaloneShortQuarters = calData.getStringArray("quarters", "stand-alone", "abbreviated");
// Initialize localized GMT format patterns
initializeGMTFormat(desiredLocale);
requestedLocale = desiredLocale;
ICUResourceBundle rb =
@ -1144,60 +1156,6 @@ public class DateFormatSymbols implements Serializable, Cloneable {
setLocale(uloc, uloc);
}
static final String DEFAULT_GMT_PATTERN = "GMT{0}";
static final String[][] DEFAULT_GMT_HOUR_PATTERNS = {
{"-HH:mm:ss", "-HH:mm"},
{"+HH:mm:ss", "+HH:mm"}
};
/**
* Initializes localized GMT format patterns
*/
private void initializeGMTFormat(ULocale desiredLocale) {
// TimeZone format localization is not included in CalendarData
gmtFormat = ZoneMeta.getTZLocalizationInfo(desiredLocale, ZoneMeta.GMT);
if (gmtFormat == null) {
gmtFormat = DEFAULT_GMT_PATTERN;
}
try {
String offsetHM = ZoneMeta.getTZLocalizationInfo(desiredLocale, ZoneMeta.HOUR);
gmtHourFormats = new String[2][2];
int sepIdx = offsetHM.indexOf(';');
if (sepIdx != -1) {
gmtHourFormats[OFFSET_POSITIVE][OFFSET_HM] = offsetHM.substring(0, sepIdx);
gmtHourFormats[OFFSET_NEGATIVE][OFFSET_HM] = offsetHM.substring(sepIdx + 1);
} else {
gmtHourFormats[OFFSET_POSITIVE][OFFSET_HM] = "+HH:mm";
gmtHourFormats[OFFSET_NEGATIVE][OFFSET_HM] = "-HH:mm";
}
// CLDR 1.5 does not have GMT offset pattern including second field.
// For now, append "ss" to the end.
if (gmtHourFormats[OFFSET_POSITIVE][OFFSET_HM].indexOf(':') != -1) {
gmtHourFormats[OFFSET_POSITIVE][OFFSET_HMS] =
gmtHourFormats[OFFSET_POSITIVE][OFFSET_HM] + ":ss";
} else if (gmtHourFormats[OFFSET_POSITIVE][OFFSET_HM].indexOf('.') != -1) {
gmtHourFormats[OFFSET_POSITIVE][OFFSET_HMS] =
gmtHourFormats[OFFSET_POSITIVE][OFFSET_HM] + ".ss";
} else {
gmtHourFormats[OFFSET_POSITIVE][OFFSET_HMS] =
gmtHourFormats[OFFSET_POSITIVE][OFFSET_HM] + "ss";
}
if (gmtHourFormats[OFFSET_NEGATIVE][OFFSET_HM].indexOf(':') != -1) {
gmtHourFormats[OFFSET_NEGATIVE][OFFSET_HMS] =
gmtHourFormats[OFFSET_NEGATIVE][OFFSET_HM] + ":ss";
} else if (gmtHourFormats[OFFSET_NEGATIVE][OFFSET_HM].indexOf('.') != -1) {
gmtHourFormats[OFFSET_NEGATIVE][OFFSET_HMS] =
gmtHourFormats[OFFSET_NEGATIVE][OFFSET_HM] + ".ss";
} else {
gmtHourFormats[OFFSET_NEGATIVE][OFFSET_HMS] =
gmtHourFormats[OFFSET_NEGATIVE][OFFSET_HM] + "ss";
}
} catch (MissingResourceException e) {
gmtHourFormats = DEFAULT_GMT_HOUR_PATTERNS;
}
}
private static final boolean arrayOfArrayEquals(Object[][] aa1, Object[][]aa2) {
if (aa1 == aa2) { // both are null
return true;
@ -1218,57 +1176,6 @@ public class DateFormatSymbols implements Serializable, Cloneable {
return equal;
}
/**
* Package local method (for now) to get localized GMT format pattern.
*/
String getGmtFormat() {
return gmtFormat;
}
static final int OFFSET_HMS = 0;
static final int OFFSET_HM = 1;
static final int OFFSET_NEGATIVE = 0;
static final int OFFSET_POSITIVE = 1;
/*
* Package local method (for now) to get hour format pattern used by localized
* GMT string.
*/
String getGmtHourFormat(int sign, int width) {
return gmtHourFormats[sign][width];
}
/*
* Package local method to access ZoneStringFormat used by this
* DateFormatSymbols instance.
*/
ZoneStringFormat getZoneStringFormat() {
if (zsformat != null) {
return zsformat;
}
if (zoneStrings != null) {
zsformat = new ZoneStringFormat(zoneStrings);
return zsformat;
}
// We do not want to hold the reference to an instance of
// ZoneStringFormat. An instance of ZoneStringFormat for
// a locale is shared and cached in ZoneStringFormat class
// itself.
return ZoneStringFormat.getInstance(requestedLocale);
}
TimeZoneFormat getTimeZoneFormat() {
if (tzformat != null) {
return tzformat;
}
tzformat = TimeZoneFormat.createInstance(requestedLocale);
if (zoneStrings != null) {
tzformat.zsf = new ZoneStringFormat(zoneStrings);
}
return tzformat;
}
/*
* save the input locale
*/
@ -1638,8 +1545,5 @@ public class DateFormatSymbols implements Serializable, Cloneable {
*/
private void readObject(ObjectInputStream stream) throws IOException, ClassNotFoundException {
stream.defaultReadObject();
if (gmtFormat == null) {
initializeGMTFormat(requestedLocale);
}
}
}

View File

@ -10,7 +10,6 @@ package com.ibm.icu.text;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.ref.WeakReference;
import java.text.AttributedCharacterIterator;
import java.text.AttributedString;
import java.text.FieldPosition;
@ -28,13 +27,13 @@ import com.ibm.icu.impl.DateNumberFormat;
import com.ibm.icu.impl.ICUCache;
import com.ibm.icu.impl.PatternProps;
import com.ibm.icu.impl.SimpleCache;
import com.ibm.icu.impl.ZoneMeta;
import com.ibm.icu.impl.ZoneStringFormat.ZoneStringInfo;
import com.ibm.icu.lang.UCharacter;
import com.ibm.icu.text.TimeZoneFormat.Style;
import com.ibm.icu.text.TimeZoneFormat.TimeType;
import com.ibm.icu.util.BasicTimeZone;
import com.ibm.icu.util.Calendar;
import com.ibm.icu.util.GregorianCalendar;
import com.ibm.icu.util.HebrewCalendar;
import com.ibm.icu.util.Output;
import com.ibm.icu.util.TimeZone;
import com.ibm.icu.util.TimeZoneTransition;
import com.ibm.icu.util.ULocale;
@ -346,8 +345,6 @@ public class SimpleDateFormat extends DateFormat {
private transient int tztype = TZTYPE_UNK;
private static final int millisPerHour = 60 * 60 * 1000;
private static final int millisPerMinute = 60 * 1000;
private static final int millisPerSecond = 1000;
// When possessing ISO format, the ERA may be ommitted is the
// year specifier is a negative number.
@ -364,6 +361,11 @@ public class SimpleDateFormat extends DateFormat {
*/
private transient boolean useFastFormat;
/*
* The time zone sub-formatter, introduced in ICU 4.8
*/
private volatile TimeZoneFormat tzFormat;
/**
* Constructs a SimpleDateFormat using the default pattern for the default
* locale. <b>Note:</b> Not all locales support SimpleDateFormat; for full
@ -509,7 +511,7 @@ public class SimpleDateFormat extends DateFormat {
}
if (numberFormat == null) {
NumberingSystem ns = NumberingSystem.getInstance(locale);
if ( ns.isAlgorithmic() ) {
if (ns.isAlgorithmic()) {
numberFormat = NumberFormat.getInstance(locale);
} else {
String digitString = ns.getDescription();
@ -531,6 +533,46 @@ public class SimpleDateFormat extends DateFormat {
}
/**
* Private method lazily instantiate the TimeZoneFormat field
* @param bForceUpdate when true, check if tzFormat is synchronized with
* the current numberFormat and update its digits if necessary. When false,
* this check is skipped.
*/
private synchronized void initializeTimeZoneFormat(boolean bForceUpdate) {
if (bForceUpdate || tzFormat == null) {
tzFormat = TimeZoneFormat.getInstance(locale);
String digits = null;
if (numberFormat instanceof DecimalFormat) {
DecimalFormatSymbols decsym = ((DecimalFormat) numberFormat).getDecimalFormatSymbols();
digits = new String(decsym.getDigits());
} else if (numberFormat instanceof DateNumberFormat) {
digits = new String(((DateNumberFormat)numberFormat).getDigits());
}
if (digits != null) {
if (!tzFormat.getGMTOffsetDigits().equals(digits)) {
if (tzFormat.isFrozen()) {
tzFormat = tzFormat.cloneAsThawed();
}
tzFormat.setGMTOffsetDigits(digits);
}
}
}
}
/**
* Private method, returns non-null TimeZoneFormat.
* @return the TimeZoneFormat used by this formatter.
*/
private TimeZoneFormat tzFormat() {
if (tzFormat == null) {
initializeTimeZoneFormat(false);
}
return tzFormat;
}
// privates for the default pattern
private static ULocale cachedDefaultLocale = null;
private static String cachedDefaultPattern = null;
@ -958,67 +1000,32 @@ public class SimpleDateFormat extends DateFormat {
case 17: // 'z' - ZONE_OFFSET
if (count < 4) {
// "z", "zz", "zzz"
result = formatData.getTimeZoneFormat().format(tz, date, TimeZone.SHORT_COMMONLY_USED);
result = tzFormat().format(Style.SPECIFIC_SHORT_COMMONLY_USED, tz, date);
} else {
result = formatData.getTimeZoneFormat().format(tz, date, TimeZone.LONG);
result = tzFormat().format(Style.SPECIFIC_LONG, tz, date);
}
if ( result == null )
result = formatData.getTimeZoneFormat().format(tz, date, TimeZone.LONG_GMT);
buf.append(result);
break;
case 23: // 'Z' - TIMEZONE_RFC
{
if (count < 4) {
// RFC822 format, must use ASCII digits
int val = (cal.get(Calendar.ZONE_OFFSET) + cal.get(Calendar.DST_OFFSET));
char sign = '+';
if (val < 0) {
val = -val;
sign = '-';
}
buf.append(sign);
int offsetH = val / millisPerHour;
val = val % millisPerHour;
int offsetM = val / millisPerMinute;
val = val % millisPerMinute;
int offsetS = val / millisPerSecond;
int num = 0, denom = 0;
if (offsetS == 0) {
val = offsetH*100 + offsetM; // HHmm
num = val % 10000;
denom = 1000;
} else {
val = offsetH*10000 + offsetM*100 + offsetS; // HHmmss
num = val % 1000000;
denom = 100000;
}
while (denom >= 1) {
char digit = (char)((num / denom) + '0');
buf.append(digit);
num = num % denom;
denom /= 10;
}
// RFC822 format
result = tzFormat().format(Style.RFC822, tz, date);
} else {
// long form, localized GMT pattern
result = formatData.getTimeZoneFormat().format(tz, date, TimeZone.LONG_GMT);
buf.append(result);
result = tzFormat().format(Style.LOCALIZED_GMT, tz, date);
}
buf.append(result);
break;
}
case 24: // 'v' - TIMEZONE_GENERIC
if (count == 1) {
// "v"
result = formatData.getTimeZoneFormat().format(tz, date, TimeZone.SHORT_GENERIC);
result = tzFormat().format(Style.GENERIC_SHORT, tz, date);
} else if (count == 4) {
// "vvvv"
result = formatData.getTimeZoneFormat().format(tz, date, TimeZone.LONG_GENERIC);
result = tzFormat().format(Style.GENERIC_LONG, tz, date);
}
if ( result == null ) {
result = formatData.getTimeZoneFormat().format(tz, date, TimeZone.LONG_GMT);
}
buf.append(result);
break;
@ -1070,13 +1077,10 @@ public class SimpleDateFormat extends DateFormat {
case 29: // 'V' - TIMEZONE_SPECIAL
if (count == 1) {
// "V"
result = formatData.getTimeZoneFormat().format(tz, date, TimeZone.SHORT);
result = tzFormat().format(Style.SPECIFIC_SHORT, tz, date);
} else if (count == 4) {
// "VVVV"
result = formatData.getTimeZoneFormat().format(tz, date, TimeZone.GENERIC_LOCATION);
}
if ( result == null ) {
result = formatData.getTimeZoneFormat().format(tz, date, TimeZone.LONG_GMT);
result = tzFormat().format(Style.GENERIC_LOCATION, tz, date);
}
buf.append(result);
break;
@ -1222,276 +1226,6 @@ public class SimpleDateFormat extends DateFormat {
return patternItems;
}
/*
* Time zone localized GMT format stuffs
*/
private static final String STR_GMT = "GMT";
private static final String STR_UT = "UT";
private static final String STR_UTC = "UTC";
private static final int STR_GMT_LEN = 3;
private static final int STR_UT_LEN = 2;
private static final int STR_UTC_LEN = 3;
private static final char PLUS = '+';
private static final char MINUS = '-';
private static final char COLON = ':';
private Integer parseGMT(String text, ParsePosition pos, NumberFormat currentNumberFormat) {
if (!isDefaultGMTFormat()) {
int start = pos.getIndex();
String gmtPattern = formatData.gmtFormat;
// Quick check
boolean prefixMatch = false;
int prefixLen = gmtPattern.indexOf('{');
if (prefixLen > 0 && text.regionMatches(start, gmtPattern, 0, prefixLen)) {
prefixMatch = true;
}
if (prefixMatch) {
// Prefix matched
MessageFormat fmt;
Object[] parsedObjects;
int offset;
// Try negative Hms
fmt = getGMTFormatter(DateFormatSymbols.OFFSET_NEGATIVE,
DateFormatSymbols.OFFSET_HMS);
parsedObjects = fmt.parse(text, pos);
if ((parsedObjects != null) && (parsedObjects[0] instanceof Date)
&& (pos.getIndex() - start) >= getGMTFormatMinHMSLen(
DateFormatSymbols.OFFSET_NEGATIVE)) {
offset = (int)((Date)parsedObjects[0]).getTime();
return new Integer(-offset /* negative */);
}
// Reset ParsePosition
pos.setIndex(start);
pos.setErrorIndex(-1);
// Try positive Hms
fmt = getGMTFormatter(DateFormatSymbols.OFFSET_POSITIVE,
DateFormatSymbols.OFFSET_HMS);
parsedObjects = fmt.parse(text, pos);
if ((parsedObjects != null) && (parsedObjects[0] instanceof Date)
&& (pos.getIndex() - start) >= getGMTFormatMinHMSLen(
DateFormatSymbols.OFFSET_POSITIVE)) {
offset = (int)((Date)parsedObjects[0]).getTime();
return new Integer(offset);
}
// Reset ParsePosition
pos.setIndex(start);
pos.setErrorIndex(-1);
// Try negative Hm
fmt = getGMTFormatter(DateFormatSymbols.OFFSET_NEGATIVE,
DateFormatSymbols.OFFSET_HM);
parsedObjects = fmt.parse(text, pos);
if ((parsedObjects != null) && (parsedObjects[0] instanceof Date)) {
offset = (int)((Date)parsedObjects[0]).getTime();
return new Integer(-offset /* negative */);
}
// Reset ParsePosition
pos.setIndex(start);
pos.setErrorIndex(-1);
// Try positive Hm
fmt = getGMTFormatter(DateFormatSymbols.OFFSET_POSITIVE,
DateFormatSymbols.OFFSET_HM);
parsedObjects = fmt.parse(text, pos);
if ((parsedObjects != null) && (parsedObjects[0] instanceof Date)) {
offset = (int)((Date)parsedObjects[0]).getTime();
return new Integer(offset);
}
// Reset ParsePosition
pos.setIndex(start);
pos.setErrorIndex(-1);
}
}
return parseGMTDefault(text, pos, currentNumberFormat);
}
private Integer parseGMTDefault(String text, ParsePosition pos,
NumberFormat currentNumberFormat) {
int start = pos.getIndex();
if (start + STR_UT_LEN + 1 >= text.length()) {
pos.setErrorIndex(start);
return null;
}
int cur = start;
// "GMT"
if (text.regionMatches(true, start, STR_GMT, 0, STR_GMT_LEN)) {
cur += STR_GMT_LEN;
} else if (text.regionMatches(true, start, STR_UT, 0, STR_UT_LEN)) {
cur += STR_UT_LEN;
} else {
pos.setErrorIndex(start);
return null;
}
// Sign
boolean negative = false;
if (text.charAt(cur) == MINUS) {
negative = true;
} else if (text.charAt(cur) != PLUS) {
pos.setErrorIndex(cur);
return null;
}
cur++;
// Numbers
int numLen;
pos.setIndex(cur);
Number n = parseInt(text, 6, pos, false,currentNumberFormat);
numLen = pos.getIndex() - cur;
if (n == null || numLen <= 0 || numLen > 6) {
pos.setIndex(start);
pos.setErrorIndex(cur);
return null;
}
int numVal = n.intValue();
int hour = 0;
int min = 0;
int sec = 0;
if (numLen <= 2) {
// H[H][:mm[:ss]]
hour = numVal;
cur += numLen;
if (cur + 2 < text.length() && text.charAt(cur) == COLON) {
cur++;
pos.setIndex(cur);
n = parseInt(text, 2, pos, false,currentNumberFormat);
numLen = pos.getIndex() - cur;
if (n != null && numLen == 2) {
// got minute field
min = n.intValue();
cur += numLen;
if (cur + 2 < text.length() && text.charAt(cur) == COLON) {
cur++;
pos.setIndex(cur);
n = parseInt(text, 2, pos, false,currentNumberFormat);
numLen = pos.getIndex() - cur;
if (n != null && numLen == 2) {
// got second field
sec = n.intValue();
} else {
// reset position
pos.setIndex(cur - 1);
pos.setErrorIndex(-1);
}
}
} else {
// reset postion
pos.setIndex(cur - 1);
pos.setErrorIndex(-1);
}
}
} else if (numLen == 3 || numLen == 4) {
// Hmm or HHmm
hour = numVal / 100;
min = numVal % 100;
} else { // numLen == 5 || numLen == 6
// Hmmss or HHmmss
hour = numVal / 10000;
min = (numVal % 10000) / 100;
sec = numVal % 100;
}
int offset = ((hour*60 + min)*60 + sec)*1000;
if (negative) {
offset = -offset;
}
return new Integer(offset);
}
transient private WeakReference<MessageFormat>[] gmtfmtCache;
@SuppressWarnings("unchecked")
private MessageFormat getGMTFormatter(int sign, int width) {
MessageFormat fmt = null;
if (gmtfmtCache == null) {
gmtfmtCache = new WeakReference[4];
}
int cacheIdx = sign*2 + width;
if (gmtfmtCache[cacheIdx] != null) {
fmt = gmtfmtCache[cacheIdx].get();
}
if (fmt == null) {
fmt = new MessageFormat(formatData.gmtFormat);
GregorianCalendar gcal = new GregorianCalendar(TimeZone.getTimeZone("Etc/UTC"));
SimpleDateFormat sdf = (SimpleDateFormat)this.clone();
sdf.setCalendar(gcal);
sdf.applyPattern(formatData.getGmtHourFormat(sign, width));
fmt.setFormat(0, sdf);
gmtfmtCache[cacheIdx] = new WeakReference<MessageFormat>(fmt);
}
return fmt;
}
transient private int[] gmtFormatHmsMinLen = null;
private int getGMTFormatMinHMSLen(int sign) {
if (gmtFormatHmsMinLen == null) {
gmtFormatHmsMinLen = new int[2];
Long offset = new Long(60*60*1000); // 1 hour
StringBuffer buf = new StringBuffer();
MessageFormat fmtNeg = getGMTFormatter(DateFormatSymbols.OFFSET_NEGATIVE, DateFormatSymbols.OFFSET_HMS);
fmtNeg.format(new Object[] {offset}, buf, null);
gmtFormatHmsMinLen[0] = buf.length();
buf.setLength(0);
MessageFormat fmtPos = getGMTFormatter(DateFormatSymbols.OFFSET_POSITIVE, DateFormatSymbols.OFFSET_HMS);
fmtPos.format(new Object[] {offset}, buf, null);
gmtFormatHmsMinLen[1] = buf.length();
}
return gmtFormatHmsMinLen[(sign < 0 ? 0 : 1)];
}
private boolean isDefaultGMTFormat() {
// GMT pattern
if (!DateFormatSymbols.DEFAULT_GMT_PATTERN.equals(formatData.getGmtFormat())) {
return false;
}
// GMT offset hour patters
boolean res = true;
for (int sign = 0; sign < 2 && res; sign++) {
for (int width = 0; width < 2; width++) {
if (!DateFormatSymbols.DEFAULT_GMT_HOUR_PATTERNS[sign][width]
.equals(formatData.getGmtHourFormat(sign, width))) {
res = false;
break;
}
}
}
return res;
}
/*
* Internal method. Returns null if the value of an array is empty, or if the
* index is out of bounds
*/
/* private String getZoneArrayValue(String[] zs, int ix) {
if (ix >= 0 && ix < zs.length) {
String result = zs[ix];
if (result != null && result.length() != 0) {
return result;
}
}
return null;
}*/
/**
* Internal high-speed method. Reuses a StringBuffer for results
* instead of creating a String on the heap for each call.
@ -1500,7 +1234,10 @@ public class SimpleDateFormat extends DateFormat {
*/
protected void zeroPaddingNumber(NumberFormat nf,StringBuffer buf, int value,
int minDigits, int maxDigits) {
if (useLocalZeroPaddingNumberFormat) {
// Note: Indian calendar uses negative value for a calendar
// field. fastZeroPaddingNumber cannot handle negative numbers.
// BTW, it looks like a design bug in the Indian calendar...
if (useLocalZeroPaddingNumberFormat && value >= 0) {
fastZeroPaddingNumber(buf, value, minDigits, maxDigits);
} else {
nf.setMinimumIntegerDigits(minDigits);
@ -1517,14 +1254,15 @@ public class SimpleDateFormat extends DateFormat {
// Override this method to update local zero padding number formatter
super.setNumberFormat(newNumberFormat);
initLocalZeroPaddingNumberFormat();
initializeTimeZoneFormat(true);
}
private void initLocalZeroPaddingNumberFormat() {
if (numberFormat instanceof DecimalFormat) {
zeroDigit = ((DecimalFormat)numberFormat).getDecimalFormatSymbols().getZeroDigit();
decDigits = ((DecimalFormat)numberFormat).getDecimalFormatSymbols().getDigits();
useLocalZeroPaddingNumberFormat = true;
} else if (numberFormat instanceof DateNumberFormat) {
zeroDigit = ((DateNumberFormat)numberFormat).getZeroDigit();
decDigits = ((DateNumberFormat)numberFormat).getDigits();
useLocalZeroPaddingNumberFormat = true;
} else {
useLocalZeroPaddingNumberFormat = false;
@ -1537,7 +1275,7 @@ public class SimpleDateFormat extends DateFormat {
// If true, use local version of zero padding number format
private transient boolean useLocalZeroPaddingNumberFormat;
private transient char zeroDigit;
private transient char[] decDigits;
private transient char[] decimalBuf;
/*
@ -1555,7 +1293,7 @@ public class SimpleDateFormat extends DateFormat {
int limit = decimalBuf.length < maxDigits ? decimalBuf.length : maxDigits;
int index = limit - 1;
while (true) {
decimalBuf[index] = (char)((value % 10) + zeroDigit);
decimalBuf[index] = decDigits[(value % 10)];
value /= 10;
if (index == 0 || value == 0) {
break;
@ -1564,13 +1302,13 @@ public class SimpleDateFormat extends DateFormat {
}
int padding = minDigits - (limit - index);
while (padding > 0 && index > 0) {
decimalBuf[--index] = zeroDigit;
decimalBuf[--index] = decDigits[0];
padding--;
}
while (padding > 0) {
// when pattern width is longer than decimalBuf, need extra
// leading zeros - ticke#7595
buf.append(zeroDigit);
buf.append(decDigits[0]);
padding--;
}
buf.append(decimalBuf, index, limit - index);
@ -2300,157 +2038,71 @@ public class SimpleDateFormat extends DateFormat {
cal.set(Calendar.HOUR, value);
return pos.getIndex();
case 17: // 'z' - ZONE_OFFSET
case 23: // 'Z' - TIMEZONE_RFC
case 24: // 'v' - TIMEZONE_GENERIC
case 29: // 'V' - TIMEZONE_SPECIAL
{
TimeZone tz = null;
int offset = 0;
boolean parsed = false;
// Step 1
// Check if this is a long GMT offset string (either localized or default)
Integer gmtoff = parseGMT(text, pos, currentNumberFormat);
if (gmtoff != null) {
offset = gmtoff.intValue();
parsed = true;
}
if (!parsed) {
// Step 2
// Check if this is an RFC822 time zone offset.
// ICU supports the standard RFC822 format [+|-]HHmm
// and its extended form [+|-]HHmmSS.
do {
int sign = 0;
char signChar = text.charAt(start);
if (signChar == '+') {
sign = 1;
} else if (signChar == '-') {
sign = -1;
} else {
// Not an RFC822 offset string
break;
}
// Parse digits
int orgPos = start + 1;
pos.setIndex(orgPos);
number = parseInt(text, 6, pos, false,currentNumberFormat);
int numLen = pos.getIndex() - orgPos;
if (numLen <= 0) {
break;
}
// Followings are possible format (excluding sign char)
// HHmmSS
// HmmSS
// HHmm
// Hmm
// HH
// H
int val = number.intValue();
int hour = 0, min = 0, sec = 0;
switch(numLen) {
case 1: // H
case 2: // HH
hour = val;
break;
case 3: // Hmm
case 4: // HHmm
hour = val / 100;
min = val % 100;
break;
case 5: // Hmmss
case 6: // HHmmss
hour = val / 10000;
min = (val % 10000) / 100;
sec = val % 100;
break;
}
if (hour > 23 || min > 59 || sec > 59) {
// Invalid value range
break;
}
offset = (((hour * 60) + min) * 60 + sec) * 1000 * sign;
parsed = true;
} while (false);
if (!parsed) {
// Failed to parse. Reset the position.
pos.setIndex(start);
Output<TimeType> tzTimeType = new Output<TimeType>();
Style style = (count < 4) ? Style.SPECIFIC_SHORT_COMMONLY_USED : Style.SPECIFIC_LONG;
TimeZone tz = tzFormat().parse(style, text, pos, tzTimeType);
if (tz != null) {
if (tzTimeType.value == TimeType.STANDARD) {
tztype = TZTYPE_STD;
} else if (tzTimeType.value == TimeType.DAYLIGHT) {
tztype = TZTYPE_DST;
}
}
if (parsed) {
// offset was successfully parsed as either a long GMT string or
// RFC822 zone offset string. Create normalized zone ID for the
// offset.
tz = ZoneMeta.getCustomTimeZone(offset);
cal.setTimeZone(tz);
return pos.getIndex();
}
// Step 3
// At this point, check for named time zones by looking through
// the locale data from the DateFormatZoneData strings.
// Want to be able to parse both short and long forms.
// optimize for calendar's current time zone
ZoneStringInfo zsinfo = null;
switch (patternCharIndex) {
case 17: // 'z' - ZONE_OFFSET
if (count < 4) {
zsinfo = formatData.getZoneStringFormat().findSpecificShort(text, start);
} else {
zsinfo = formatData.getZoneStringFormat().findSpecificLong(text, start);
return -start;
}
break;
case 24: // 'v' - TIMEZONE_GENERIC
if (count == 1) {
zsinfo = formatData.getZoneStringFormat().findGenericShort(text, start);
} else if (count == 4) {
zsinfo = formatData.getZoneStringFormat().findGenericLong(text, start);
}
break;
case 29: // 'V' - TIMEZONE_SPECIAL
if (count == 1) {
zsinfo = formatData.getZoneStringFormat().findSpecificShort(text, start);
} else if (count == 4) {
zsinfo = formatData.getZoneStringFormat().findGenericLocation(text, start);
}
break;
}
if (zsinfo != null) {
if (zsinfo.isStandard()) {
case 23: // 'Z' - TIMEZONE_RFC
{
Output<TimeType> tzTimeType = new Output<TimeType>();
Style style = (count < 4) ? Style.RFC822 : Style.LOCALIZED_GMT;
TimeZone tz = tzFormat().parse(style, text, pos, tzTimeType);
if (tz != null) {
if (tzTimeType.value == TimeType.STANDARD) {
tztype = TZTYPE_STD;
} else if (zsinfo.isDaylight()) {
} else if (tzTimeType.value == TimeType.DAYLIGHT) {
tztype = TZTYPE_DST;
}
tz = TimeZone.getTimeZone(zsinfo.getID());
cal.setTimeZone(tz);
return start + zsinfo.getString().length();
return pos.getIndex();
}
return -start;
}
// Step 4
// Final attempt - is this standalone GMT/UT/UTC?
int gmtLen = 0;
if (text.regionMatches(true, start, STR_GMT, 0, STR_GMT_LEN)) {
gmtLen = STR_GMT_LEN;
} else if (text.regionMatches(true, start, STR_UTC, 0, STR_UTC_LEN)) {
gmtLen = STR_UTC_LEN;
} else if (text.regionMatches(true, start, STR_UT, 0, STR_UT_LEN)) {
gmtLen = STR_UT_LEN;
}
if (gmtLen > 0) {
tz = TimeZone.getTimeZone("Etc/GMT");
case 24: // 'v' - TIMEZONE_GENERIC
{
Output<TimeType> tzTimeType = new Output<TimeType>();
// Note: 'v' only supports count 1 and 4
Style style = (count < 4) ? Style.GENERIC_SHORT : Style.GENERIC_LONG;
TimeZone tz = tzFormat().parse(style, text, pos, tzTimeType);
if (tz != null) {
if (tzTimeType.value == TimeType.STANDARD) {
tztype = TZTYPE_STD;
} else if (tzTimeType.value == TimeType.DAYLIGHT) {
tztype = TZTYPE_DST;
}
cal.setTimeZone(tz);
return start + gmtLen;
return pos.getIndex();
}
return -start;
}
case 29: // 'V' - TIMEZONE_SPECIAL
{
Output<TimeType> tzTimeType = new Output<TimeType>();
// Note: 'v' only supports count 1 and 4
Style style = (count < 4) ? Style.SPECIFIC_SHORT : Style.GENERIC_LOCATION;
TimeZone tz = tzFormat().parse(style, text, pos, tzTimeType);
if (tz != null) {
if (tzTimeType.value == TimeType.STANDARD) {
tztype = TZTYPE_STD;
} else if (tzTimeType.value == TimeType.DAYLIGHT) {
tztype = TZTYPE_DST;
}
cal.setTimeZone(tz);
return pos.getIndex();
}
// complete failure
return -start;
}
case 27: // 'Q' - QUARTER
if (count <= 2) { // i.e., Q or QQ.
// Don't want to parse the quarter if it is a string
@ -2676,7 +2328,6 @@ public class SimpleDateFormat extends DateFormat {
public void setDateFormatSymbols(DateFormatSymbols newFormatSymbols)
{
this.formatData = (DateFormatSymbols)newFormatSymbols.clone();
gmtfmtCache = null;
}
/**
@ -2687,6 +2338,36 @@ public class SimpleDateFormat extends DateFormat {
return formatData;
}
/**
* {@icu} Gets the time zone formatter which this date/time
* formatter uses to format and parse a time zone.
*
* @return the time zone formatter which this date/time
* formatter uses.
* @internal ICU 4.8 technology preview
* @deprecated This API might change or be removed in a future release.
*/
public TimeZoneFormat getTimeZoneFormat() {
return tzFormat().freeze();
}
/**
* {@icu} Allows you to set the time zoen formatter.
*
* @param tzfmt the new time zone formatter
* @internal ICU 4.8 technology preview
* @deprecated This API might change or be removed in a future release.
*/
public void setTimeZoneFormat(TimeZoneFormat tzfmt) {
if (tzfmt.isFrozen()) {
// If frozen, use it as is.
tzFormat = tzfmt;
} else {
// If not frozen, clone and freeze.
tzFormat = tzfmt.cloneAsThawed().freeze();
}
}
/**
* Overrides Cloneable
* @stable ICU 2.0
@ -2729,6 +2410,7 @@ public class SimpleDateFormat extends DateFormat {
// calculate and set value before serialization.
initializeDefaultCenturyStart(defaultCenturyBase);
}
initializeTimeZoneFormat(false);
stream.defaultWriteObject();
}
@ -2751,6 +2433,12 @@ public class SimpleDateFormat extends DateFormat {
}
serialVersionOnStream = currentSerialVersion;
locale = getLocale(ULocale.VALID_LOCALE);
if (locale == null) {
// ICU4J 3.6 or older versions did not have UFormat locales
// in the serialized data. This is just for preventing the
// worst case scenario...
locale = ULocale.getDefault();
}
initLocalZeroPaddingNumberFormat();
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,572 @@
/*
*******************************************************************************
* Copyright (C) 2011, International Business Machines Corporation and *
* others. All Rights Reserved. *
*******************************************************************************
*/
package com.ibm.icu.text;
import java.io.Serializable;
import java.util.Collection;
import java.util.Collections;
import java.util.EnumSet;
import java.util.Set;
import java.util.regex.Pattern;
import com.ibm.icu.impl.ICUConfig;
import com.ibm.icu.impl.SoftCache;
import com.ibm.icu.util.TimeZone;
import com.ibm.icu.util.ULocale;
/**
* <code>TimeZoneNames</code> is an abstract class representing the time zone display name data model defined
* by <a href="http://www.unicode.org/reports/tr35/">UTS#35 Unicode Locale Data Markup Language (LDML)</a>.
* The model defines meta zone, which is used for storing a set of display names. A meta zone can be shared
* by multiple time zones. Also a time zone may have multiple meta zone historic mappings.
* <p>
* For example, people in the United States refer the zone used by the east part of North America as "Eastern Time".
* The tz database contains multiple time zones "America/New_York", "America/Detroit", "America/Montreal" and some
* others that belong to "Eastern Time". However, assigning different display names to these time zones does not make
* much sense for most of people.
* <p>
* In <a href="http://cldr.unicode.org/">CLDR</a> (which uses LDML for representing locale data), the display name
* "Eastern Time" is stored as long generic display name of a meta zone identified by the ID "America_Eastern".
* Then, there is another table maintaining the historic mapping to meta zones for each time zone. The time zones in
* the above example ("America/New_York", "America/Detroit"...) are mapped to the meta zone "America_Eastern".
* <p>
* Sometimes, a time zone is mapped to a different time zone in the past. For example, "America/Indiana/Knox"
* had been moving "Eastern Time" and "Central Time" back and forth. Therefore, it is necessary that time zone
* to meta zones mapping data are stored by date range.
*
* <p><b>Note:</b>
* <p>
* {@link TimeZoneFormat} assumes an instance of <code>TimeZoneNames</code> is immutable. If you want to provide
* your own <code>TimeZoneNames</code> implementation and use it with {@link TimeZoneFormat}, you must follow
* the contract.
* <p>
* The methods in this class assume that time zone IDs are already canonicalized. For example, you may not get proper
* result returned by a method with time zone ID "America/Indiana/Indianapolis", because it's not a canonical time zone
* ID (the canonical time zone ID for the time zone is "America/Indianapolis". See
* {@link TimeZone#getCanonicalID(String)} about ICU canonical time zone IDs.
*
* <p>
* In CLDR, most of time zone display names except location names are provided through meta zones. But a time zone may
* have a specific name that is not shared with other time zones.
*
* For example, time zone "Europe/London" has English long name for standard time "Greenwich Mean Time", which is also
* shared with other time zones. However, the long name for daylight saving time is "British Summer Time", which is only
* used for "Europe/London".
*
* <p>
* {@link #getTimeZoneDisplayName(String, NameType)} is designed for accessing a name only used by a single time zone.
* But is not necessarily mean that a subclass implementation use the same model with CLDR. A subclass implementation
* may provide time zone names only through {@link #getTimeZoneDisplayName(String, NameType)}, or only through
* {@link #getMetaZoneDisplayName(String, NameType)}, or both.
*
* @internal ICU 4.8 technology preview
* @deprecated This API might change or be removed in a future release.
*/
public abstract class TimeZoneNames implements Serializable {
private static final long serialVersionUID = -9180227029248969153L;
/**
* Time zone display name types
*
* @internal ICU 4.8 technology preview
* @deprecated This API might change or be removed in a future release.
*/
public enum NameType {
/**
* Long display name, such as "Eastern Time".
*
* @internal ICU 4.8 technology preview
* @deprecated This API might change or be removed in a future release.
*/
LONG_GENERIC,
/**
* Long display name for standard time, such as "Eastern Standard Time".
*
* @internal ICU 4.8 technology preview
* @deprecated This API might change or be removed in a future release.
*/
LONG_STANDARD,
/**
* Long display name for daylight saving time, such as "Eastern Daylight Time".
*
* @internal ICU 4.8 technology preview
* @deprecated This API might change or be removed in a future release.
*/
LONG_DAYLIGHT,
/**
* Short display name, such as "ET".
*
* @internal ICU 4.8 technology preview
* @deprecated This API might change or be removed in a future release.
*/
SHORT_GENERIC,
/**
* Short display name for standard time, such as "EST".
*
* @internal ICU 4.8 technology preview
* @deprecated This API might change or be removed in a future release.
*/
SHORT_STANDARD,
/**
* Short display name for daylight saving time, such as "EDT".
*
* @internal ICU 4.8 technology preview
* @deprecated This API might change or be removed in a future release.
*/
SHORT_DAYLIGHT,
/**
* Short display name for standard time, such as "EST".
* <p><b>Note:</b> The short abbreviation might not be well understood by people not familiar with the zone.
* Unlike {@link #SHORT_STANDARD}, this type excludes short standard names not commonly used by the region.
*
* @internal ICU 4.8 technology preview
* @deprecated This API might change or be removed in a future release.
*/
SHORT_STANDARD_COMMONLY_USED,
/**
* Short display name for daylight saving time, such as "EDT".
* <p><b>Note:</b> The short abbreviation might not be well understood by people not familiar with the zone.
* Unlike {@link #SHORT_DAYLIGHT}, this type excludes short daylight names not commonly used by the region.
*
* @internal ICU 4.8 technology preview
* @deprecated This API might change or be removed in a future release.
*/
SHORT_DAYLIGHT_COMMONLY_USED
}
private static Cache TZNAMES_CACHE = new Cache();
private static final Factory TZNAMES_FACTORY;
private static final String FACTORY_NAME_PROP = "com.ibm.icu.text.TimeZoneNames.Factory.impl";
private static final String DEFAULT_FACTORY_CLASS = "com.ibm.icu.impl.TimeZoneNamesFactoryImpl";
private static final Pattern LOC_EXCLUSION_PATTERN = Pattern.compile("Etc/.*|SystemV/.*|.*/Riyadh8[7-9]");
static {
Factory factory = null;
String classname = ICUConfig.get(FACTORY_NAME_PROP, DEFAULT_FACTORY_CLASS);
while (true) {
try {
factory = (Factory) Class.forName(classname).newInstance();
break;
} catch (ClassNotFoundException cnfe) {
// fall through
} catch (IllegalAccessException iae) {
// fall through
} catch (InstantiationException ie) {
// fall through
}
if (classname.equals(DEFAULT_FACTORY_CLASS)) {
break;
}
classname = DEFAULT_FACTORY_CLASS;
}
if (factory == null) {
factory = new DefaultTimeZoneNames.FactoryImpl();
}
TZNAMES_FACTORY = factory;
}
/**
* Returns an instance of <code>TimeZoneDisplayNames</code> for the specified locale.
*
* @param locale
* The locale.
* @return An instance of <code>TimeZoneDisplayNames</code>
* @internal ICU 4.8 technology preview
* @deprecated This API might change or be removed in a future release.
*/
public static TimeZoneNames getInstance(ULocale locale) {
String key = locale.getBaseName();
return TZNAMES_CACHE.getInstance(key, locale);
}
/**
* Returns an immutable set of all available meta zone IDs.
* @return An immutable set of all available meta zone IDs.
* @internal ICU 4.8 technology preview
* @deprecated This API might change or be removed in a future release.
*/
public abstract Set<String> getAvailableMetaZoneIDs();
/**
* Returns an immutable set of all available meta zone IDs used by the given time zone.
*
* @param tzID
* The canonical time zone ID.
* @return An immutable set of all available meta zone IDs used by the given time zone.
* @internal ICU 4.8 technology preview
* @deprecated This API might change or be removed in a future release.
*/
public abstract Set<String> getAvailableMetaZoneIDs(String tzID);
/**
* Returns the meta zone ID for the given canonical time zone ID at the given date.
*
* @param tzID
* The canonical time zone ID.
* @param date
* The date.
* @return The meta zone ID for the given time zone ID at the given date. If the time zone does not have a
* corresponding meta zone at the given date or the implementation does not support meta zones, null is
* returned.
* @internal ICU 4.8 technology preview
* @deprecated This API might change or be removed in a future release.
*/
public abstract String getMetaZoneID(String tzID, long date);
/**
* Returns the reference zone ID for the given meta zone ID for the region.
*
* @param mzID
* The meta zone ID.
* @param region
* The region.
* @return The reference zone ID ("golden zone" in the LDML specification) for the given time zone ID for the
* region. If the meta zone is unknown or the implementation does not support meta zones, null is returned.
* @internal ICU 4.8 technology preview
* @deprecated This API might change or be removed in a future release.
*/
public abstract String getReferenceZoneID(String mzID, String region);
/**
* Returns the display name of the meta zone.
*
* @param mzID
* The meta zone ID.
* @param type
* The display name type. See {@link TimeZoneNames.NameType}.
* @return The display name of the meta zone. When this object does not have a localized display name for the given
* meta zone with the specified type or the implementation does not provide any display names associated
* with meta zones, null is returned.
* @internal ICU 4.8 technology preview
* @deprecated This API might change or be removed in a future release.
*/
public abstract String getMetaZoneDisplayName(String mzID, NameType type);
/**
* Returns the display name of the time zone at the given date.
*
* <p>
* <b>Note:</b> This method calls the subclass's {@link #getTimeZoneDisplayName(String, NameType)} first. When the
* result is null, this method calls {@link #getMetaZoneID(String, long)} to get the meta zone ID mapped from the
* time zone, then calls {@link #getMetaZoneDisplayName(String, NameType)}.
*
* @param tzID
* The canonical time zone ID.
* @param type
* The display name type. See {@link TimeZoneNames.NameType}.
* @param date
* The date
* @return The display name for the time zone at the given date. When this object does not have a localized display
* name for the time zone with the specified type and date, null is returned.
* @internal ICU 4.8 technology preview
* @deprecated This API might change or be removed in a future release.
*/
public final String getDisplayName(String tzID, NameType type, long date) {
String name = getTimeZoneDisplayName(tzID, type);
if (name == null) {
String mzID = getMetaZoneID(tzID, date);
name = getMetaZoneDisplayName(mzID, type);
}
return name;
}
/**
* Returns the display name of the time zone. Unlike {@link #getDisplayName(String, NameType, long)},
* this method does not get a name from a meta zone used by the time zone.
*
* @param tzID
* The canonical time zone ID.
* @param type
* The display name type. See {@link TimeZoneNames.NameType}.
* @return The display name for the time zone. When this object does not have a localized display name for the given
* time zone with the specified type, null is returned.
* @internal ICU 4.8 technology preview
* @deprecated This API might change or be removed in a future release.
*/
public abstract String getTimeZoneDisplayName(String tzID, NameType type);
/**
* Returns the exemplar location name for the given time zone. When this object does not have a localized location
* name, the default implementation may still returns a programmatically generated name with the logic described
* below.
* <ol>
* <li>Check if the ID contains "/". If not, return null.
* <li>Check if the ID does not start with "Etc/" or "SystemV/". If it does, return null.
* <li>Extract a substring after the last occurrence of "/".
* <li>Replace "_" with " ".
* </ol>
* For example, "New York" is returned for the time zone ID "America/New_York" when this object does not have the
* localized location name.
*
* @param tzID
* The canonical time zone ID
* @return The exemplar location name for the given time zone, or null when a localized location name is not
* available and the fallback logic described above cannot extract location from the ID.
* @internal ICU 4.8 technology preview
* @deprecated This API might change or be removed in a future release.
*/
public String getExemplarLocationName(String tzID) {
if (tzID == null || tzID.length() == 0 || LOC_EXCLUSION_PATTERN.matcher(tzID).matches()) {
return null;
}
String location = null;
int sep = tzID.lastIndexOf('/');
if (sep > 0 && sep + 1 < tzID.length()) {
location = tzID.substring(sep + 1).replace('_', ' ');
}
return location;
}
/**
* Finds time zone name prefix matches for the input text at the
* given offset and returns a collection of the matches.
*
* @param text the text.
* @param start the starting offset within the text.
* @param types the set of name types, or <code>null</code> for all name types.
* @return A collection of matches.
* @see NameType
* @see MatchInfo
* @internal ICU 4.8 technology preview
* @deprecated This API might change or be removed in a future release.
*/
public Collection<MatchInfo> find(String text, int start, EnumSet<NameType> types) {
throw new UnsupportedOperationException("The method is not implemented in TimeZoneNames base class.");
}
/**
* A <code>MatchInfo</code> represents a time zone name match used by
* {@link TimeZoneNames#find(String, int, EnumSet)}.
* @internal ICU 4.8 technology preview
* @deprecated This API might change or be removed in a future release.
*/
public static class MatchInfo {
private NameType _nameType;
private String _tzID;
private String _mzID;
private int _matchLength;
/**
* Constructing a <code>MatchInfo</code>.
*
* @param nameType the name type enum.
* @param tzID the time zone ID, or null
* @param mzID the meta zone ID, or null
* @param matchLength the match length.
* @throws IllegalArgumentException when 1) <code>nameType</code> is <code>null</code>,
* or 2) both <code>tzID</code> and <code>mzID</code> are <code>null</code>,
* or 3) <code>matchLength</code> is 0 or smaller.
* @see NameType
* @internal ICU 4.8 technology preview
* @deprecated This API might change or be removed in a future release.
*/
public MatchInfo(NameType nameType, String tzID, String mzID, int matchLength) {
if (nameType == null) {
throw new IllegalArgumentException("nameType is null");
}
if (tzID == null && mzID == null) {
throw new IllegalArgumentException("Either tzID or mzID must be available");
}
if (matchLength <= 0) {
throw new IllegalArgumentException("matchLength must be positive value");
}
_nameType = nameType;
_tzID = tzID;
_mzID = mzID;
_matchLength = matchLength;
}
/**
* Returns the time zone ID, or <code>null</code> if not available.
*
* <p><b>Note</b>: A <code>MatchInfo</code> must have either a time zone ID
* or a meta zone ID.
*
* @return the time zone ID, or <code>null</code>.
* @see #mzID()
* @internal ICU 4.8 technology preview
* @deprecated This API might change or be removed in a future release.
*/
public String tzID() {
return _tzID;
}
/**
* Returns the meta zone ID, or <code>null</code> if not available.
*
* <p><b>Note</b>: A <code>MatchInfo</code> must have either a time zone ID
* or a meta zone ID.
*
* @return the meta zone ID, or <code>null</code>.
* @see #tzID()
* @internal ICU 4.8 technology preview
* @deprecated This API might change or be removed in a future release.
*/
public String mzID() {
return _mzID;
}
/**
* Returns the time zone name type.
* @return the time zone name type enum.
* @see NameType
* @internal ICU 4.8 technology preview
* @deprecated This API might change or be removed in a future release.
*/
public NameType nameType() {
return _nameType;
}
/**
* Returns the match length.
* @return the match length.
* @internal ICU 4.8 technology preview
* @deprecated This API might change or be removed in a future release.
*/
public int matchLength() {
return _matchLength;
}
}
/**
* Sole constructor for invocation by subclass constructors.
*
* @internal ICU 4.8 technology preview
* @deprecated This API might change or be removed in a future release.
*/
protected TimeZoneNames() {
}
/**
* The super class of <code>TimeZoneNames</code> service factory classes.
*
* @internal
*/
public static abstract class Factory {
/**
* The factory method of <code>TimeZoneNames</code>.
*
* @param locale
* The display locale
* @return An instance of <code>TimeZoneNames</code>.
* @internal
*/
public abstract TimeZoneNames getTimeZoneNames(ULocale locale);
}
/**
* TimeZoneNames cache used by {@link TimeZoneNames#getInstance(ULocale)}
*/
private static class Cache extends SoftCache<String, TimeZoneNames, ULocale> {
/*
* (non-Javadoc)
*
* @see com.ibm.icu.impl.CacheBase#createInstance(java.lang.Object, java.lang.Object)
*/
@Override
protected TimeZoneNames createInstance(String key, ULocale data) {
return TZNAMES_FACTORY.getTimeZoneNames(data);
}
}
/**
* The default implementation of <code>TimeZoneNames</code> used by {@link TimeZoneNames#getInstance(ULocale)} when
* the ICU4J tznamedata component is not available.
*/
private static class DefaultTimeZoneNames extends TimeZoneNames {
private static final long serialVersionUID = -995672072494349071L;
public static final DefaultTimeZoneNames INSTANCE = new DefaultTimeZoneNames();
/* (non-Javadoc)
* @see com.ibm.icu.text.TimeZoneNames#getAvailableMetaZoneIDs()
*/
@Override
public Set<String> getAvailableMetaZoneIDs() {
return Collections.emptySet();
}
/* (non-Javadoc)
* @see com.ibm.icu.text.TimeZoneNames#getAvailableMetaZoneIDs(java.lang.String)
*/
@Override
public Set<String> getAvailableMetaZoneIDs(String tzID) {
return Collections.emptySet();
}
/*
* (non-Javadoc)
*
* @see com.ibm.icu.text.TimeZoneNames#getMetaZoneID (java.lang.String, long)
*/
@Override
public String getMetaZoneID(String tzID, long date) {
return null;
}
/*
* (non-Javadoc)
*
* @see com.ibm.icu.text.TimeZoneNames#getReferenceZoneID(java.lang.String, java.lang.String)
*/
@Override
public String getReferenceZoneID(String mzID, String region) {
return null;
}
/*
* (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) {
return null;
}
/*
* (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) {
return null;
}
/* (non-Javadoc)
* @see com.ibm.icu.text.TimeZoneNames#find(java.lang.String, int, com.ibm.icu.text.TimeZoneNames.NameType[])
*/
@Override
public Collection<MatchInfo> find(String text, int start, EnumSet<NameType> nameTypes) {
return Collections.emptyList();
}
/**
* The default <code>TimeZoneNames</code> factory called from {@link TimeZoneNames#getInstance(ULocale)} when
* the ICU4J tznamedata component is not available.
*/
public static class FactoryImpl extends Factory {
/*
* (non-Javadoc)
*
* @see com.ibm.icu.text.TimeZoneNames.Factory#getTimeZoneNames (com.ibm.icu.util.ULocale)
*/
@Override
public TimeZoneNames getTimeZoneNames(ULocale locale) {
return DefaultTimeZoneNames.INSTANCE;
}
}
}
}

View File

@ -0,0 +1,50 @@
/*
*******************************************************************************
* Copyright (C) 2011, International Business Machines Corporation and *
* others. All Rights Reserved. *
*******************************************************************************
*/
package com.ibm.icu.util;
/**
* Simple struct-like class for output parameters.
* @param <T> The type of the parameter.
* @draft ICU 4.8
* @provisional This API might change or be removed in a future release.
*/
public class Output<T> {
/**
* The value field
* @draft ICU 4.8
* @provisional This API might change or be removed in a future release.
*/
public T value;
/**
* {@inheritDoc}
* @draft ICU 4.8
* @provisional This API might change or be removed in a future release.
*/
public String toString() {
return value == null ? "null" : value.toString();
}
/**
* Constructs an empty <code>Output</code>
* @draft ICU 4.8
* @provisional This API might change or be removed in a future release.
*/
public Output() {
}
/**
* Constructs an <code>Output</code> withe the given value.
* @param value the initial value
* @draft ICU 4.8
* @provisional This API might change or be removed in a future release.
*/
public Output(T value) {
this.value = value;
}
}

View File

@ -14,14 +14,16 @@ import java.util.MissingResourceException;
import java.util.Set;
import com.ibm.icu.impl.Grego;
import com.ibm.icu.impl.ICUCache;
import com.ibm.icu.impl.ICUConfig;
import com.ibm.icu.impl.ICULogger;
import com.ibm.icu.impl.JavaTimeZone;
import com.ibm.icu.impl.SimpleCache;
import com.ibm.icu.impl.TimeZoneAdapter;
import com.ibm.icu.impl.TimeZoneFormat;
import com.ibm.icu.impl.ZoneMeta;
import com.ibm.icu.text.TimeZoneFormat;
import com.ibm.icu.text.TimeZoneFormat.Style;
import com.ibm.icu.text.TimeZoneFormat.TimeType;
import com.ibm.icu.text.TimeZoneNames;
import com.ibm.icu.text.TimeZoneNames.NameType;
/**
* {@icuenhanced java.util.TimeZone}.{@icu _usage_}
@ -206,7 +208,7 @@ abstract public class TimeZone implements Serializable, Cloneable {
* @see #getTimeZone(String)
*
* @draft ICU 4.8
* @provisional This API might change or be removed in a future release.
* @provisional This API might change or be removed in a future release.
*/
public static final String UNKNOWN_ZONE_ID = "Etc/Unknown";
@ -215,37 +217,31 @@ abstract public class TimeZone implements Serializable, Cloneable {
* {@link TimeZone#getAvailableIDs(SystemTimeZoneType, String, Integer)}
*
* @draft ICU 4.8
* @provisional This API might change or be removed in a future release.
* @provisional This API might change or be removed in a future release.
*/
public enum SystemTimeZoneType {
/**
* Any system zones.
* @draft ICU 4.8
* @provisional This API might change or be removed in a future release.
* @provisional This API might change or be removed in a future release.
*/
ANY,
/**
* Canonical system zones.
* @draft ICU 4.8
* @provisional This API might change or be removed in a future release.
* @provisional This API might change or be removed in a future release.
*/
CANONICAL,
/**
* Canonical system zones associated with actual locations.
* @draft ICU 4.8
* @provisional This API might change or be removed in a future release.
* @provisional This API might change or be removed in a future release.
*/
CANONICAL_LOCATION,
}
/**
* Cache to hold the SimpleDateFormat objects for a Locale.
*/
private static ICUCache<ULocale, TimeZoneFormat> cachedLocaleData =
new SimpleCache<ULocale, TimeZoneFormat>();
/**
* Gets the time zone offset, for current date, modified in case of
* daylight savings. This is the offset to add *to* UTC to get local time.
@ -385,7 +381,7 @@ abstract public class TimeZone implements Serializable, Cloneable {
* @stable ICU 2.0
*/
public final String getDisplayName() {
return _getDisplayName(false, false, LONG_GENERIC, ULocale.getDefault());
return _getDisplayName(LONG_GENERIC, false, ULocale.getDefault());
}
/**
@ -400,7 +396,7 @@ abstract public class TimeZone implements Serializable, Cloneable {
* @stable ICU 2.0
*/
public final String getDisplayName(Locale locale) {
return _getDisplayName(false, false, LONG_GENERIC, ULocale.forLocale(locale));
return _getDisplayName(LONG_GENERIC, false, ULocale.forLocale(locale));
}
/**
@ -415,15 +411,15 @@ abstract public class TimeZone implements Serializable, Cloneable {
* @stable ICU 3.2
*/
public final String getDisplayName(ULocale locale) {
return _getDisplayName(false, false, LONG_GENERIC, locale);
return _getDisplayName(LONG_GENERIC, false, locale);
}
/**
* Returns a name of this time zone suitable for presentation to the user
* in the default locale.
* If the display name is not available for the locale,
* then this method returns a string in the format
* <code>GMT[+-]hh:mm</code>.
* then this method returns a string in the localized GMT offset format
* such as <code>GMT[+-]HH:mm</code>.
* @param daylight if true, return the daylight savings name.
* @param style the output style of the display name. Valid styles are
* <code>SHORT</code>, <code>LONG</code>, <code>SHORT_GENERIC</code>,
@ -440,8 +436,8 @@ abstract public class TimeZone implements Serializable, Cloneable {
* Returns a name of this time zone suitable for presentation to the user
* in the specified locale.
* If the display name is not available for the locale,
* then this method returns a string in the format
* <code>GMT[+-]hh:mm</code>.
* then this method returns a string in the localized GMT offset format
* such as <code>GMT[+-]HH:mm</code>.
* @param daylight if true, return the daylight savings name.
* @param style the output style of the display name. Valid styles are
* <code>SHORT</code>, <code>LONG</code>, <code>SHORT_GENERIC</code>,
@ -461,8 +457,8 @@ abstract public class TimeZone implements Serializable, Cloneable {
* Returns a name of this time zone suitable for presentation to the user
* in the specified locale.
* If the display name is not available for the locale,
* then this method returns a string in the format
* <code>GMT[+-]hh:mm</code>.
* then this method returns a string in the localized GMT offset format
* such as <code>GMT[+-]HH:mm</code>.
* @param daylight if true, return the daylight savings name.
* @param style the output style of the display name. Valid styles are
* <code>SHORT</code>, <code>LONG</code>, <code>SHORT_GENERIC</code>,
@ -479,7 +475,7 @@ abstract public class TimeZone implements Serializable, Cloneable {
throw new IllegalArgumentException("Illegal style: " + style);
}
return _getDisplayName(daylight, true, style, locale);
return _getDisplayName(style, daylight, locale);
}
/**
@ -487,37 +483,81 @@ abstract public class TimeZone implements Serializable, Cloneable {
* SHORT, LONG, SHORT_GENERIC, LONG_GENERIC, SHORT_GMT, LONG_GMT,
* SHORT_COMMONLY_USED and GENERIC_LOCATION.
*/
private String _getDisplayName(boolean daylight, boolean daylightRequested, int style, ULocale locale) {
private String _getDisplayName(int style, boolean daylight, ULocale locale) {
if (locale == null) {
throw new NullPointerException("locale is null");
}
// We keep a cache, indexed by locale.
TimeZoneFormat tzf = null;
tzf = cachedLocaleData.get(locale);
if (tzf == null) {
tzf = TimeZoneFormat.createInstance(locale);
cachedLocaleData.put(locale, tzf);
}
String result = null;
String result;
if ( daylightRequested ) {
result = tzf.format(this, style, daylight);
if ( result == null) {
result = tzf.format( this, LONG_GMT, daylight);
if (style == GENERIC_LOCATION || style == LONG_GENERIC || style == SHORT_GENERIC) {
// Generic format
TimeZoneFormat tzfmt = TimeZoneFormat.getInstance(locale);
long date = System.currentTimeMillis();
Output<TimeType> timeType = new Output<TimeType>(TimeType.UNKNOWN);
switch (style) {
case GENERIC_LOCATION:
result = tzfmt.format(Style.GENERIC_LOCATION, this, date, timeType);
break;
case LONG_GENERIC:
result = tzfmt.format(Style.GENERIC_LONG, this, date, timeType);
break;
case SHORT_GENERIC:
result = tzfmt.format(Style.GENERIC_SHORT, this, date, timeType);
break;
}
// Generic format many use Localized GMT as the final fallback.
// When Localized GMT format is used, the result might not be
// appropriate for the requested daylight value.
if (daylight && timeType.value == TimeType.STANDARD ||
!daylight && timeType.value == TimeType.DAYLIGHT) {
int offset = daylight ? getRawOffset() + getDSTSavings() : getRawOffset();
result = tzfmt.formatOffsetLocalizedGMT(offset);
}
} else if (style == LONG_GMT || style == SHORT_GMT) {
// Offset format
TimeZoneFormat tzfmt = TimeZoneFormat.getInstance(locale);
int offset = daylight && useDaylightTime() ? getRawOffset() + getDSTSavings() : getRawOffset();
switch (style) {
case LONG_GMT:
result = tzfmt.formatOffsetLocalizedGMT(offset);
break;
case SHORT_GMT:
result = tzfmt.formatOffsetRFC822(offset);
break;
}
} else {
long now = System.currentTimeMillis();
result = tzf.format(this, now, style);
if ( result == null) {
result = tzf.format( this, now, LONG_GMT);
// Specific format
assert(style == LONG || style == SHORT || style == SHORT_COMMONLY_USED);
// Gets the name directly from TimeZoneNames
long date = System.currentTimeMillis();
TimeZoneNames tznames = TimeZoneNames.getInstance(locale);
NameType nameType = null;
switch (style) {
case LONG:
nameType = daylight ? NameType.LONG_DAYLIGHT : NameType.LONG_STANDARD;
break;
case SHORT:
nameType = daylight ? NameType.SHORT_DAYLIGHT : NameType.SHORT_STANDARD;
break;
case SHORT_COMMONLY_USED:
nameType = daylight ? NameType.SHORT_DAYLIGHT_COMMONLY_USED : NameType.SHORT_STANDARD_COMMONLY_USED;
break;
}
result = tznames.getDisplayName(ZoneMeta.getCanonicalCLDRID(this), nameType, date);
if (result == null) {
// Fallback to localized GMT
TimeZoneFormat tzfmt = TimeZoneFormat.getInstance(locale);
int offset = daylight && useDaylightTime() ? getRawOffset() + getDSTSavings() : getRawOffset();
result = tzfmt.formatOffsetLocalizedGMT(offset);
}
}
if ( result == null )
result = tzf.format(this, LONG_GMT, daylight);
assert(result != null);
return result;
}
@ -604,7 +644,7 @@ abstract public class TimeZone implements Serializable, Cloneable {
result = new JavaTimeZone(ID);
} else {
/* We first try to lookup the zone ID in our system list. If this
* fails, we try to parse it as a custom string GMT[+-]hh:mm. If
* fails, we try to parse it as a custom string GMT[+-]HH:mm. If
* all else fails, we return GMT, which is probably not what the
* user wants, but at least is a functioning TimeZone object.
*
@ -667,7 +707,7 @@ abstract public class TimeZone implements Serializable, Cloneable {
* @see SystemTimeZoneType
*
* @draft ICU 4.8
* @provisional This API might change or be removed in a future release.
* @provisional This API might change or be removed in a future release.
*/
public static Set<String> getAvailableIDs(SystemTimeZoneType zoneType,
String region, Integer rawOffset) {
@ -954,7 +994,7 @@ abstract public class TimeZone implements Serializable, Cloneable {
* @see #getAvailableIDs(String)
*
* @draft ICU 4.8
* @provisional This API might change or be removed in a future release.
* @provisional This API might change or be removed in a future release.
*/
public static String getRegion(String id) {
String region = null;

View File

@ -1,6 +1,6 @@
/*
*******************************************************************************
* Copyright (C) 2001-2010, International Business Machines Corporation and *
* Copyright (C) 2001-2011, International Business Machines Corporation and *
* others. All Rights Reserved. *
*******************************************************************************
*/
@ -32,6 +32,7 @@ import com.ibm.icu.text.DateFormat;
import com.ibm.icu.text.DateFormatSymbols;
import com.ibm.icu.text.NumberFormat;
import com.ibm.icu.text.SimpleDateFormat;
import com.ibm.icu.text.TimeZoneFormat;
import com.ibm.icu.util.BuddhistCalendar;
import com.ibm.icu.util.Calendar;
import com.ibm.icu.util.ChineseCalendar;
@ -308,7 +309,7 @@ public class DateFormatTest extends com.ibm.icu.dev.test.TestFmwk {
"Anno Domini", "1997", "August", "0013", "0014", "0014", "0034", "0012", "5130",
"Wednesday", "0225", "0002", "0033", "0002", "PM", "0002", "0002", "Pacific Daylight Time", "1997",
"Wednesday", "1997", "2450674", "52452513", "GMT-07:00", "Pacific Time","Wednesday","August", "3rd quarter", "3rd quarter","United States (Los Angeles)",
"Wednesday", "1997", "2450674", "52452513", "GMT-07:00", "Pacific Time","Wednesday","August", "3rd quarter", "3rd quarter","United States Time (Los Angeles)",
};
assertTrue("data size", EXPECTED.length == COUNT * DateFormat.FIELD_COUNT);
@ -552,7 +553,7 @@ public class DateFormatTest extends com.ibm.icu.dev.test.TestFmwk {
// TODO: Revisit after 3.8
//"y/M/d H:mm vvvv", "pf", "2004/10/31 1:30 Argentina Time", "2004 10 30 21:30 PDT", "2004/10/31 1:30 Argentina Time",
};
expect(ZDATA, en);
expect(ZDATA, en, true);
logln("cross format/parse tests");
final String basepat = "yy/MM/dd H:mm ";
@ -564,6 +565,16 @@ public class DateFormatTest extends com.ibm.icu.dev.test.TestFmwk {
};
final SimpleDateFormat univ = new SimpleDateFormat("yyyy MM dd HH:mm zzz", en);
// To allow cross pattern parsing, we need setParseAllStyles(true) since 4.8
TimeZoneFormat tzfmt = univ.getTimeZoneFormat().cloneAsThawed();
tzfmt.setParseAllStyles(true);
tzfmt.freeze();
univ.setTimeZoneFormat(tzfmt);
for (SimpleDateFormat sdf : formats) {
sdf.setTimeZoneFormat(tzfmt);
}
final String[] times = { "2004 01 02 03:04 PST", "2004 07 08 09:10 PDT" };
for (int i = 0; i < times.length; ++i) {
try {
@ -626,7 +637,7 @@ public class DateFormatTest extends com.ibm.icu.dev.test.TestFmwk {
"vvvv y/M/d H:mm", "pf", "PT 2004/7/1 1:00", "2004 07 01 01:00 PDT", "Pacific Time 2004/7/1 1:00",
};
Locale en = new Locale("en", "", "");
expect(XDATA, en);
expect(XDATA, en, true);
}
public void TestTimeZoneDisplayName() {
@ -675,7 +686,7 @@ public class DateFormatTest extends com.ibm.icu.dev.test.TestFmwk {
{ "en", "America/Los_Angeles", "2004-07-15T00:00:00Z", "zzzz", "Pacific Daylight Time", "America/Los_Angeles" },
{ "en", "America/Los_Angeles", "2004-07-15T00:00:00Z", "v", "PT", "America/Los_Angeles" },
{ "en", "America/Los_Angeles", "2004-07-15T00:00:00Z", "vvvv", "Pacific Time", "America/Los_Angeles" },
{ "en", "America/Los_Angeles", "2004-07-15T00:00:00Z", "VVVV", "United States (Los Angeles)", "America/Los_Angeles" },
{ "en", "America/Los_Angeles", "2004-07-15T00:00:00Z", "VVVV", "United States Time (Los Angeles)", "America/Los_Angeles" },
{ "en_GB", "America/Los_Angeles", "2004-01-15T12:00:00Z", "z", "PST", "America/Los_Angeles" },
{ "en", "America/Phoenix", "2004-01-15T00:00:00Z", "Z", "-0700", "-7:00" },
{ "en", "America/Phoenix", "2004-01-15T00:00:00Z", "ZZZZ", "GMT-07:00", "-7:00" },
@ -689,7 +700,7 @@ public class DateFormatTest extends com.ibm.icu.dev.test.TestFmwk {
{ "en", "America/Phoenix", "2004-07-15T00:00:00Z", "zzzz", "Mountain Standard Time", "America/Phoenix" },
{ "en", "America/Phoenix", "2004-07-15T00:00:00Z", "v", "MST", "America/Phoenix" },
{ "en", "America/Phoenix", "2004-07-15T00:00:00Z", "vvvv", "Mountain Standard Time", "America/Phoenix" },
{ "en", "America/Phoenix", "2004-07-15T00:00:00Z", "VVVV", "United States (Phoenix)", "America/Phoenix" },
{ "en", "America/Phoenix", "2004-07-15T00:00:00Z", "VVVV", "United States Time (Phoenix)", "America/Phoenix" },
{ "en", "America/Argentina/Buenos_Aires", "2004-01-15T00:00:00Z", "Z", "-0300", "-3:00" },
{ "en", "America/Argentina/Buenos_Aires", "2004-01-15T00:00:00Z", "ZZZZ", "GMT-03:00", "-3:00" },
@ -701,9 +712,9 @@ public class DateFormatTest extends com.ibm.icu.dev.test.TestFmwk {
{ "en", "America/Argentina/Buenos_Aires", "2004-07-15T00:00:00Z", "z", "GMT-03:00", "-3:00" },
{ "en", "America/Argentina/Buenos_Aires", "2004-07-15T00:00:00Z", "V", "ART", "-3:00" },
{ "en", "America/Argentina/Buenos_Aires", "2004-07-15T00:00:00Z", "zzzz", "Argentina Time", "-3:00" },
{ "en", "America/Argentina/Buenos_Aires", "2004-07-15T00:00:00Z", "v", "Argentina (Buenos Aires)", "America/Buenos_Aires" },
{ "en", "America/Argentina/Buenos_Aires", "2004-07-15T00:00:00Z", "v", "Argentina Time (Buenos Aires)", "America/Buenos_Aires" },
{ "en", "America/Argentina/Buenos_Aires", "2004-07-15T00:00:00Z", "vvvv", "Argentina Time", "America/Buenos_Aires" },
{ "en", "America/Argentina/Buenos_Aires", "2004-07-15T00:00:00Z", "VVVV", "Argentina (Buenos Aires)", "America/Buenos_Aires" },
{ "en", "America/Argentina/Buenos_Aires", "2004-07-15T00:00:00Z", "VVVV", "Argentina Time (Buenos Aires)", "America/Buenos_Aires" },
{ "en", "America/Buenos_Aires", "2004-01-15T00:00:00Z", "Z", "-0300", "-3:00" },
{ "en", "America/Buenos_Aires", "2004-01-15T00:00:00Z", "ZZZZ", "GMT-03:00", "-3:00" },
@ -715,9 +726,9 @@ public class DateFormatTest extends com.ibm.icu.dev.test.TestFmwk {
{ "en", "America/Buenos_Aires", "2004-07-15T00:00:00Z", "z", "GMT-03:00", "-3:00" },
{ "en", "America/Buenos_Aires", "2004-07-15T00:00:00Z", "V", "ART", "-3:00" },
{ "en", "America/Buenos_Aires", "2004-07-15T00:00:00Z", "zzzz", "Argentina Time", "-3:00" },
{ "en", "America/Buenos_Aires", "2004-07-15T00:00:00Z", "v", "Argentina (Buenos Aires)", "America/Buenos_Aires" },
{ "en", "America/Buenos_Aires", "2004-07-15T00:00:00Z", "v", "Argentina Time (Buenos Aires)", "America/Buenos_Aires" },
{ "en", "America/Buenos_Aires", "2004-07-15T00:00:00Z", "vvvv", "Argentina Time", "America/Buenos_Aires" },
{ "en", "America/Buenos_Aires", "2004-07-15T00:00:00Z", "VVVV", "Argentina (Buenos Aires)", "America/Buenos_Aires" },
{ "en", "America/Buenos_Aires", "2004-07-15T00:00:00Z", "VVVV", "Argentina Time (Buenos Aires)", "America/Buenos_Aires" },
{ "en", "America/Havana", "2004-01-15T00:00:00Z", "Z", "-0500", "-5:00" },
{ "en", "America/Havana", "2004-01-15T00:00:00Z", "ZZZZ", "GMT-05:00", "-5:00" },
@ -743,9 +754,9 @@ public class DateFormatTest extends com.ibm.icu.dev.test.TestFmwk {
{ "en", "Australia/ACT", "2004-07-15T00:00:00Z", "z", "GMT+10:00", "+10:00" },
{ "en", "Australia/ACT", "2004-07-15T00:00:00Z", "V", "AEST", "+10:00" },
{ "en", "Australia/ACT", "2004-07-15T00:00:00Z", "zzzz", "Australian Eastern Standard Time", "+10:00" },
{ "en", "Australia/ACT", "2004-07-15T00:00:00Z", "v", "Australia (Sydney)", "Australia/Sydney" },
{ "en", "Australia/ACT", "2004-07-15T00:00:00Z", "v", "Australia Time (Sydney)", "Australia/Sydney" },
{ "en", "Australia/ACT", "2004-07-15T00:00:00Z", "vvvv", "Eastern Australia Time", "Australia/Sydney" },
{ "en", "Australia/ACT", "2004-07-15T00:00:00Z", "VVVV", "Australia (Sydney)", "Australia/Sydney" },
{ "en", "Australia/ACT", "2004-07-15T00:00:00Z", "VVVV", "Australia Time (Sydney)", "Australia/Sydney" },
{ "en", "Australia/Sydney", "2004-01-15T00:00:00Z", "Z", "+1100", "+11:00" },
{ "en", "Australia/Sydney", "2004-01-15T00:00:00Z", "ZZZZ", "GMT+11:00", "+11:00" },
@ -757,12 +768,12 @@ public class DateFormatTest extends com.ibm.icu.dev.test.TestFmwk {
{ "en", "Australia/Sydney", "2004-07-15T00:00:00Z", "z", "GMT+10:00", "+10:00" },
{ "en", "Australia/Sydney", "2004-07-15T00:00:00Z", "V", "AEST", "+10:00" },
{ "en", "Australia/Sydney", "2004-07-15T00:00:00Z", "zzzz", "Australian Eastern Standard Time", "+10:00" },
{ "en", "Australia/Sydney", "2004-07-15T00:00:00Z", "v", "Australia (Sydney)", "Australia/Sydney" },
{ "en", "Australia/Sydney", "2004-07-15T00:00:00Z", "v", "Australia Time (Sydney)", "Australia/Sydney" },
{ "en", "Australia/Sydney", "2004-07-15T00:00:00Z", "vvvv", "Eastern Australia Time", "Australia/Sydney" },
{ "en", "Australia/Sydney", "2004-07-15T00:00:00Z", "VVVV", "Australia (Sydney)", "Australia/Sydney" },
{ "en", "Australia/Sydney", "2004-07-15T00:00:00Z", "VVVV", "Australia Time (Sydney)", "Australia/Sydney" },
{ "en", "Europe/London", "2004-01-15T00:00:00Z", "Z", "+0000", "+0:00" },
{ "en", "Europe/London", "2004-01-15T00:00:00Z", "ZZZZ", "GMT+00:00", "+0:00" },
{ "en", "Europe/London", "2004-01-15T00:00:00Z", "ZZZZ", "GMT", "+0:00" },
{ "en", "Europe/London", "2004-01-15T00:00:00Z", "z", "GMT", "+0:00" },
{ "en", "Europe/London", "2004-01-15T00:00:00Z", "V", "GMT", "+0:00" },
{ "en", "Europe/London", "2004-01-15T00:00:00Z", "zzzz", "Greenwich Mean Time", "+0:00" },
@ -873,9 +884,9 @@ public class DateFormatTest extends com.ibm.icu.dev.test.TestFmwk {
{ "de", "Australia/Sydney", "2004-07-15T00:00:00Z", "vvvv", "Australien (Sydney)", "Australia/Sydney" },
{ "de", "Europe/London", "2004-01-15T00:00:00Z", "Z", "+0000", "+0:00" },
{ "de", "Europe/London", "2004-01-15T00:00:00Z", "ZZZZ", "GMT+00:00", "+0:00" },
{ "de", "Europe/London", "2004-01-15T00:00:00Z", "z", "GMT+00:00", "+0:00" },
{ "de", "Europe/London", "2004-01-15T00:00:00Z", "zzzz", "GMT+00:00", "+0:00" },
{ "de", "Europe/London", "2004-01-15T00:00:00Z", "ZZZZ", "GMT", "+0:00" },
{ "de", "Europe/London", "2004-01-15T00:00:00Z", "z", "GMT", "+0:00" },
{ "de", "Europe/London", "2004-01-15T00:00:00Z", "zzzz", "GMT", "+0:00" },
{ "de", "Europe/London", "2004-07-15T00:00:00Z", "Z", "+0100", "+1:00" },
{ "de", "Europe/London", "2004-07-15T00:00:00Z", "ZZZZ", "GMT+01:00", "+1:00" },
{ "de", "Europe/London", "2004-07-15T00:00:00Z", "z", "GMT+01:00", "+1:00" },
@ -977,8 +988,8 @@ public class DateFormatTest extends com.ibm.icu.dev.test.TestFmwk {
{ "zh", "Australia/Sydney", "2004-07-15T00:00:00Z", "vvvv", "\u6fb3\u5927\u5229\u4e9a\u4e1c\u90e8\u65f6\u95f4", "Australia/Sydney" },
{ "zh", "Europe/London", "2004-01-15T00:00:00Z", "Z", "+0000", "+0:00" },
{ "zh", "Europe/London", "2004-01-15T00:00:00Z", "ZZZZ", GMT_ZH+"+0000", "+0:00" },
{ "zh", "Europe/London", "2004-01-15T00:00:00Z", "z", GMT_ZH+"+0000", "+0:00" },
{ "zh", "Europe/London", "2004-01-15T00:00:00Z", "ZZZZ", GMT_ZH, "+0:00" },
{ "zh", "Europe/London", "2004-01-15T00:00:00Z", "z", GMT_ZH, "+0:00" },
{ "zh", "Europe/London", "2004-01-15T00:00:00Z", "V", "GMT", "+0:00" },
{ "zh", "Europe/London", "2004-01-15T00:00:00Z", "zzzz", "\u683C\u6797\u5C3C\u6CBB\u6807\u51C6\u65F6\u95F4", "+0:00" },
{ "zh", "Europe/London", "2004-07-15T00:00:00Z", "Z", "+0100", "+1:00" },
@ -1082,9 +1093,9 @@ public class DateFormatTest extends com.ibm.icu.dev.test.TestFmwk {
{ "hi", "Australia/Sydney", "2004-07-15T00:00:00Z", "vvvv", "\u0911\u0938\u094d\u091f\u094d\u0930\u0947\u0932\u093f\u092f\u093e (\u0938\u093f\u0921\u0928\u0940)", "Australia/Sydney" },
{ "hi", "Europe/London", "2004-01-15T00:00:00Z", "Z", "+0000", "+0:00" },
{ "hi", "Europe/London", "2004-01-15T00:00:00Z", "ZZZZ", "GMT+\u0966\u0966:\u0966\u0966", "+0:00" },
{ "hi", "Europe/London", "2004-01-15T00:00:00Z", "z", "GMT+\u0966\u0966:\u0966\u0966", "+0:00" },
{ "hi", "Europe/London", "2004-01-15T00:00:00Z", "zzzz", "GMT+\u0966\u0966:\u0966\u0966", "+0:00" },
{ "hi", "Europe/London", "2004-01-15T00:00:00Z", "ZZZZ", "GMT", "+0:00" },
{ "hi", "Europe/London", "2004-01-15T00:00:00Z", "z", "GMT", "+0:00" },
{ "hi", "Europe/London", "2004-01-15T00:00:00Z", "zzzz", "GMT", "+0:00" },
{ "hi", "Europe/London", "2004-07-15T00:00:00Z", "Z", "+0100", "+1:00" },
{ "hi", "Europe/London", "2004-07-15T00:00:00Z", "ZZZZ", "GMT+\u0966\u0967:\u0966\u0966", "+1:00" },
{ "hi", "Europe/London", "2004-07-15T00:00:00Z", "z", "GMT+\u0966\u0967:\u0966\u0966", "+1:00" },
@ -1188,8 +1199,8 @@ public class DateFormatTest extends com.ibm.icu.dev.test.TestFmwk {
{ "bg", "Australia/Sydney", "2004-07-15T00:00:00Z", "vvvv", "\u0410\u0432\u0441\u0442\u0440\u0430\u043b\u0438\u044f (\u0421\u0438\u0434\u043D\u0438)", "Australia/Sydney" },
{ "bg", "Europe/London", "2004-01-15T00:00:00Z", "Z", "+0000", "+0:00" },
{ "bg", "Europe/London", "2004-01-15T00:00:00Z", "ZZZZ", GMT_BG+"+0000", "+0:00" },
{ "bg", "Europe/London", "2004-01-15T00:00:00Z", "z", GMT_BG+"+0000", "+0:00" },
{ "bg", "Europe/London", "2004-01-15T00:00:00Z", "ZZZZ", GMT_BG, "+0:00" },
{ "bg", "Europe/London", "2004-01-15T00:00:00Z", "z", GMT_BG, "+0:00" },
{ "bg", "Europe/London", "2004-01-15T00:00:00Z", "zzzz", "\u0427\u0430\u0441\u043E\u0432\u0430 \u0437\u043E\u043D\u0430 \u0413\u0440\u0438\u043D\u0443\u0438\u0447", "+0:00" },
{ "bg", "Europe/London", "2004-07-15T00:00:00Z", "Z", "+0100", "+1:00" },
{ "bg", "Europe/London", "2004-07-15T00:00:00Z", "ZZZZ", GMT_BG+"+0100", "+1:00" },
@ -1297,8 +1308,8 @@ public class DateFormatTest extends com.ibm.icu.dev.test.TestFmwk {
{ "ja", "Australia/Sydney", "2004-07-15T00:00:00Z", "vvvv", "\u30aa\u30fc\u30b9\u30c8\u30e9\u30ea\u30a2 (\u30b7\u30c9\u30cb\u30fc)", "Australia/Sydney" },
{ "ja", "Europe/London", "2004-01-15T00:00:00Z", "Z", "+0000", "+0:00" },
{ "ja", "Europe/London", "2004-01-15T00:00:00Z", "ZZZZ", "GMT+00:00", "+0:00" },
{ "ja", "Europe/London", "2004-01-15T00:00:00Z", "z", "GMT+00:00", "+0:00" },
{ "ja", "Europe/London", "2004-01-15T00:00:00Z", "ZZZZ", "GMT", "+0:00" },
{ "ja", "Europe/London", "2004-01-15T00:00:00Z", "z", "GMT", "+0:00" },
{ "ja", "Europe/London", "2004-01-15T00:00:00Z", "V", "GMT", "+0:00" },
{ "ja", "Europe/London", "2004-01-15T00:00:00Z", "zzzz", "\u30B0\u30EA\u30CB\u30C3\u30B8\u6A19\u6E96\u6642", "+0:00" },
{ "ja", "Europe/London", "2004-07-15T00:00:00Z", "Z", "+0100", "+1:00" },
@ -1402,9 +1413,9 @@ public class DateFormatTest extends com.ibm.icu.dev.test.TestFmwk {
{ "si", "Australia/Sydney", "2004-07-15T00:00:00Z", "vvvv", "AU (Sydney)", "Australia/Sydney" },
{ "si", "Europe/London", "2004-01-15T00:00:00Z", "Z", "+0000", "+0:00" },
{ "si", "Europe/London", "2004-01-15T00:00:00Z", "ZZZZ", "GMT+00:00", "+0:00" },
{ "si", "Europe/London", "2004-01-15T00:00:00Z", "z", "GMT+00:00", "+0:00" },
{ "si", "Europe/London", "2004-01-15T00:00:00Z", "zzzz", "GMT+00:00", "+0:00" },
{ "si", "Europe/London", "2004-01-15T00:00:00Z", "ZZZZ", "GMT", "+0:00" },
{ "si", "Europe/London", "2004-01-15T00:00:00Z", "z", "GMT", "+0:00" },
{ "si", "Europe/London", "2004-01-15T00:00:00Z", "zzzz", "GMT", "+0:00" },
{ "si", "Europe/London", "2004-07-15T00:00:00Z", "Z", "+0100", "+1:00" },
{ "si", "Europe/London", "2004-07-15T00:00:00Z", "ZZZZ", "GMT+01:00", "+1:00" },
{ "si", "Europe/London", "2004-07-15T00:00:00Z", "z", "GMT+01:00", "+1:00" },
@ -1867,11 +1878,11 @@ public class DateFormatTest extends com.ibm.icu.dev.test.TestFmwk {
Date greenwichdate = greenwichcalendar.getTime();
// format every way
String DATA[] = {
"simple format: ", "04/04/97 23:00 GMT+00:00",
"simple format: ", "04/04/97 23:00 GMT",
"MM/dd/yy HH:mm zzz", "full format: ",
"Friday, April 4, 1997 11:00:00 o'clock PM GMT+00:00",
"Friday, April 4, 1997 11:00:00 o'clock PM GMT",
"EEEE, MMMM d, yyyy h:mm:ss 'o''clock' a zzz",
"long format: ", "April 4, 1997 11:00:00 PM GMT+00:00",
"long format: ", "April 4, 1997 11:00:00 PM GMT",
"MMMM d, yyyy h:mm:ss a z", "default format: ",
"04-Apr-97 11:00:00 PM", "dd-MMM-yy h:mm:ss a",
"short format: ", "4/4/97 11:00 PM",
@ -2511,28 +2522,6 @@ public class DateFormatTest extends com.ibm.icu.dev.test.TestFmwk {
TimeZone.setDefault(oldtz);
throw new IllegalStateException(e.getMessage());
}
// create DFS that recognizes our bogus time zone, sortof
DateFormatSymbols xsym = new DateFormatSymbols();
String[][] tzids = xsym.getZoneStrings();
if (tzids.length > 0) { // let's hope!
tzids[0][1] = "DBDY"; // change a local name
logln("replaced '" + tzids[0][0] + "' with DBDY");
xsym.setZoneStrings(tzids);
fmt.setDateFormatSymbols(xsym);
try {
fmt.parse(text);
logln("we parsed DBDY (as GMT, but still...)");
}
catch (ParseException e) {
errln("hey, still didn't recognize DBDY");
}
finally {
TimeZone.setDefault(oldtz);
}
}
}
{
@ -2893,7 +2882,7 @@ public class DateFormatTest extends com.ibm.icu.dev.test.TestFmwk {
"HH:mm:ss zzzz", "10:20:30 UTC", "10:20:30 +0000", // standalone "UTC"
"ZZZZ HH:mm:ss", "UT 10:20:30", "10:20:30 +0000",
"V HH:mm:ss", "UT+0130 10:20:30", "10:20:30 +0130",
"V HH:mm:ss", "UTC+0130 10:20:30", null, // UTC+0130 is not a supported pattern
"V HH:mm:ss", "UTC+0130 10:20:30", "10:20:30 +0130",
"HH mm Z ss", "10 20 GMT-1100 30", "10:20:30 -1100",
};
expectParse(DATA, new Locale("en", "", ""));
@ -3066,6 +3055,10 @@ public class DateFormatTest extends com.ibm.icu.dev.test.TestFmwk {
* -- compare r0 and A, fail if not equal
*/
void expect(String[] data, Locale loc) {
expect(data, loc, false);
}
void expect(String[] data, Locale loc, boolean parseAllTZStyles) {
int i = 1;
SimpleDateFormat univ = new SimpleDateFormat("EE G yyyy MM dd HH:mm:ss.SSS zzz", loc);
String currentPat = null;
@ -3073,6 +3066,13 @@ public class DateFormatTest extends com.ibm.icu.dev.test.TestFmwk {
while (i<data.length) {
SimpleDateFormat fmt = new SimpleDateFormat("", loc);
if (parseAllTZStyles) {
TimeZoneFormat tzfmt = fmt.getTimeZoneFormat().cloneAsThawed();
tzfmt.setParseAllStyles(true).freeze();
fmt.setTimeZoneFormat(tzfmt);
}
String pattern = data[i++];
if (pattern != null) {
fmt.applyPattern(pattern);

View File

@ -109,7 +109,7 @@ public class DateTimeGeneratorTest extends TestFmwk {
{"EyyyyMMMddhhmmss", "Thu, Oct 14, 1999 6:58:59 AM"}, // (fixed expected result per ticket 6872<-7180)
{"hmm", "6:58 AM"},
{"hhmm", "6:58 AM"}, // (fixed expected result per ticket 6872<-7180)
{"hhmmVVVV", "6:58 AM GMT+00:00"}, // (fixed expected result per ticket 6872<-7180)
{"hhmmVVVV", "6:58 AM GMT"}, // (fixed expected result per ticket 6872<-7180)
};
for (int i = 0; i < tests.length; ++i) {
final String testSkeleton = tests[i][0];

View File

@ -14,6 +14,7 @@ import java.util.Set;
import com.ibm.icu.lang.UCharacter;
import com.ibm.icu.text.SimpleDateFormat;
import com.ibm.icu.text.TimeZoneFormat;
import com.ibm.icu.util.BasicTimeZone;
import com.ibm.icu.util.Calendar;
import com.ibm.icu.util.SimpleTimeZone;
@ -29,6 +30,7 @@ public class TimeZoneFormatTest extends com.ibm.icu.dev.test.TestFmwk {
}
private static final String[] PATTERNS = {"z", "zzzz", "Z", "ZZZZ", "v", "vvvv", "V", "VVVV"};
boolean REALLY_VERBOSE_LOG = false;
/*
* Test case for checking if a TimeZone is properly set in the result calendar
@ -82,7 +84,12 @@ public class TimeZoneFormatTest extends com.ibm.icu.dev.test.TestFmwk {
// Run the roundtrip test
for (int locidx = 0; locidx < LOCALES.length; locidx++) {
logln("Locale: " + LOCALES[locidx].toString());
String localGMTString = TimeZoneFormat.getInstance(LOCALES[locidx]).formatOffsetLocalizedGMT(0);
for (int patidx = 0; patidx < PATTERNS.length; patidx++) {
logln(" pattern: " + PATTERNS[patidx]);
SimpleDateFormat sdf = new SimpleDateFormat(PATTERNS[patidx], LOCALES[locidx]);
for (int tzidx = 0; tzidx < tzids.length; tzidx++) {
@ -115,13 +122,13 @@ public class TimeZoneFormatTest extends com.ibm.icu.dev.test.TestFmwk {
if (PATTERNS[patidx].equals("VVVV")) {
// Location: time zone rule must be preserved except
// zones not actually associated with a specific location.
// Time zones in this category do not have "/" in its ID.
String canonicalID = TimeZone.getCanonicalID(tzids[tzidx]);
boolean hasNoLocation = TimeZone.getRegion(tzids[tzidx]).equals("001");
if (canonicalID != null && !outtz.getID().equals(canonicalID)) {
// Canonical ID did not match - check the rules
boolean bFailure = false;
if ((tz instanceof BasicTimeZone) && (outtz instanceof BasicTimeZone)) {
bFailure = !(canonicalID.indexOf('/') == -1)
bFailure = !hasNoLocation
&& !((BasicTimeZone)outtz).hasEquivalentTransitions(tz, low, high);
}
if (bFailure) {
@ -129,7 +136,7 @@ public class TimeZoneFormatTest extends com.ibm.icu.dev.test.TestFmwk {
+ ", locale=" + LOCALES[locidx] + ", pattern=" + PATTERNS[patidx]
+ ", time=" + DATES[datidx].getTime() + ", str=" + tzstr
+ ", outtz=" + outtz.getID());
} else {
} else if (REALLY_VERBOSE_LOG) {
logln("Canonical round trip failed (as expected); tz=" + tzids[tzidx]
+ ", locale=" + LOCALES[locidx] + ", pattern=" + PATTERNS[patidx]
+ ", time=" + DATES[datidx].getTime() + ", str=" + tzstr
@ -145,7 +152,7 @@ public class TimeZoneFormatTest extends com.ibm.icu.dev.test.TestFmwk {
}
}
if (numDigits >= 3) {
if (tzstr.equals(localGMTString) || numDigits >= 3) {
// Localized GMT or RFC: total offset (raw + dst) must be preserved.
int inOffset = inOffsets[0] + inOffsets[1];
int outOffset = outOffsets[0] + outOffsets[1];
@ -162,10 +169,12 @@ public class TimeZoneFormatTest extends com.ibm.icu.dev.test.TestFmwk {
&& tzids[tzidx].startsWith("SystemV/")) {
// JDK uses rule SystemV for these zones while
// ICU handles these zones as aliases of existing time zones
logln("Raw offset round trip failed; tz=" + tzids[tzidx]
+ ", locale=" + LOCALES[locidx] + ", pattern=" + PATTERNS[patidx]
+ ", time=" + DATES[datidx].getTime() + ", str=" + tzstr
+ ", inRawOffset=" + inOffsets[0] + ", outRawOffset=" + outOffsets[0]);
if (REALLY_VERBOSE_LOG) {
logln("Raw offset round trip failed; tz=" + tzids[tzidx]
+ ", locale=" + LOCALES[locidx] + ", pattern=" + PATTERNS[patidx]
+ ", time=" + DATES[datidx].getTime() + ", str=" + tzstr
+ ", inRawOffset=" + inOffsets[0] + ", outRawOffset=" + outOffsets[0]);
}
} else {
errln("Raw offset round trip failed; tz=" + tzids[tzidx]
@ -217,7 +226,6 @@ public class TimeZoneFormatTest extends com.ibm.icu.dev.test.TestFmwk {
final String BASEPATTERN = "yyyy-MM-dd'T'HH:mm:ss.SSS";
ULocale[] LOCALES = null;
boolean REALLY_VERBOSE = false;
// timer for performance analysis
long[] times = new long[PATTERNS.length];
@ -317,7 +325,7 @@ public class TimeZoneFormatTest extends com.ibm.icu.dev.test.TestFmwk {
.append(", diff=").append(restime - testTimes[testidx]);
if (expectedRoundTrip[testidx]) {
errln("FAIL: " + msg.toString());
} else if (REALLY_VERBOSE) {
} else if (REALLY_VERBOSE_LOG) {
logln(msg.toString());
}
}

View File

@ -1,6 +1,6 @@
/*
*******************************************************************************
* Copyright (c) 2004-2010, International Business Machines
* Copyright (c) 2004-2011, International Business Machines
* Corporation and others. All Rights Reserved.
*******************************************************************************
*
@ -13,6 +13,9 @@ import java.util.HashMap;
import java.util.Locale;
import com.ibm.icu.impl.DateNumberFormat;
import com.ibm.icu.impl.TimeZoneGenericNames;
import com.ibm.icu.impl.TimeZoneGenericNames.GenericNameType;
import com.ibm.icu.impl.Utility;
import com.ibm.icu.text.ChineseDateFormat;
import com.ibm.icu.text.ChineseDateFormatSymbols;
import com.ibm.icu.text.CurrencyPluralInfo;
@ -31,11 +34,15 @@ import com.ibm.icu.text.RuleBasedNumberFormat;
import com.ibm.icu.text.SelectFormat;
import com.ibm.icu.text.SimpleDateFormat;
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.util.Calendar;
import com.ibm.icu.util.DateInterval;
import com.ibm.icu.util.GregorianCalendar;
import com.ibm.icu.util.TimeUnit;
import com.ibm.icu.util.TimeUnitAmount;
import com.ibm.icu.util.TimeZone;
import com.ibm.icu.util.ULocale;
/**
@ -2184,6 +2191,119 @@ public class FormatTests
}
}
public static class TimeZoneNamesHandler implements SerializableTest.Handler {
public Object[] getTestObjects() {
return new Object[] {
TimeZoneNames.getInstance(ULocale.ENGLISH),
TimeZoneNames.getInstance(ULocale.JAPAN)
};
}
public boolean hasSameBehavior(Object a, Object b) {
TimeZoneNames tzna = (TimeZoneNames)a;
TimeZoneNames tznb = (TimeZoneNames)b;
final String tzid = "America/Los_Angeles";
String eca = tzna.getExemplarLocationName(tzid);
String ecb = tznb.getExemplarLocationName(tzid);
if (!eca.equals(ecb)) {
return false;
}
final String mzID = "America_Pacific";
final String region = "US";
String refza = tzna.getReferenceZoneID(mzID, region);
String refzb = tznb.getReferenceZoneID(mzID, region);
if (!refza.equals(refzb)) {
return false;
}
return true;
}
}
public static class TimeZoneGenericNamesHandler implements SerializableTest.Handler {
public Object[] getTestObjects() {
return new Object[] {
TimeZoneGenericNames.getInstance(ULocale.ENGLISH),
TimeZoneGenericNames.getInstance(ULocale.JAPAN)
};
}
public boolean hasSameBehavior(Object a, Object b) {
TimeZoneGenericNames tzgna = (TimeZoneGenericNames)a;
TimeZoneGenericNames tzgnb = (TimeZoneGenericNames)b;
final String[] TZIDS = {
"America/Los_Angeles",
"America/Argentina/Buenos_Aires",
"Etc/GMT"
};
final long[] DATES = {
1277942400000L, // 2010-07-01 00:00:00 GMT
1293840000000L, // 2011-01-01 00:00:00 GMT
};
for (String tzid : TZIDS) {
TimeZone tz = TimeZone.getTimeZone(tzid);
for (GenericNameType nt : GenericNameType.values()) {
for (long date : DATES) {
String nameA = tzgna.getDisplayName(tz, nt, date);
String nameB = tzgnb.getDisplayName(tz, 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";
public Object[] getTestObjects() {
TimeZoneFormat tzfmt = TimeZoneFormat.getInstance(ULocale.ENGLISH).cloneAsThawed();
tzfmt.setGMTPattern(CUSTOM_GMT_PATTERN);
return new Object[] {tzfmt};
}
public boolean hasSameBehavior(Object a, Object b) {
TimeZoneFormat tzfa = (TimeZoneFormat)a;
TimeZoneFormat tzfb = (TimeZoneFormat)b;
if (!tzfa.getGMTPattern().equals(tzfb.getGMTPattern())) {
return false;
}
final int offset = -5 * 60 * 60 * 1000;
String gmta = tzfa.formatOffsetLocalizedGMT(offset);
String gmtb = tzfb.formatOffsetLocalizedGMT(offset);
if (!gmta.equals(gmtb)) {
return false;
}
long now = System.currentTimeMillis();
TimeZone tz = TimeZone.getTimeZone("America/Los_Angeles");
String genloca = tzfa.format(Style.GENERIC_LOCATION, tz, now);
String genlocb = tzfb.format(Style.GENERIC_LOCATION, tz, now);
if (!genloca.equals(genlocb)) {
return false;
}
return true;
}
}
public static void main(String[] args)
{
// nothing needed...

View File

@ -1,6 +1,6 @@
/*
*******************************************************************************
* Copyright (C) 1996-2009, International Business Machines Corporation and *
* Copyright (C) 1996-2011, International Business Machines Corporation and *
* others. All Rights Reserved. *
*******************************************************************************
*
@ -672,6 +672,9 @@ public class SerializableTest extends TestFmwk.TestGroup
map.put("com.ibm.icu.text.PluralRules", new FormatTests.PluralRulesHandler());
map.put("com.ibm.icu.text.TimeUnitFormat", new FormatTests.TimeUnitFormatHandler());
map.put("com.ibm.icu.text.SelectFormat", new FormatTests.SelectFormatHandler());
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.util.Calendar", new CalendarTests.CalendarHandler());
map.put("com.ibm.icu.util.BuddhistCalendar", new CalendarTests.BuddhistCalendarHandler());

View File

@ -378,8 +378,8 @@ public class TimeZoneTest extends TestFmwk
// V and VVVV
Boolean.FALSE, new Integer(TimeZone.SHORT_COMMONLY_USED), "PST",
Boolean.TRUE, new Integer(TimeZone.SHORT_COMMONLY_USED), "PDT",
Boolean.FALSE, new Integer(TimeZone.GENERIC_LOCATION), "United States (Los Angeles)",
Boolean.TRUE, new Integer(TimeZone.GENERIC_LOCATION), "United States (Los Angeles)",
Boolean.FALSE, new Integer(TimeZone.GENERIC_LOCATION), "United States Time (Los Angeles)",
Boolean.TRUE, new Integer(TimeZone.GENERIC_LOCATION), "United States Time (Los Angeles)",
};
for (int i=0; i<DATA.length; i+=3) {
@ -476,14 +476,24 @@ public class TimeZoneTest extends TestFmwk
ULocale locale = new ULocale(locales[j]);
for (int i = 0; i < timezones.length; ++i) {
TimeZone tz = TimeZone.getTimeZone(timezones[i]);
String displayName0 = tz.getDisplayName(locale); // doesn't work???
String displayName0 = tz.getDisplayName(locale);
SimpleDateFormat dt = new SimpleDateFormat("vvvv", locale);
dt.setTimeZone(tz);
String displayName1 = dt.format(now); // date value _does_ matter if we fallback to GMT
logln(locale.getDisplayName() + ", " + tz.getID() + ": " + displayName0);
if (!displayName1.equals(displayName0)) {
errln(locale.getDisplayName() + ", " + tz.getID() +
": expected " + displayName1 + " but got: " + displayName0);
// This could happen when the date used is in DST,
// because TimeZone.getDisplayName(ULocale) may use
// localized GMT format for the time zone's standard
// time.
if (tz.inDaylightTime(now)) {
// Try getDisplayName with daylight argument
displayName0 = tz.getDisplayName(true, TimeZone.LONG_GENERIC, locale);
}
if (!displayName1.equals(displayName0)) {
errln(locale.getDisplayName() + ", " + tz.getID() +
": expected " + displayName1 + " but got: " + displayName0);
}
}
}
}