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