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