/*
* @(#)TimeZone.java 1.51 00/01/19
*
- * Copyright (C) 1996-2011, International Business Machines
+ * Copyright (C) 1996-2013, International Business Machines
* Corporation and others. All Rights Reserved.
*/
import java.util.Locale;
import java.util.MissingResourceException;
import java.util.Set;
+import java.util.logging.Logger;
import com.ibm.icu.impl.Grego;
import com.ibm.icu.impl.ICUConfig;
-import com.ibm.icu.impl.ICULogger;
+import com.ibm.icu.impl.ICUResourceBundle;
import com.ibm.icu.impl.JavaTimeZone;
import com.ibm.icu.impl.TimeZoneAdapter;
import com.ibm.icu.impl.ZoneMeta;
* </pre>
* </blockquote>
* You can use the {@link #getAvailableIDs()} method to iterate through
- * all the supported time zone IDs. You can then choose a
+ * all the supported time zone IDs, or getCanonicalID method to check
+ * if a time zone ID is supported or not. You can then choose a
* supported ID to get a <code>TimeZone</code>.
* If the time zone you want is not represented by one of the
* supported IDs, then you can create a custom time zone ID with
*
* For example, you might specify GMT+14:00 as a custom
* time zone ID. The <code>TimeZone</code> that is returned
- * when you specify a custom time zone ID does not include
- * daylight savings time.
+ * when you specify a custom time zone ID uses the specified
+ * offset from GMT(=UTC) and does not observe daylight saving
+ * time. For example, you might specify GMT+14:00 as a custom
+ * time zone ID to create a TimeZone representing 14 hours ahead
+ * of GMT (with no daylight saving time). In addition,
+ * <code>getCanonicalID</code> can also be used to
+ * normalize a custom time zone ID.
*
* <p>For compatibility with JDK 1.1.x, some other three-letter time zone IDs
* (such as "PST", "CTT", "AST") are also supported. However, <strong>their
* @author Mark Davis, David Goldsmith, Chen-Lieh Huang, Alan Liu
* @stable ICU 2.0
*/
-abstract public class TimeZone implements Serializable, Cloneable {
+abstract public class TimeZone implements Serializable, Cloneable, Freezable<TimeZone> {
/**
- * {@icu} A logger for TimeZone. Will be null if logging is not on by way of system
- * property: "icu4j.debug.logging"
- * @draft ICU 4.4
- * @provisional This API might change or be removed in a future release.
+ * Logger instance for this class
*/
- public static ICULogger TimeZoneLogger = ICULogger.getICULogger(TimeZone.class.getName());
+ private static final Logger LOGGER = Logger.getLogger("com.ibm.icu.util.TimeZone");
// using serialver from jdk1.4.2_05
private static final long serialVersionUID = -744942128318337471L;
public TimeZone() {
}
+ /**
+ * Constructing a TimeZone with the given time zone ID.
+ * @param ID the time zone ID.
+ * @internal
+ * @deprecated This API is ICU internal only.
+ */
+ protected TimeZone(String ID) {
+ if (ID == null) {
+ throw new NullPointerException();
+ }
+ this.ID = ID;
+ }
+
/**
* {@icu} A time zone implementation type indicating ICU's own TimeZone used by
* <code>getTimeZone</code>, <code>setDefaultTimeZoneType</code>
* {@icu} The time zone ID reserved for unknown time zone.
* @see #getTimeZone(String)
*
- * @draft ICU 4.8
- * @provisional This API might change or be removed in a future release.
+ * @stable ICU 4.8
*/
public static final String UNKNOWN_ZONE_ID = "Etc/Unknown";
+ /**
+ * The canonical ID for GMT(UTC) time zone.
+ */
+ static final String GMT_ZONE_ID = "Etc/GMT";
+
+ /**
+ * {@icu} The immutable (frozen) "unknown" time zone.
+ * It behaves like the GMT/UTC time zone but has the UNKNOWN_ZONE_ID = "Etc/Unknown".
+ * {@link TimeZone#getTimeZone(String)} returns a mutable clone of this
+ * time zone if the input ID is not recognized.
+ *
+ * @see #UNKNOWN_ZONE_ID
+ * @see #getTimeZone(String)
+ *
+ * @stable ICU 49
+ */
+ public static final TimeZone UNKNOWN_ZONE = new SimpleTimeZone(0, UNKNOWN_ZONE_ID).freeze();
+
+ /**
+ * {@icu} The immutable GMT (=UTC) time zone. Its ID is "Etc/GMT".
+ *
+ * @stable ICU 49
+ */
+ public static final TimeZone GMT_ZONE = new SimpleTimeZone(0, GMT_ZONE_ID).freeze();
+
/**
* {@icu} System time zone type constants used by filtering zones in
* {@link TimeZone#getAvailableIDs(SystemTimeZoneType, String, Integer)}
*
- * @draft ICU 4.8
- * @provisional This API might change or be removed in a future release.
+ * @stable ICU 4.8
*/
public enum SystemTimeZoneType {
/**
* Any system zones.
- * @draft ICU 4.8
+ * @stable ICU 4.8
* @provisional This API might change or be removed in a future release.
*/
ANY,
/**
* Canonical system zones.
- * @draft ICU 4.8
+ * @stable ICU 4.8
* @provisional This API might change or be removed in a future release.
*/
CANONICAL,
/**
* Canonical system zones associated with actual locations.
- * @draft ICU 4.8
+ * @stable ICU 4.8
* @provisional This API might change or be removed in a future release.
*/
CANONICAL_LOCATION,
if (ID == null) {
throw new NullPointerException();
}
+ if (isFrozen()) {
+ throw new UnsupportedOperationException("Attempt to modify a frozen TimeZone instance.");
+ }
this.ID = ID;
}
if (daylight && timeType.value == TimeType.STANDARD ||
!daylight && timeType.value == TimeType.DAYLIGHT) {
int offset = daylight ? getRawOffset() + getDSTSavings() : getRawOffset();
- result = tzfmt.formatOffsetLocalizedGMT(offset);
+ result = (style == SHORT_GENERIC) ?
+ tzfmt.formatOffsetShortLocalizedGMT(offset) : tzfmt.formatOffsetLocalizedGMT(offset);
}
} else if (style == LONG_GMT || style == SHORT_GMT) {
result = tzfmt.formatOffsetLocalizedGMT(offset);
break;
case SHORT_GMT:
- result = tzfmt.formatOffsetRFC822(offset);
+ result = tzfmt.formatOffsetISO8601Basic(offset, false, false, false);
break;
}
} else {
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;
+ nameType = daylight ? NameType.SHORT_DAYLIGHT : NameType.SHORT_STANDARD;
break;
}
result = tznames.getDisplayName(ZoneMeta.getCanonicalCLDRID(this), nameType, date);
// Fallback to localized GMT
TimeZoneFormat tzfmt = TimeZoneFormat.getInstance(locale);
int offset = daylight && useDaylightTime() ? getRawOffset() + getDSTSavings() : getRawOffset();
- result = tzfmt.formatOffsetLocalizedGMT(offset);
+ result = (style == LONG) ?
+ tzfmt.formatOffsetLocalizedGMT(offset) : tzfmt.formatOffsetShortLocalizedGMT(offset);
}
}
assert(result != null);
*/
abstract public boolean useDaylightTime();
+ /**
+ * Queries if this time zone is in daylight saving time or will observe
+ * daylight saving time at any future time.
+ * <p>The default implementation in this class returns <code>true</code> if {@link #useDaylightTime()}
+ * or {@link #inDaylightTime(Date) inDaylightTime(new Date())} returns <code>true</code>.
+ * <p>
+ * <strong>Note:</strong> This method was added for JDK compatibility support.
+ * The JDK's <code>useDaylightTime()</code> only checks the last known rule(s), therefore
+ * it may return false even the zone observes daylight saving time currently. JDK added
+ * <code>observesDaylightTime()</code> to resolve the issue. In ICU, {@link #useDaylightTime()}
+ * works differently. The ICU implementation checks if the zone uses daylight saving time
+ * in the current calendar year. Therefore, it will never return <code>false</code> if
+ * daylight saving time is currently used.
+ * <p>
+ * ICU's TimeZone subclass implementations override this method to support the same behavior
+ * with JDK's <code>observesDaylightSavingTime()</code>. Unlike {@link #useDaylightTime()},
+ * the implementation does not take past daylight saving time into account, so
+ * that this method may return <code>false</code> even when {@link #useDaylightTime()} returns
+ * <code>true</code>.
+ *
+ * @return <code>true</code> if this time zone is in daylight saving time or will observe
+ * daylight saving time at any future time.
+ * @see #useDaylightTime
+ * @stable ICU 49
+ */
+ public boolean observesDaylightTime() {
+ return useDaylightTime() || inDaylightTime(new Date());
+ }
+
/**
* Queries if the given date is in daylight savings time in
* this time zone.
* or a custom ID such as "GMT-8:00". Note that the support of abbreviations,
* such as "PST", is for JDK 1.1.x compatibility only and full names should be used.
*
- * @return the specified <code>TimeZone</code>, or the GMT zone with ID "Etc/Unknown"
- * if the given ID cannot be understood.
- * @see #UNKNOWN_ZONE_ID
+ * @return the specified <code>TimeZone</code>, or a mutable clone of the UNKNOWN_ZONE
+ * if the given ID cannot be understood or if the given ID is "Etc/Unknown".
+ * @see #UNKNOWN_ZONE
* @stable ICU 2.0
*/
- public static synchronized TimeZone getTimeZone(String ID) {
- return getTimeZone(ID, TZ_IMPL);
+ public static TimeZone getTimeZone(String ID) {
+ return getTimeZone(ID, TZ_IMPL, false);
+ }
+
+ /**
+ * Gets the <code>TimeZone</code> for the given ID. The instance of <code>TimeZone</code>
+ * returned by this method is immutable. Any methods mutate the instance({@link #setID(String)},
+ * {@link #setRawOffset(int)}) will throw <code>UnsupportedOperationException</code> upon its
+ * invocation.
+ *
+ * @param ID the ID for a <code>TimeZone</code>, such as "America/Los_Angeles",
+ * or a custom ID such as "GMT-8:00". Note that the support of abbreviations,
+ * such as "PST", is for JDK 1.1.x compatibility only and full names should be used.
+ *
+ * @return the specified <code>TimeZone</code>, or the UNKNOWN_ZONE
+ * if the given ID cannot be understood.
+ * @see #UNKNOWN_ZONE
+ * @stable ICU 49
+ */
+ public static TimeZone getFrozenTimeZone(String ID) {
+ return getTimeZone(ID, TZ_IMPL, true);
}
/**
* "PST", is for JDK 1.1.x compatibility only and full names should be used.
* @param type Time zone type, either <code>TIMEZONE_ICU</code> or
* <code>TIMEZONE_JDK</code>.
- * @return the specified <code>TimeZone</code>, or the GMT zone if the given ID
- * cannot be understood.
+ * @return the specified <code>TimeZone</code>, or a mutable clone of the UNKNOWN_ZONE if the given ID
+ * cannot be understood or if the given ID is "Etc/Unknown".
+ * @see #UNKNOWN_ZONE
* @stable ICU 4.0
*/
- public static synchronized TimeZone getTimeZone(String ID, int type) {
+ public static TimeZone getTimeZone(String ID, int type) {
+ return getTimeZone(ID, type, false);
+ }
+
+ /**
+ * Gets the <code>TimeZone</code> for the given ID and the timezone type.
+ * @param ID time zone ID
+ * @param type time zone implementation type, TIMEZONE_JDK or TIMEZONE_ICU
+ * @param frozen specify if the returned object can be frozen
+ * @return the specified <code>TimeZone</code> or UNKNOWN_ZONE if the given ID
+ * cannot be understood.
+ */
+ private static synchronized TimeZone getTimeZone(String ID, int type, boolean frozen) {
TimeZone result;
if (type == TIMEZONE_JDK) {
- result = new JavaTimeZone(ID);
+ result = JavaTimeZone.createTimeZone(ID);
+ if (result != null) {
+ return frozen ? result.freeze() : result;
+ }
} 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
throw new NullPointerException();
}
result = ZoneMeta.getSystemTimeZone(ID);
+ }
- if (result == null) {
- result = ZoneMeta.getCustomTimeZone(ID);
- }
- if (result == null) {
- /* Log that timezone is using GMT if logging is on. */
- if (TimeZoneLogger != null && TimeZoneLogger.isLoggingOn()) {
- TimeZoneLogger.warning(
- "\"" +ID + "\" is a bogus id so timezone is falling back to Etc/Unknown(GMT).");
- }
- result = new SimpleTimeZone(0, UNKNOWN_ZONE_ID);
- }
+ if (result == null) {
+ result = ZoneMeta.getCustomTimeZone(ID);
}
- return result;
+
+ if (result == null) {
+ LOGGER.fine("\"" +ID + "\" is a bogus id so timezone is falling back to Etc/Unknown(GMT).");
+ result = UNKNOWN_ZONE;
+ }
+
+ return frozen ? result : result.cloneAsThawed();
}
/**
* @return an immutable set of system time zone IDs.
* @see SystemTimeZoneType
*
- * @draft ICU 4.8
- * @provisional This API might change or be removed in a future release.
+ * @stable ICU 4.8
*/
public static Set<String> getAvailableIDs(SystemTimeZoneType zoneType,
String region, Integer rawOffset) {
defaultZone = new JavaTimeZone();
} else {
java.util.TimeZone temp = java.util.TimeZone.getDefault();
- defaultZone = getTimeZone(temp.getID());
+ defaultZone = getFrozenTimeZone(temp.getID());
}
}
- return (TimeZone) defaultZone.clone();
+ return defaultZone.cloneAsThawed();
}
/**
* @stable ICU 2.0
*/
public Object clone() {
- try {
- TimeZone other = (TimeZone) super.clone();
- other.ID = ID;
- return other;
- } catch (CloneNotSupportedException e) {
- throw new IllegalStateException();
+ if (isFrozen()) {
+ return this;
}
+ return cloneAsThawed();
}
/**
* @throws IllegalArgumentException if <code>id</code> is not a known system ID.
* @see #getAvailableIDs(String)
*
- * @draft ICU 4.8
- * @provisional This API might change or be removed in a future release.
+ * @stable ICU 4.8
*/
public static String getRegion(String id) {
String region = null;
return region;
}
+ /**
+ * {@icu} Converts a system time zone ID to an equivalent Windows time zone ID. For example,
+ * Windows time zone ID "Pacific Standard Time" is returned for input "America/Los_Angeles".
+ *
+ * <p>There are system time zones that cannot be mapped to Windows zones. When the input
+ * system time zone ID is unknown or unmappable to a Windows time zone, then this
+ * method returns <code>null</code>.
+ *
+ * <p>This implementation utilizes <a href="http://unicode.org/cldr/charts/supplemental/zone_tzid.html">
+ * Zone-Tzid mapping data<a>. The mapping data is updated time to time. To get the latest changes,
+ * please read the ICU user guide section <a href="http://userguide.icu-project.org/datetime/timezone#TOC-Updating-the-Time-Zone-Data">
+ * Updating the Time Zone Data</a>.
+ *
+ * @param id A system time zone ID
+ * @return A Windows time zone ID mapped from the input system time zone ID,
+ * or <code>null</code> when the input ID is unknown or unmappable.
+ * @see #getIDForWindowsID(String, String)
+ *
+ * @draft ICU 52
+ * @provisional This API might change or be removed in a future release.
+ */
+ public static String getWindowsID(String id) {
+ // canonicalize the input ID
+ boolean[] isSystemID = {false};
+ id = getCanonicalID(id, isSystemID);
+ if (!isSystemID[0]) {
+ // mapping data is only applicable to tz database IDs
+ return null;
+ }
+
+ UResourceBundle top = UResourceBundle.getBundleInstance(
+ ICUResourceBundle.ICU_BASE_NAME, "windowsZones", ICUResourceBundle.ICU_DATA_CLASS_LOADER);
+ UResourceBundle mapTimezones = top.get("mapTimezones");
+
+ UResourceBundleIterator resitr = mapTimezones.getIterator();
+ while (resitr.hasNext()) {
+ UResourceBundle winzone = resitr.next();
+ if (winzone.getType() != UResourceBundle.TABLE) {
+ continue;
+ }
+ UResourceBundleIterator rgitr = winzone.getIterator();
+ while (rgitr.hasNext()) {
+ UResourceBundle regionalData = rgitr.next();
+ if (regionalData.getType() != UResourceBundle.STRING) {
+ continue;
+ }
+ String[] tzids = regionalData.getString().split(" ");
+ for (String tzid : tzids) {
+ if (tzid.equals(id)) {
+ return winzone.getKey();
+ }
+ }
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * {@icu} Converts a Windows time zone ID to an equivalent system time zone ID
+ * for a region. For example, system time zone ID "America/Los_Angeles" is returned
+ * for input Windows ID "Pacific Standard Time" and region "US" (or <code>null</code>),
+ * "America/Vancouver" is returned for the same Windows ID "Pacific Standard Time" and
+ * region "CA".
+ *
+ * <p>Not all Windows time zones can be mapped to system time zones. When the input
+ * Windows time zone ID is unknown or unmappable to a system time zone, then this
+ * method returns <code>null</code>.
+ *
+ * <p>This implementation utilizes <a href="http://unicode.org/cldr/charts/supplemental/zone_tzid.html">
+ * Zone-Tzid mapping data<a>. The mapping data is updated time to time. To get the latest changes,
+ * please read the ICU user guide section <a href="http://userguide.icu-project.org/datetime/timezone#TOC-Updating-the-Time-Zone-Data">
+ * Updating the Time Zone Data</a>.
+ *
+ * @param winid A Windows time zone ID
+ * @param region A region code, or <code>null</code> if no regional preference.
+ * @return A system time zone ID mapped from the input Windows time zone ID,
+ * or <code>null</code> when the input ID is unknown or unmappable.
+ * @see #getWindowsID(String)
+ *
+ * @draft ICU 52
+ * @provisional This API might change or be removed in a future release.
+ */
+ public static String getIDForWindowsID(String winid, String region) {
+ String id = null;
+
+ UResourceBundle top = UResourceBundle.getBundleInstance(
+ ICUResourceBundle.ICU_BASE_NAME, "windowsZones", ICUResourceBundle.ICU_DATA_CLASS_LOADER);
+ UResourceBundle mapTimezones = top.get("mapTimezones");
+
+ try {
+ UResourceBundle zones = mapTimezones.get(winid);
+ if (region != null) {
+ try {
+ id = zones.getString(region);
+ if (id != null) {
+ // first ID delimited by space is the default one
+ int endIdx = id.indexOf(' ');
+ if (endIdx > 0) {
+ id = id.substring(0, endIdx);
+ }
+ }
+ } catch (MissingResourceException e) {
+ // no explicit region mapping found
+ }
+ }
+ if (id == null) {
+ id = zones.getString("001");
+ }
+ } catch (MissingResourceException e) {
+ // no mapping data found
+ }
+
+ return id;
+ }
+
+ // Freezable stuffs
+
+ /**
+ * {@inheritDoc}
+ * @stable ICU 49
+ */
+ public boolean isFrozen() {
+ return false;
+ }
+
+ /**
+ * {@inheritDoc}
+ * @stable ICU 49
+ */
+ public TimeZone freeze() {
+ throw new UnsupportedOperationException("Needs to be implemented by the subclass.");
+ }
+
+ /**
+ * {@inheritDoc}
+ * @stable ICU 49
+ */
+ public TimeZone cloneAsThawed() {
+ try {
+ TimeZone other = (TimeZone) super.clone();
+ return other;
+ } catch (CloneNotSupportedException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
// =======================privates===============================
/**