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