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