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