blob: 60c4098b5fe707eefecd66c3307865b8c483d350 [file] [log] [blame]
/*
* @(#)TimeZone.java 1.51 00/01/19
*
* Copyright 1996-2000 Sun Microsystems, Inc. All Rights Reserved.
*
* This software is the proprietary information of Sun Microsystems, Inc.
* Use is subject to license terms.
*
*/
/*
* (C) Copyright Taligent, Inc. 1996 - All Rights Reserved
* (C) Copyright IBM Corp. 1996 - All Rights Reserved
*
* The original version of this source code and documentation is copyrighted
* and owned by Taligent, Inc., a wholly-owned subsidiary of IBM. These
* materials are provided under terms of a License Agreement between Taligent
* and Sun. This technology is protected by multiple US and International
* patents. This notice and attribution to Taligent may not be removed.
* Taligent is a registered trademark of Taligent, Inc.
*
*/
package com.ibm.util;
import java.io.Serializable;
import java.lang.ref.SoftReference;
import java.security.AccessController;
import java.security.PrivilegedAction;
import com.ibm.text.SimpleDateFormat;
import com.ibm.text.NumberFormat;
import java.text.ParsePosition;
import sun.security.action.GetPropertyAction;
import java.util.Date;
import java.util.Hashtable;
import java.util.Locale;
import java.util.Vector;
/**
* <code>TimeZone</code> represents a time zone offset, and also figures out daylight
* savings.
*
* <p>
* Typically, you get a <code>TimeZone</code> using <code>getDefault</code>
* which creates a <code>TimeZone</code> based on the time zone where the program
* is running. For example, for a program running in Japan, <code>getDefault</code>
* creates a <code>TimeZone</code> object based on Japanese Standard Time.
*
* <p>
* You can also get a <code>TimeZone</code> using <code>getTimeZone</code>
* along with a time zone ID. For instance, the time zone ID for the
* U.S. Pacific Time zone is "America/Los_Angeles". So, you can get a
* U.S. Pacific Time <code>TimeZone</code> object with:
* <blockquote>
* <pre>
* TimeZone tz = TimeZone.getTimeZone("America/Los_Angeles");
* </pre>
* </blockquote>
* You can use <code>getAvailableIDs</code> method to iterate through
* all the supported time zone IDs. 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
* the following syntax:
*
* <blockquote>
* <pre>
* GMT[+|-]hh[[:]mm]
* </pre>
* </blockquote>
*
* 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.
* <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
* use is deprecated</strong> because the same abbreviation is often used
* for multiple time zones (for example, "CST" could be U.S. "Central Standard
* Time" and "China Standard Time"), and the Java platform can then only
* recognize one of them.
*
*
* @see Calendar
* @see GregorianCalendar
* @see SimpleTimeZone
* @version 1.51 01/19/00
* @author Mark Davis, David Goldsmith, Chen-Lieh Huang, Alan Liu
* @since JDK1.1
*/
abstract public class TimeZone implements Serializable, Cloneable {
/**
* Sole constructor. (For invocation by subclass constructors, typically
* implicit.)
*/
public TimeZone() {
}
/**
* A style specifier for <code>getDisplayName()</code> indicating
* a short name, such as "PST."
* @see #LONG
* @since 1.2
*/
public static final int SHORT = 0;
/**
* A style specifier for <code>getDisplayName()</code> indicating
* a long name, such as "Pacific Standard Time."
* @see #SHORT
* @since 1.2
*/
public static final int LONG = 1;
// Constants used internally; unit is milliseconds
private static final int ONE_MINUTE = 60*1000;
private static final int ONE_HOUR = 60*ONE_MINUTE;
private static final int ONE_DAY = 24*ONE_HOUR;
/**
* Cache to hold the SimpleDateFormat objects for a Locale.
*/
private static Hashtable cachedLocaleData = new Hashtable(3);
// Proclaim serialization compatibility with JDK 1.1
static final long serialVersionUID = 3581463369166924961L;
/**
* 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.
* @param era the era of the given date.
* @param year the year in the given date.
* @param month the month in the given date.
* Month is 0-based. e.g., 0 for January.
* @param day the day-in-month of the given date.
* @param dayOfWeek the day-of-week of the given date.
* @param milliseconds the millis in day in <em>standard</em> local time.
* @return the offset to add *to* GMT to get local time.
*/
abstract public int getOffset(int era, int year, int month, int day,
int dayOfWeek, int milliseconds);
/**
* 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.
* @param era the era of the given date.
* @param year the year in the given date.
* @param month the month in the given date.
* Month is 0-based. e.g., 0 for January.
* @param day the day-in-month of the given date.
* @param dayOfWeek the day-of-week of the given date.
* @param milliseconds the millis in day in <em>standard</em> local time.
* @param monthLength the length of the given month in days.
* @param prevMonthLength the length of the previous month in days.
* @return the offset to add *to* GMT to get local time.
*/
int getOffset(int era, int year, int month, int day,
int dayOfWeek, int milliseconds, int monthLength, int prevMonthLength) {
// Default implementation which ignores the monthLength.
// SimpleTimeZone overrides this and actually uses monthLength.
return getOffset(era, year, month, day, dayOfWeek, milliseconds);
}
/**
* NEWCAL
* Gets the local time zone offset for this zone, for the given date.
* @param eyear the extended Gregorian year, with 0 = 1 BC, -1 = 2 BC,
* etc.
*/
int getOffset(int eyear, int month, int dayOfMonth, int dayOfWeek,
int milliseconds, int monthLength, int prevMonthLength) {
// TEMPORARY: Convert the eyear to an era/year and call old API
int era = GregorianCalendar.AD;
if (eyear < 1) {
era = GregorianCalendar.BC;
eyear = 1 - eyear;
}
return getOffset(era, eyear, month, dayOfMonth, dayOfWeek,
milliseconds, monthLength, prevMonthLength);
}
/**
* Sets the base time zone offset to GMT.
* This is the offset to add *to* UTC to get local time.
* @param offsetMillis the given base time zone offset to GMT.
*/
abstract public void setRawOffset(int offsetMillis);
/**
* Gets unmodified offset, NOT modified in case of daylight savings.
* This is the offset to add *to* UTC to get local time.
* @return the unmodified offset to add *to* UTC to get local time.
*/
abstract public int getRawOffset();
/**
* Gets the ID of this time zone.
* @return the ID of this time zone.
*/
public String getID()
{
return ID;
}
/**
* Sets the time zone ID. This does not change any other data in
* the time zone object.
* @param ID the new time zone ID.
*/
public void setID(String ID)
{
if (ID == null) {
throw new NullPointerException();
}
this.ID = ID;
}
/**
* Returns a name of this time zone suitable for presentation to the user
* in the default locale.
* This method returns the long name, not including daylight savings.
* If the display name is not available for the locale,
* then this method returns a string in the format
* <code>GMT[+-]hh:mm</code>.
* @return the human-readable name of this time zone in the default locale.
* @since 1.2
*/
public final String getDisplayName() {
return getDisplayName(false, LONG, Locale.getDefault());
}
/**
* Returns a name of this time zone suitable for presentation to the user
* in the specified locale.
* This method returns the long name, not including daylight savings.
* If the display name is not available for the locale,
* then this method returns a string in the format
* <code>GMT[+-]hh:mm</code>.
* @param locale the locale in which to supply the display name.
* @return the human-readable name of this time zone in the given locale
* or in the default locale if the given locale is not recognized.
* @since 1.2
*/
public final String getDisplayName(Locale locale) {
return getDisplayName(false, LONG, 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>.
* @param daylight if true, return the daylight savings name.
* @param style either <code>LONG</code> or <code>SHORT</code>
* @return the human-readable name of this time zone in the default locale.
* @since 1.2
*/
public final String getDisplayName(boolean daylight, int style) {
return getDisplayName(daylight, style, Locale.getDefault());
}
/**
* 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>.
* @param daylight if true, return the daylight savings name.
* @param style either <code>LONG</code> or <code>SHORT</code>
* @param locale the locale in which to supply the display name.
* @return the human-readable name of this time zone in the given locale
* or in the default locale if the given locale is not recognized.
* @exception IllegalArgumentException style is invalid.
* @since 1.2
*/
public String getDisplayName(boolean daylight, int style, Locale locale) {
/* NOTES:
* (1) We use SimpleDateFormat for simplicity; we could do this
* more efficiently but it would duplicate the SimpleDateFormat code
* here, which is undesirable.
* (2) Attempts to move the code from SimpleDateFormat to here also run
* aground because this requires SimpleDateFormat to keep a Locale
* object around, which it currently doesn't; to synthesize such a
* locale upon resurrection; and to somehow handle the special case of
* construction from a DateFormatSymbols object.
*/
if (style != SHORT && style != LONG) {
throw new IllegalArgumentException("Illegal style: " + style);
}
// We keep a cache, indexed by locale. The cache contains a
// SimpleDateFormat object, which we create on demand.
SoftReference data = (SoftReference)cachedLocaleData.get(locale);
SimpleDateFormat format;
if (data == null ||
(format = (SimpleDateFormat)data.get()) == null) {
format = new SimpleDateFormat(null, locale);
cachedLocaleData.put(locale, new SoftReference(format));
}
// Create a new SimpleTimeZone as a stand-in for this zone; the stand-in
// will have no DST, or DST during January, but the same ID and offset,
// and hence the same display name. We don't cache these because
// they're small and cheap to create.
SimpleTimeZone tz;
if (daylight && useDaylightTime()) {
int savings = ONE_HOUR;
try {
savings = ((SimpleTimeZone) this).getDSTSavings();
} catch (ClassCastException e) {}
tz = new SimpleTimeZone(getRawOffset(), getID(),
Calendar.JANUARY, 1, 0, 0,
Calendar.FEBRUARY, 1, 0, 0,
savings);
} else {
tz = new SimpleTimeZone(getRawOffset(), getID());
}
format.applyPattern(style == LONG ? "zzzz" : "z");
format.setTimeZone(tz);
// Format a date in January. We use the value 10*ONE_DAY == Jan 11 1970
// 0:00 GMT.
return format.format(new Date(864000000L));
}
/**
* Queries if this time zone uses daylight savings time.
* @return true if this time zone uses daylight savings time,
* false, otherwise.
*/
abstract public boolean useDaylightTime();
/**
* Queries if the given date is in daylight savings time in
* this time zone.
* @param date the given Date.
* @return true if the given date is in daylight savings time,
* false, otherwise.
*/
abstract public boolean inDaylightTime(Date date);
/**
* Gets the <code>TimeZone</code> for the given ID.
*
* @param ID the ID for a <code>TimeZone</code>, either an abbreviation
* such as "PST", a full name such as "America/Los_Angeles", or a custom
* ID such as "GMT-8:00". Note that the support of abbreviations is
* for JDK 1.1.x compatibility only and full names should be used.
*
* @return the specified <code>TimeZone</code>, or the GMT zone if the given ID
* cannot be understood.
*/
public static synchronized TimeZone getTimeZone(String ID) {
/* We first try to lookup the zone ID in our hashtable. If this fails,
* we try to parse it as a custom string GMT[+-]hh:mm. This allows us
* to recognize zones in user.timezone that otherwise cannot be
* identified. We do the recognition here, rather than in getDefault(),
* so that the default zone is always the result of calling
* getTimeZone() with the property user.timezone.
*
* If all else fails, we return GMT, which is probably not what the user
* wants, but at least is a functioning TimeZone object. */
TimeZone zone = TimeZoneData.get(ID);
if (zone == null) zone = parseCustomTimeZone(ID);
if (zone == null) zone = (TimeZone)GMT.clone();
return zone;
}
/**
* Return a new String array containing all system TimeZone IDs
* with the given raw offset from GMT. These IDs may be passed to
* <code>get()</code> to construct the corresponding TimeZone
* object.
* @param rawOffset the offset in milliseconds from GMT
* @return an array of IDs for system TimeZones with the given
* raw offset. If there are none, return a zero-length array.
*/
public static String[] getAvailableIDs(int rawOffset) {
return TimeZoneData.getAvailableIDs(rawOffset);
}
/**
* Return a new String array containing all system TimeZone IDs
* associated with the given country. These IDs may be passed to
* <code>get()</code> to construct the corresponding TimeZone
* object.
* @param a two-letter ISO 3166 country code, or <code>null</code>
* to return zones not associated with any country
* @return an array of IDs for system TimeZones in the given
* country. If there are none, return a zero-length array.
*/
public static String[] getAvailableIDs(String country) {
return TimeZoneData.getAvailableIDs(country);
}
/**
* Return a new String array containing all system TimeZone IDs.
* These IDs (and only these IDs) may be passed to
* <code>get()</code> to construct the corresponding TimeZone
* object.
* @return an array of all system TimeZone IDs
*/
public static String[] getAvailableIDs() {
return TimeZoneData.getAvailableIDs();
}
/**
* Returns the number of IDs in the equivalency group that
* includes the given ID. An equivalency group contains zones
* that have the same GMT offset and rules.
*
* <p>The returned count includes the given ID; it is always >= 1
* for valid IDs. The given ID must be a system time zone. If it
* is not, returns zero.
* @param id a system time zone ID
* @return the number of zones in the equivalency group containing
* 'id', or zero if 'id' is not a valid system ID
* @see #getEquivalentID
*/
public static int countEquivalentIDs(String id) {
return TimeZoneData.countEquivalentIDs(id);
}
/**
* Returns an ID in the equivalency group that
* includes the given ID. An equivalency group contains zones
* that have the same GMT offset and rules.
*
* <p>The given index must be in the range 0..n-1, where n is the
* value returned by <code>countEquivalentIDs(id)</code>. For
* some value of 'index', the returned value will be equal to the
* given id. If the given id is not a valid system time zone, or
* if 'index' is out of range, then returns an empty string.
* @param id a system time zone ID
* @param index a value from 0 to n-1, where n is the value
* returned by <code>countEquivalentIDs(id)</code>
* @return the ID of the index-th zone in the equivalency group
* containing 'id', or an empty string if 'id' is not a valid
* system ID or 'index' is out of range
* @see #countEquivalentIDs
*/
public static String getEquivalentID(String id, int index) {
return TimeZoneData.getEquivalentID(id, index);
}
/**
* Gets the platform defined TimeZone ID.
**/
// private static native String getSystemTimeZoneID(String javaHome,
// String region);
/**
* Gets the default <code>TimeZone</code> for this host.
* The source of the default <code>TimeZone</code>
* may vary with implementation.
* @return a default <code>TimeZone</code>.
*/
public static synchronized TimeZone getDefault() {
if (defaultZone == null) {
// The following causes an AccessControlException when run in an
// applet. To get around this, we don't read the user.timezone
// property; instead we defer to TimeZone.getDefault(). - Liu
//!// get the time zone ID from the system properties
//!String zoneID = (String) AccessController.doPrivileged(
//! new GetPropertyAction("user.timezone"));
//!
//!// if the time zone ID is not set (yet), perform the
//!// platform to Java time zone ID mapping.
//!if (zoneID == null || zoneID.equals("")) {
//String region = (String) AccessController.doPrivileged(
// new GetPropertyAction("user.region"));
//String javaHome = (String) AccessController.doPrivileged(
// new GetPropertyAction("java.home"));
//zoneID = getSystemTimeZoneID(javaHome, region);
// [icu4j We get the default zone by querying java.util.TimeZone,
// and then attempting to map the ID. - liu ]
java.util.TimeZone _default = java.util.TimeZone.getDefault();
if (false) System.out.println("java.util.TZ.default " + _default);
String zoneID = _default.getID();
defaultZone = TimeZoneData.get(zoneID);
if (defaultZone == null) {
// [icu4j This means that the zone returned by the JDK does
// not exist in our table. We will, for the moment, map to
// a std zone that has the same raw offset. In the future
// we might find it worthwhile to extract the rules from the
// system default zone, but this is too much trouble for
// now. It will be easier to extend our mapping table to
// match the JDKs we want to support. - liu ]
try {
java.util.SimpleTimeZone s = (java.util.SimpleTimeZone) _default;
defaultZone = new SimpleTimeZone(s.getRawOffset(), s.getID());
} catch (ClassCastException e) {}
} else {
if (zoneID == null) {
zoneID = GMT_ID;
}
// Again, we can't do this from outside the JDK in an applet.
//!final String id = zoneID;
//!AccessController.doPrivileged(new PrivilegedAction() {
//! public Object run() {
//! System.setProperty("user.timezone", id);
//! return null;
//! }
//!});
}
//!}
if (defaultZone == null) {
defaultZone = getTimeZone(zoneID);
}
if (false) System.out.println("com.ibm.util.TZ.default " + defaultZone);
}
return (TimeZone)defaultZone.clone();
}
/**
* Sets the <code>TimeZone</code> that is
* returned by the <code>getDefault</code> method. If <code>zone</code>
* is null, reset the default to the value it had originally when the
* VM first started.
* @param zone the new default time zone
*/
public static synchronized void setDefault(TimeZone zone)
{
defaultZone = zone;
// [icu4j Keep java.util.TimeZone default in sync so java.util.Date
// can interoperate with com.ibm.util classes. This solution
// is _imperfect_; see SimpleTimeZoneAdapter. - liu]
try {
java.util.TimeZone.setDefault(
new SimpleTimeZoneAdapter((SimpleTimeZone) zone));
} catch (ClassCastException e) {}
}
/**
* Returns true if this zone has the same rule and offset as another zone.
* That is, if this zone differs only in ID, if at all. Returns false
* if the other zone is null.
* @param other the <code>TimeZone</code> object to be compared with
* @return true if the other zone is not null and is the same as this one,
* with the possible exception of the ID
* @since 1.2
*/
public boolean hasSameRules(TimeZone other) {
return other != null && getRawOffset() == other.getRawOffset() &&
useDaylightTime() == other.useDaylightTime();
}
/**
* Overrides Cloneable
*/
public Object clone()
{
try {
TimeZone other = (TimeZone) super.clone();
other.ID = ID;
return other;
} catch (CloneNotSupportedException e) {
throw new InternalError();
}
}
// =======================privates===============================
/**
* The string identifier of this <code>TimeZone</code>. This is a
* programmatic identifier used internally to look up <code>TimeZone</code>
* objects from the system table and also to map them to their localized
* display names. <code>ID</code> values are unique in the system
* table but may not be for dynamically created zones.
* @serial
*/
private String ID;
private static TimeZone defaultZone = null;
static final String GMT_ID = "GMT";
private static final int GMT_ID_LENGTH = 3;
private static final String CUSTOM_ID = "Custom";
private static NumberFormat numberFormat = null;
private static final TimeZone GMT = new SimpleTimeZone(0, GMT_ID);
/**
* Parse a custom time zone identifier and return a corresponding zone.
* @param id a string of the form GMT[+-]hh:mm, GMT[+-]hhmm, or
* GMT[+-]hh.
* @return a newly created SimpleTimeZone with the given offset and
* no daylight savings time, or null if the id cannot be parsed.
*/
private static final SimpleTimeZone parseCustomTimeZone(String id) {
if (id.length() > GMT_ID_LENGTH &&
id.regionMatches(true, 0, GMT_ID, 0, GMT_ID_LENGTH)) {
ParsePosition pos = new ParsePosition(GMT_ID_LENGTH);
boolean negative = false;
int offset;
if (id.charAt(pos.getIndex()) == '-')
negative = true;
else if (id.charAt(pos.getIndex()) != '+')
return null;
pos.setIndex(pos.getIndex() + 1);
// Create NumberFormat if necessary
synchronized (TimeZone.class) {
if (numberFormat == null) {
numberFormat = NumberFormat.getInstance();
numberFormat.setParseIntegerOnly(true);
}
}
synchronized (numberFormat) {
// Look for either hh:mm, hhmm, or hh
int start = pos.getIndex();
Number n = numberFormat.parse(id, pos);
if (n == null) return null;
offset = n.intValue();
if (pos.getIndex() < id.length() &&
id.charAt(pos.getIndex()) == ':') {
// hh:mm
offset *= 60;
pos.setIndex(pos.getIndex() + 1);
n = numberFormat.parse(id, pos);
if (n == null) return null;
offset += n.intValue();
}
else {
// hhmm or hh
// Be strict about interpreting something as hh; it must be
// an offset < 30, and it must be one or two digits. Thus
// 0010 is interpreted as 00:10, but 10 is interpreted as
// 10:00.
if (offset < 30 && (pos.getIndex() - start) <= 2)
offset *= 60; // hh, from 00 to 29; 30 is 00:30
else
offset = offset % 100 + offset / 100 * 60; // hhmm
}
if (negative) offset = -offset;
return new SimpleTimeZone(offset * 60000, CUSTOM_ID);
}
}
return null;
}
// Internal Implementation Notes [LIU]
//
// TimeZone data is stored in two parts. The first is an encoding of the
// rules for each TimeZone. A TimeZone rule includes the offset of a zone
// in milliseconds from GMT, the starting month and day for daylight savings
// time, if there is any, and the ending month and day for daylight savings
// time. The starting and ending days are specified in terms of the n-th
// day of the week, for instance, the first Sunday or the last ("-1"-th)
// Sunday of the month. The rules are stored as statically-constructed
// SimpleTimeZone objects in the TimeZone class.
//
// Each rule has a unique internal identifier string which is used to
// specify it. This identifier string is arbitrary, and is not to be shown
// to the user -- it is for programmatic use only. In order to instantiate
// a TimeZone object, you pass its identifier string to
// TimeZone.getTimeZone(). (This identifier is also used to index the
// localized string data.)
//
// The second part of the data consists of localized string names used by
// DateFormat to describe various TimeZones. A TimeZone may have up to four
// names: The abbreviated and long name for standard time in that zone, and
// the abbreviated and long name for daylight savings time in that zone.
// The data also includes a representative city. For example, [ "PST",
// "Pacific Standard Time", "PDT", "Pacific Daylight Time", "Los Angeles" ]
// might be one such set of string names in the en_US locale. These strings
// are intended to be shown to the user. The string data is indexed in the
// system by a pair (String id, Locale locale). The id is the unique string
// identifier for the rule for the given TimeZone (as passed to
// TimeZone.getTimeZone()). String names are stored as localized resource
// data of the class java.text.resources.DateFormatZoneData??? where ??? is
// the Locale specifier (e.g., DateFormatZoneData_en_US). This data is a
// two-dimensional array of strings with N rows and 6 columns. The columns
// are id, short standard name, long standard name, short daylight name,
// long daylight name, representative city name.
//
// The mapping between rules (SimpleTimeZone objects) and localized string
// names (DateFormatZoneData objects) is one-to-many. That is, there will
// sometimes be more than one localized string name sets associated with
// each rule.
//
// Each locale can potentially have localized name data for all time zones.
// Since we support approximately 90 time zones and approximately 50
// locales, there can be over 4500 sets of localized names. In practice,
// only a fraction of these names are provided. If a time zone needs to be
// displayed to the user in a given locale, and there is no string data in
// that locale for that time zone, then the default representation will be
// shown. This is a string of the form GMT+HHMM or GMT-HHMM, where HHMM
// represents the offset in hours and minutes with respect to GMT. This
// format is used because it is recognized in all locales. In order to make
// this mechanism to work, the root resource data (in the class
// DateFormatZoneData) is left empty.
//
// The current default TimeZone is determined via the system property
// user.timezone. This is set by the platform-dependent native code to
// a three-letter abbreviation. We interpret these into our own internal
// IDs using a lookup table.
}
//eof