TimeZone.java revision 21557bb6a8f35a2f9889adba449cac950c9d41b9
1/*
2 *  Licensed to the Apache Software Foundation (ASF) under one or more
3 *  contributor license agreements.  See the NOTICE file distributed with
4 *  this work for additional information regarding copyright ownership.
5 *  The ASF licenses this file to You under the Apache License, Version 2.0
6 *  (the "License"); you may not use this file except in compliance with
7 *  the License.  You may obtain a copy of the License at
8 *
9 *     http://www.apache.org/licenses/LICENSE-2.0
10 *
11 *  Unless required by applicable law or agreed to in writing, software
12 *  distributed under the License is distributed on an "AS IS" BASIS,
13 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 *  See the License for the specific language governing permissions and
15 *  limitations under the License.
16 */
17
18package java.util;
19
20import java.io.Serializable;
21import libcore.icu.TimeZones;
22import org.apache.harmony.luni.internal.util.ZoneInfo;
23import org.apache.harmony.luni.internal.util.ZoneInfoDB;
24
25/**
26 * {@code TimeZone} represents a time zone offset, taking into account
27 * daylight savings.
28 * <p>
29 * Typically, you get a {@code TimeZone} using {@code getDefault}
30 * which creates a {@code TimeZone} based on the time zone where the
31 * program is running. For example, for a program running in Japan,
32 * {@code getDefault} creates a {@code TimeZone} object based on
33 * Japanese Standard Time.
34 * <p>
35 * You can also get a {@code TimeZone} using {@code getTimeZone}
36 * along with a time zone ID. For instance, the time zone ID for the U.S.
37 * Pacific Time zone is "America/Los_Angeles". So, you can get a U.S. Pacific
38 * Time {@code TimeZone} object with the following: <blockquote>
39 *
40 * <pre>
41 * TimeZone tz = TimeZone.getTimeZone(&quot;America/Los_Angeles&quot;);
42 * </pre>
43 *
44 * </blockquote> You can use the {@code getAvailableIDs} method to iterate
45 * through all the supported time zone IDs. You can then choose a supported ID
46 * to get a {@code TimeZone}. If the time zone you want is not
47 * represented by one of the supported IDs, then you can create a custom time
48 * zone ID with the following syntax: <blockquote>
49 *
50 * <pre>
51 * GMT[+|-]hh[[:]mm]
52 * </pre>
53 *
54 * </blockquote> For example, you might specify GMT+14:00 as a custom time zone
55 * ID. The {@code TimeZone} that is returned when you specify a custom
56 * time zone ID does not include daylight savings time.
57 * <p>
58 * For compatibility with JDK 1.1.x, some other three-letter time zone IDs (such
59 * as "PST", "CTT", "AST") are also supported. However, <strong>their use is
60 * deprecated</strong> because the same abbreviation is often used for multiple
61 * time zones (for example, "CST" could be U.S. "Central Standard Time" and
62 * "China Standard Time"), and the Java platform can then only recognize one of
63 * them.
64 * <p>
65 * Please note the type returned by factory methods, i.e. {@code getDefault()}
66 * and {@code getTimeZone(String)}, is implementation dependent, so it may
67 * introduce serialization incompatibility issues between different
68 * implementations. Android returns instances of {@link SimpleTimeZone} so that
69 * the bytes serialized by Android can be deserialized successfully on other
70 * implementations, but the reverse compatibility cannot be guaranteed.
71 *
72 * @see GregorianCalendar
73 * @see SimpleTimeZone
74 */
75public abstract class TimeZone implements Serializable, Cloneable {
76    private static final long serialVersionUID = 3581463369166924961L;
77
78    /**
79     * The short display name style, such as {@code PDT}. Requests for this
80     * style may yield GMT offsets like {@code GMT-08:00}.
81     */
82    public static final int SHORT = 0;
83
84    /**
85     * The long display name style, such as {@code Pacific Daylight Time}.
86     * Requests for this style may yield GMT offsets like {@code GMT-08:00}.
87     */
88    public static final int LONG = 1;
89
90    static final TimeZone GMT = new SimpleTimeZone(0, "GMT"); // Greenwich Mean Time
91
92    private static TimeZone defaultTimeZone;
93
94    private String ID;
95
96    public TimeZone() {}
97
98    /**
99     * Returns a new time zone with the same ID, raw offset, and daylight
100     * savings time rules as this time zone.
101     */
102    @Override public Object clone() {
103        try {
104            return super.clone();
105        } catch (CloneNotSupportedException e) {
106            throw new AssertionError(e);
107        }
108    }
109
110    /**
111     * Returns the system's installed time zone IDs. Any of these IDs can be
112     * passed to {@link #getTimeZone} to lookup the corresponding time zone
113     * instance.
114     */
115    public static synchronized String[] getAvailableIDs() {
116        return ZoneInfoDB.getAvailableIDs();
117    }
118
119    /**
120     * Returns the IDs of the time zones whose offset from UTC is {@code
121     * offsetMillis}. Any of these IDs can be passed to {@link #getTimeZone} to
122     * lookup the corresponding time zone instance.
123     *
124     * @return a possibly-empty array.
125     */
126    public static synchronized String[] getAvailableIDs(int offsetMillis) {
127        return ZoneInfoDB.getAvailableIDs(offsetMillis);
128    }
129
130    /**
131     * Returns the user's preferred time zone. This may have been overridden for
132     * this process with {@link #setDefault}.
133     *
134     * <p>Since the user's time zone changes dynamically, avoid caching this
135     * value. Instead, use this method to look it up for each use.
136     */
137    public static synchronized TimeZone getDefault() {
138        if (defaultTimeZone == null) {
139            defaultTimeZone = ZoneInfoDB.getSystemDefault();
140        }
141        return (TimeZone) defaultTimeZone.clone();
142    }
143
144    /**
145     * Equivalent to {@code getDisplayName(false, TimeZone.LONG, Locale.getDefault())}.
146     * <a href="../util/Locale.html#default_locale">Be wary of the default locale</a>.
147     */
148    public final String getDisplayName() {
149        return getDisplayName(false, LONG, Locale.getDefault());
150    }
151
152    /**
153     * Equivalent to {@code getDisplayName(false, TimeZone.LONG, locale)}.
154     */
155    public final String getDisplayName(Locale locale) {
156        return getDisplayName(false, LONG, locale);
157    }
158
159    /**
160     * Equivalent to {@code getDisplayName(daylightTime, style, Locale.getDefault())}.
161     * <a href="../util/Locale.html#default_locale">Be wary of the default locale</a>.
162     */
163    public final String getDisplayName(boolean daylightTime, int style) {
164        return getDisplayName(daylightTime, style, Locale.getDefault());
165    }
166
167    /**
168     * Returns the {@link #SHORT short} or {@link #LONG long} name of this time
169     * zone with either standard or daylight time, as written in {@code locale}.
170     * If the name is not available, the result is in the format
171     * {@code GMT[+-]hh:mm}.
172     *
173     * @param daylightTime true for daylight time, false for standard time.
174     * @param style either {@link TimeZone#LONG} or {@link TimeZone#SHORT}.
175     * @param locale the display locale.
176     */
177    public String getDisplayName(boolean daylightTime, int style, Locale locale) {
178        if (style != SHORT && style != LONG) {
179            throw new IllegalArgumentException();
180        }
181
182        boolean useDaylight = daylightTime && useDaylightTime();
183
184        String result = TimeZones.getDisplayName(getID(), daylightTime, style, locale);
185        if (result != null) {
186            return result;
187        }
188
189        int offset = getRawOffset();
190        if (useDaylight && this instanceof SimpleTimeZone) {
191            offset += getDSTSavings();
192        }
193        offset /= 60000;
194        char sign = '+';
195        if (offset < 0) {
196            sign = '-';
197            offset = -offset;
198        }
199        StringBuilder builder = new StringBuilder(9);
200        builder.append("GMT");
201        builder.append(sign);
202        appendNumber(builder, 2, offset / 60);
203        builder.append(':');
204        appendNumber(builder, 2, offset % 60);
205        return builder.toString();
206    }
207
208    private void appendNumber(StringBuilder builder, int count, int value) {
209        String string = Integer.toString(value);
210        for (int i = 0; i < count - string.length(); i++) {
211            builder.append('0');
212        }
213        builder.append(string);
214    }
215
216    /**
217     * Returns the ID of this {@code TimeZone}, such as
218     * {@code America/Los_Angeles}, {@code GMT-08:00} or {@code UTC}.
219     */
220    public String getID() {
221        return ID;
222    }
223
224    /**
225     * Returns the daylight savings offset in milliseconds for this time zone.
226     * The base implementation returns {@code 3600000} (1 hour) for time zones
227     * that use daylight savings time and {@code 0} for timezones that do not.
228     * Subclasses should override this method for other daylight savings
229     * offsets.
230     *
231     * <p>Note that this method doesn't tell you whether or not to apply the
232     * offset: you need to call {@code inDaylightTime} for the specific time
233     * you're interested in. If this method returns a non-zero offset, that only
234     * tells you that this {@code TimeZone} sometimes observes daylight savings.
235     */
236    public int getDSTSavings() {
237        return useDaylightTime() ? 3600000 : 0;
238    }
239
240    /**
241     * Returns the offset in milliseconds from UTC for this time zone at {@code
242     * time}. The offset includes daylight savings time if the specified
243     * date is within the daylight savings time period.
244     *
245     * @param time the date in milliseconds since January 1, 1970 00:00:00 UTC
246     */
247    public int getOffset(long time) {
248        if (inDaylightTime(new Date(time))) {
249            return getRawOffset() + getDSTSavings();
250        }
251        return getRawOffset();
252    }
253
254    /**
255     * Returns this time zone's offset in milliseconds from UTC at the specified
256     * date and time. The offset includes daylight savings time if the date
257     * and time is within the daylight savings time period.
258     *
259     * <p>This method is intended to be used by {@link Calendar} to compute
260     * {@link Calendar#DST_OFFSET} and {@link Calendar#ZONE_OFFSET}. Application
261     * code should have no reason to call this method directly. Each parameter
262     * is interpreted in the same way as the corresponding {@code Calendar}
263     * field. Refer to {@link Calendar} for specific definitions of this
264     * method's parameters.
265     */
266    public abstract int getOffset(int era, int year, int month, int day,
267            int dayOfWeek, int timeOfDayMillis);
268
269    /**
270     * Returns the offset in milliseconds from UTC of this time zone's standard
271     * time.
272     */
273    public abstract int getRawOffset();
274
275    /**
276     * Returns a time zone whose ID is {@code id}. Time zone IDs are typically
277     * named by geographic identifiers like {@code America/Los_Angeles} or GMT
278     * offsets like {@code GMT-8:00}. Three letter IDs like {@code PST} are
279     * supported but should not be used because they are often ambiguous.
280     *
281     * @return a time zone with the specified ID, or {@code GMT} if the ID
282     *     is not recognized and cannot be parsed.
283     */
284    public static synchronized TimeZone getTimeZone(String id) {
285        TimeZone zone = ZoneInfo.getTimeZone(id);
286        if (zone != null) {
287            return (TimeZone) zone.clone();
288        }
289
290        if (!id.startsWith("GMT") || id.length() <= 3) {
291            return (TimeZone) GMT.clone();
292        }
293        char sign = id.charAt(3);
294        if (sign != '+' && sign != '-') {
295            return (TimeZone) GMT.clone();
296        }
297        int[] position = new int[1];
298        String formattedName = formatTimeZoneName(id, 4);
299        int hour = parseNumber(formattedName, 4, position);
300        if (hour < 0 || hour > 23) {
301            return (TimeZone) GMT.clone();
302        }
303        int index = position[0];
304        if (index == -1) {
305            return (TimeZone) GMT.clone();
306        }
307        int raw = hour * 3600000;
308        if (index < formattedName.length() && formattedName.charAt(index) == ':') {
309            int minute = parseNumber(formattedName, index + 1, position);
310            if (position[0] == -1 || minute < 0 || minute > 59) {
311                return (TimeZone) GMT.clone();
312            }
313            raw += minute * 60000;
314        } else if (hour >= 30 || index > 6) {
315            raw = (hour / 100 * 3600000) + (hour % 100 * 60000);
316        }
317        if (sign == '-') {
318            raw = -raw;
319        }
320        return new SimpleTimeZone(raw, formattedName);
321    }
322
323    private static String formatTimeZoneName(String name, int offset) {
324        StringBuilder buf = new StringBuilder();
325        int index = offset, length = name.length();
326        buf.append(name.substring(0, offset));
327
328        while (index < length) {
329            if (Character.digit(name.charAt(index), 10) != -1) {
330                buf.append(name.charAt(index));
331                if ((length - (index + 1)) == 2) {
332                    buf.append(':');
333                }
334            } else if (name.charAt(index) == ':') {
335                buf.append(':');
336            }
337            index++;
338        }
339
340        if (buf.toString().indexOf(":") == -1) {
341            buf.append(':');
342            buf.append("00");
343        }
344
345        if (buf.toString().indexOf(":") == 5) {
346            buf.insert(4, '0');
347        }
348
349        return buf.toString();
350    }
351
352    /**
353     * Returns true if {@code timeZone} has the same rules as this time zone.
354     *
355     * <p>The base implementation returns true if both time zones have the same
356     * raw offset.
357     */
358    public boolean hasSameRules(TimeZone timeZone) {
359        if (timeZone == null) {
360            return false;
361        }
362        return getRawOffset() == timeZone.getRawOffset();
363    }
364
365    /**
366     * Returns true if {@code time} is in a daylight savings time period for
367     * this time zone.
368     */
369    public abstract boolean inDaylightTime(Date time);
370
371    private static int parseNumber(String string, int offset, int[] position) {
372        int index = offset, length = string.length(), digit, result = 0;
373        while (index < length
374                && (digit = Character.digit(string.charAt(index), 10)) != -1) {
375            index++;
376            result = result * 10 + digit;
377        }
378        position[0] = index == offset ? -1 : index;
379        return result;
380    }
381
382    /**
383     * Overrides the default time zone for the current process only.
384     *
385     * <p><strong>Warning</strong>: avoid using this method to use a custom time
386     * zone in your process. This value may be cleared or overwritten at any
387     * time, which can cause unexpected behavior. Instead, manually supply a
388     * custom time zone as needed.
389     *
390     * @param timeZone a custom time zone, or {@code null} to set the default to
391     *     the user's preferred value.
392     */
393    public static synchronized void setDefault(TimeZone timeZone) {
394        defaultTimeZone = timeZone != null ? (TimeZone) timeZone.clone() : null;
395    }
396
397    /**
398     * Sets the ID of this {@code TimeZone}.
399     */
400    public void setID(String id) {
401        if (id == null) {
402            throw new NullPointerException();
403        }
404        ID = id;
405    }
406
407    /**
408     * Sets the offset in milliseconds from UTC of this time zone's standard
409     * time.
410     */
411    public abstract void setRawOffset(int offsetMillis);
412
413    /**
414     * Returns true if this time zone has a daylight savings time period. More
415     * specifically, this method returns true to indicate that there's at least
416     * one known future transition to or from daylight savings. This means that,
417     * say, Taiwan will return false because its historical use of daylight
418     * savings doesn't count. A hypothetical country that has never observed
419     * daylight savings before but plans to start next year would return true.
420     *
421     * <p>If this method returns true, that only tells you that this
422     * {@code TimeZone} sometimes observes daylight savings. You need to call
423     * {@code inDaylightTime} to find out whether daylight savings is in effect.
424     */
425    public abstract boolean useDaylightTime();
426}
427