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