1/*
2 * Copyright (c) 1996, 2015, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation.  Oracle designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Oracle in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
23 * questions.
24 */
25
26/*
27 * (C) Copyright Taligent, Inc. 1996 - All Rights Reserved
28 * (C) Copyright IBM Corp. 1996 - All Rights Reserved
29 *
30 *   The original version of this source code and documentation is copyrighted
31 * and owned by Taligent, Inc., a wholly-owned subsidiary of IBM. These
32 * materials are provided under terms of a License Agreement between Taligent
33 * and Sun. This technology is protected by multiple US and International
34 * patents. This notice and attribution to Taligent may not be removed.
35 *   Taligent is a registered trademark of Taligent, Inc.
36 *
37 */
38
39package java.util;
40
41import java.io.Serializable;
42import java.security.AccessController;
43import java.security.PrivilegedAction;
44import java.time.ZoneId;
45import java.util.Properties;
46import sun.security.action.GetPropertyAction;
47import sun.util.calendar.ZoneInfo;
48import sun.util.calendar.ZoneInfoFile;
49import sun.util.locale.provider.TimeZoneNameUtility;
50
51/**
52 * <code>TimeZone</code> represents a time zone offset, and also figures out daylight
53 * savings.
54 *
55 * <p>
56 * Typically, you get a <code>TimeZone</code> using <code>getDefault</code>
57 * which creates a <code>TimeZone</code> based on the time zone where the program
58 * is running. For example, for a program running in Japan, <code>getDefault</code>
59 * creates a <code>TimeZone</code> object based on Japanese Standard Time.
60 *
61 * <p>
62 * You can also get a <code>TimeZone</code> using <code>getTimeZone</code>
63 * along with a time zone ID. For instance, the time zone ID for the
64 * U.S. Pacific Time zone is "America/Los_Angeles". So, you can get a
65 * U.S. Pacific Time <code>TimeZone</code> object with:
66 * <blockquote><pre>
67 * TimeZone tz = TimeZone.getTimeZone("America/Los_Angeles");
68 * </pre></blockquote>
69 * You can use the <code>getAvailableIDs</code> method to iterate through
70 * all the supported time zone IDs. You can then choose a
71 * supported ID to get a <code>TimeZone</code>.
72 * If the time zone you want is not represented by one of the
73 * supported IDs, then a custom time zone ID can be specified to
74 * produce a TimeZone. The syntax of a custom time zone ID is:
75 *
76 * <blockquote><pre>
77 * <a id="CustomID"><i>CustomID:</i></a>
78 *         <code>GMT</code> <i>Sign</i> <i>Hours</i> <code>:</code> <i>Minutes</i>
79 *         <code>GMT</code> <i>Sign</i> <i>Hours</i> <i>Minutes</i>
80 *         <code>GMT</code> <i>Sign</i> <i>Hours</i>
81 * <i>Sign:</i> one of
82 *         <code>+ -</code>
83 * <i>Hours:</i>
84 *         <i>Digit</i>
85 *         <i>Digit</i> <i>Digit</i>
86 * <i>Minutes:</i>
87 *         <i>Digit</i> <i>Digit</i>
88 * <i>Digit:</i> one of
89 *         <code>0 1 2 3 4 5 6 7 8 9</code>
90 * </pre></blockquote>
91 *
92 * <i>Hours</i> must be between 0 to 23 and <i>Minutes</i> must be
93 * between 00 to 59.  For example, "GMT+10" and "GMT+0010" mean ten
94 * hours and ten minutes ahead of GMT, respectively.
95 * <p>
96 * The format is locale independent and digits must be taken from the
97 * Basic Latin block of the Unicode standard. No daylight saving time
98 * transition schedule can be specified with a custom time zone ID. If
99 * the specified string doesn't match the syntax, <code>"GMT"</code>
100 * is used.
101 * <p>
102 * When creating a <code>TimeZone</code>, the specified custom time
103 * zone ID is normalized in the following syntax:
104 * <blockquote><pre>
105 * <a id="NormalizedCustomID"><i>NormalizedCustomID:</i></a>
106 *         <code>GMT</code> <i>Sign</i> <i>TwoDigitHours</i> <code>:</code> <i>Minutes</i>
107 * <i>Sign:</i> one of
108 *         <code>+ -</code>
109 * <i>TwoDigitHours:</i>
110 *         <i>Digit</i> <i>Digit</i>
111 * <i>Minutes:</i>
112 *         <i>Digit</i> <i>Digit</i>
113 * <i>Digit:</i> one of
114 *         <code>0 1 2 3 4 5 6 7 8 9</code>
115 * </pre></blockquote>
116 * For example, TimeZone.getTimeZone("GMT-8").getID() returns "GMT-08:00".
117 *
118 * <h3>Three-letter time zone IDs</h3>
119 *
120 * For compatibility with JDK 1.1.x, some other three-letter time zone IDs
121 * (such as "PST", "CTT", "AST") are also supported. However, <strong>their
122 * use is deprecated</strong> because the same abbreviation is often used
123 * for multiple time zones (for example, "CST" could be U.S. "Central Standard
124 * Time" and "China Standard Time"), and the Java platform can then only
125 * recognize one of them.
126 *
127 *
128 * @see          Calendar
129 * @see          GregorianCalendar
130 * @see          SimpleTimeZone
131 * @author       Mark Davis, David Goldsmith, Chen-Lieh Huang, Alan Liu
132 * @since        1.1
133 */
134public abstract class TimeZone implements Serializable, Cloneable {
135    /**
136     * Sole constructor.  (For invocation by subclass constructors, typically
137     * implicit.)
138     */
139    public TimeZone() {
140    }
141
142    /**
143     * A style specifier for <code>getDisplayName()</code> indicating
144     * a short name, such as "PST."
145     * @see #LONG
146     * @since 1.2
147     */
148    public static final int SHORT = 0;
149
150    /**
151     * A style specifier for <code>getDisplayName()</code> indicating
152     * a long name, such as "Pacific Standard Time."
153     * @see #SHORT
154     * @since 1.2
155     */
156    public static final int LONG  = 1;
157
158    // Constants used internally; unit is milliseconds
159    private static final int ONE_MINUTE = 60*1000;
160    private static final int ONE_HOUR   = 60*ONE_MINUTE;
161    private static final int ONE_DAY    = 24*ONE_HOUR;
162
163    // Proclaim serialization compatibility with JDK 1.1
164    static final long serialVersionUID = 3581463369166924961L;
165
166    /**
167     * Gets the time zone offset, for current date, modified in case of
168     * daylight savings. This is the offset to add to UTC to get local time.
169     * <p>
170     * This method returns a historically correct offset if an
171     * underlying <code>TimeZone</code> implementation subclass
172     * supports historical Daylight Saving Time schedule and GMT
173     * offset changes.
174     *
175     * @param era the era of the given date.
176     * @param year the year in the given date.
177     * @param month the month in the given date.
178     * Month is 0-based. e.g., 0 for January.
179     * @param day the day-in-month of the given date.
180     * @param dayOfWeek the day-of-week of the given date.
181     * @param milliseconds the milliseconds in day in <em>standard</em>
182     * local time.
183     *
184     * @return the offset in milliseconds to add to GMT to get local time.
185     *
186     * @see Calendar#ZONE_OFFSET
187     * @see Calendar#DST_OFFSET
188     */
189    public abstract int getOffset(int era, int year, int month, int day,
190                                  int dayOfWeek, int milliseconds);
191
192    /**
193     * Returns the offset of this time zone from UTC at the specified
194     * date. If Daylight Saving Time is in effect at the specified
195     * date, the offset value is adjusted with the amount of daylight
196     * saving.
197     * <p>
198     * This method returns a historically correct offset value if an
199     * underlying TimeZone implementation subclass supports historical
200     * Daylight Saving Time schedule and GMT offset changes.
201     *
202     * @param date the date represented in milliseconds since January 1, 1970 00:00:00 GMT
203     * @return the amount of time in milliseconds to add to UTC to get local time.
204     *
205     * @see Calendar#ZONE_OFFSET
206     * @see Calendar#DST_OFFSET
207     * @since 1.4
208     */
209    public int getOffset(long date) {
210        if (inDaylightTime(new Date(date))) {
211            return getRawOffset() + getDSTSavings();
212        }
213        return getRawOffset();
214    }
215
216    /**
217     * Gets the raw GMT offset and the amount of daylight saving of this
218     * time zone at the given time.
219     * @param date the milliseconds (since January 1, 1970,
220     * 00:00:00.000 GMT) at which the time zone offset and daylight
221     * saving amount are found
222     * @param offsets an array of int where the raw GMT offset
223     * (offset[0]) and daylight saving amount (offset[1]) are stored,
224     * or null if those values are not needed. The method assumes that
225     * the length of the given array is two or larger.
226     * @return the total amount of the raw GMT offset and daylight
227     * saving at the specified date.
228     *
229     * @see Calendar#ZONE_OFFSET
230     * @see Calendar#DST_OFFSET
231     */
232    int getOffsets(long date, int[] offsets) {
233        int rawoffset = getRawOffset();
234        int dstoffset = 0;
235        if (inDaylightTime(new Date(date))) {
236            dstoffset = getDSTSavings();
237        }
238        if (offsets != null) {
239            offsets[0] = rawoffset;
240            offsets[1] = dstoffset;
241        }
242        return rawoffset + dstoffset;
243    }
244
245    /**
246     * Sets the base time zone offset to GMT.
247     * This is the offset to add to UTC to get local time.
248     * <p>
249     * If an underlying <code>TimeZone</code> implementation subclass
250     * supports historical GMT offset changes, the specified GMT
251     * offset is set as the latest GMT offset and the difference from
252     * the known latest GMT offset value is used to adjust all
253     * historical GMT offset values.
254     *
255     * @param offsetMillis the given base time zone offset to GMT.
256     */
257    public abstract void setRawOffset(int offsetMillis);
258
259    /**
260     * Returns the amount of time in milliseconds to add to UTC to get
261     * standard time in this time zone. Because this value is not
262     * affected by daylight saving time, it is called <I>raw
263     * offset</I>.
264     * <p>
265     * If an underlying <code>TimeZone</code> implementation subclass
266     * supports historical GMT offset changes, the method returns the
267     * raw offset value of the current date. In Honolulu, for example,
268     * its raw offset changed from GMT-10:30 to GMT-10:00 in 1947, and
269     * this method always returns -36000000 milliseconds (i.e., -10
270     * hours).
271     *
272     * @return the amount of raw offset time in milliseconds to add to UTC.
273     * @see Calendar#ZONE_OFFSET
274     */
275    public abstract int getRawOffset();
276
277    /**
278     * Gets the ID of this time zone.
279     * @return the ID of this time zone.
280     */
281    public String getID()
282    {
283        return ID;
284    }
285
286    /**
287     * Sets the time zone ID. This does not change any other data in
288     * the time zone object.
289     * @param ID the new time zone ID.
290     */
291    public void setID(String ID)
292    {
293        if (ID == null) {
294            throw new NullPointerException();
295        }
296        this.ID = ID;
297        this.zoneId = null;   // invalidate cache
298    }
299
300    /**
301     * Returns a long standard time name of this {@code TimeZone} suitable for
302     * presentation to the user in the default locale.
303     *
304     * <p>This method is equivalent to:
305     * <blockquote><pre>
306     * getDisplayName(false, {@link #LONG},
307     *                Locale.getDefault({@link Locale.Category#DISPLAY}))
308     * </pre></blockquote>
309     *
310     * @return the human-readable name of this time zone in the default locale.
311     * @since 1.2
312     * @see #getDisplayName(boolean, int, Locale)
313     * @see Locale#getDefault(Locale.Category)
314     * @see Locale.Category
315     */
316    public final String getDisplayName() {
317        return getDisplayName(false, LONG,
318                              Locale.getDefault(Locale.Category.DISPLAY));
319    }
320
321    /**
322     * Returns a long standard time name of this {@code TimeZone} suitable for
323     * presentation to the user in the specified {@code locale}.
324     *
325     * <p>This method is equivalent to:
326     * <blockquote><pre>
327     * getDisplayName(false, {@link #LONG}, locale)
328     * </pre></blockquote>
329     *
330     * @param locale the locale in which to supply the display name.
331     * @return the human-readable name of this time zone in the given locale.
332     * @exception NullPointerException if {@code locale} is {@code null}.
333     * @since 1.2
334     * @see #getDisplayName(boolean, int, Locale)
335     */
336    public final String getDisplayName(Locale locale) {
337        return getDisplayName(false, LONG, locale);
338    }
339
340    /**
341     * Returns a name in the specified {@code style} of this {@code TimeZone}
342     * suitable for presentation to the user in the default locale. If the
343     * specified {@code daylight} is {@code true}, a Daylight Saving Time name
344     * is returned (even if this {@code TimeZone} doesn't observe Daylight Saving
345     * Time). Otherwise, a Standard Time name is returned.
346     *
347     * <p>This method is equivalent to:
348     * <blockquote><pre>
349     * getDisplayName(daylight, style,
350     *                Locale.getDefault({@link Locale.Category#DISPLAY}))
351     * </pre></blockquote>
352     *
353     * @param daylight {@code true} specifying a Daylight Saving Time name, or
354     *                 {@code false} specifying a Standard Time name
355     * @param style either {@link #LONG} or {@link #SHORT}
356     * @return the human-readable name of this time zone in the default locale.
357     * @exception IllegalArgumentException if {@code style} is invalid.
358     * @since 1.2
359     * @see #getDisplayName(boolean, int, Locale)
360     * @see Locale#getDefault(Locale.Category)
361     * @see Locale.Category
362     * @see java.text.DateFormatSymbols#getZoneStrings()
363     */
364    public final String getDisplayName(boolean daylight, int style) {
365        return getDisplayName(daylight, style,
366                              Locale.getDefault(Locale.Category.DISPLAY));
367    }
368
369    /**
370     * Returns a name in the specified {@code style} of this {@code TimeZone}
371     * suitable for presentation to the user in the specified {@code
372     * locale}. If the specified {@code daylight} is {@code true}, a Daylight
373     * Saving Time name is returned (even if this {@code TimeZone} doesn't
374     * observe Daylight Saving Time). Otherwise, a Standard Time name is
375     * returned.
376     *
377     * <p>When looking up a time zone name, the {@linkplain
378     * ResourceBundle.Control#getCandidateLocales(String,Locale) default
379     * <code>Locale</code> search path of <code>ResourceBundle</code>} derived
380     * from the specified {@code locale} is used. (No {@linkplain
381     * ResourceBundle.Control#getFallbackLocale(String,Locale) fallback
382     * <code>Locale</code>} search is performed.) If a time zone name in any
383     * {@code Locale} of the search path, including {@link Locale#ROOT}, is
384     * found, the name is returned. Otherwise, a string in the
385     * <a href="#NormalizedCustomID">normalized custom ID format</a> is returned.
386     *
387     * @param daylight {@code true} specifying a Daylight Saving Time name, or
388     *                 {@code false} specifying a Standard Time name
389     * @param style either {@link #LONG} or {@link #SHORT}
390     * @param locale   the locale in which to supply the display name.
391     * @return the human-readable name of this time zone in the given locale.
392     * @exception IllegalArgumentException if {@code style} is invalid.
393     * @exception NullPointerException if {@code locale} is {@code null}.
394     * @since 1.2
395     * @see java.text.DateFormatSymbols#getZoneStrings()
396     */
397    public String getDisplayName(boolean daylight, int style, Locale locale) {
398        if (style != SHORT && style != LONG) {
399            throw new IllegalArgumentException("Illegal style: " + style);
400        }
401        String id = getID();
402        String name = TimeZoneNameUtility.retrieveDisplayName(id, daylight, style, locale);
403        if (name != null) {
404            return name;
405        }
406
407        if (id.startsWith("GMT") && id.length() > 3) {
408            char sign = id.charAt(3);
409            if (sign == '+' || sign == '-') {
410                return id;
411            }
412        }
413        int offset = getRawOffset();
414        if (daylight) {
415            offset += getDSTSavings();
416        }
417        return ZoneInfoFile.toCustomID(offset);
418    }
419
420    private static String[] getDisplayNames(String id, Locale locale) {
421        return TimeZoneNameUtility.retrieveDisplayNames(id, locale);
422    }
423
424    /**
425     * Returns the amount of time to be added to local standard time
426     * to get local wall clock time.
427     *
428     * <p>The default implementation returns 3600000 milliseconds
429     * (i.e., one hour) if a call to {@link #useDaylightTime()}
430     * returns {@code true}. Otherwise, 0 (zero) is returned.
431     *
432     * <p>If an underlying {@code TimeZone} implementation subclass
433     * supports historical and future Daylight Saving Time schedule
434     * changes, this method returns the amount of saving time of the
435     * last known Daylight Saving Time rule that can be a future
436     * prediction.
437     *
438     * <p>If the amount of saving time at any given time stamp is
439     * required, construct a {@link Calendar} with this {@code
440     * TimeZone} and the time stamp, and call {@link Calendar#get(int)
441     * Calendar.get}{@code (}{@link Calendar#DST_OFFSET}{@code )}.
442     *
443     * @return the amount of saving time in milliseconds
444     * @since 1.4
445     * @see #inDaylightTime(Date)
446     * @see #getOffset(long)
447     * @see #getOffset(int,int,int,int,int,int)
448     * @see Calendar#ZONE_OFFSET
449     */
450    public int getDSTSavings() {
451        if (useDaylightTime()) {
452            return 3600000;
453        }
454        return 0;
455    }
456
457    /**
458     * Queries if this {@code TimeZone} uses Daylight Saving Time.
459     *
460     * <p>If an underlying {@code TimeZone} implementation subclass
461     * supports historical and future Daylight Saving Time schedule
462     * changes, this method refers to the last known Daylight Saving Time
463     * rule that can be a future prediction and may not be the same as
464     * the current rule. Consider calling {@link #observesDaylightTime()}
465     * if the current rule should also be taken into account.
466     *
467     * @return {@code true} if this {@code TimeZone} uses Daylight Saving Time,
468     *         {@code false}, otherwise.
469     * @see #inDaylightTime(Date)
470     * @see Calendar#DST_OFFSET
471     */
472    public abstract boolean useDaylightTime();
473
474    /**
475     * Returns {@code true} if this {@code TimeZone} is currently in
476     * Daylight Saving Time, or if a transition from Standard Time to
477     * Daylight Saving Time occurs at any future time.
478     *
479     * <p>The default implementation returns {@code true} if
480     * {@code useDaylightTime()} or {@code inDaylightTime(new Date())}
481     * returns {@code true}.
482     *
483     * @return {@code true} if this {@code TimeZone} is currently in
484     * Daylight Saving Time, or if a transition from Standard Time to
485     * Daylight Saving Time occurs at any future time; {@code false}
486     * otherwise.
487     * @since 1.7
488     * @see #useDaylightTime()
489     * @see #inDaylightTime(Date)
490     * @see Calendar#DST_OFFSET
491     */
492    public boolean observesDaylightTime() {
493        return useDaylightTime() || inDaylightTime(new Date());
494    }
495
496    /**
497     * Queries if the given {@code date} is in Daylight Saving Time in
498     * this time zone.
499     *
500     * @param date the given Date.
501     * @return {@code true} if the given date is in Daylight Saving Time,
502     *         {@code false}, otherwise.
503     */
504    public abstract boolean inDaylightTime(Date date);
505
506    /**
507     * Gets the <code>TimeZone</code> for the given ID.
508     *
509     * @param ID the ID for a <code>TimeZone</code>, either an abbreviation
510     * such as "PST", a full name such as "America/Los_Angeles", or a custom
511     * ID such as "GMT-8:00". Note that the support of abbreviations is
512     * for JDK 1.1.x compatibility only and full names should be used.
513     *
514     * @return the specified <code>TimeZone</code>, or the GMT zone if the given ID
515     * cannot be understood.
516     */
517    public static synchronized TimeZone getTimeZone(String ID) {
518        return getTimeZone(ID, true);
519    }
520
521    /**
522     * Gets the {@code TimeZone} for the given {@code zoneId}.
523     *
524     * @param zoneId a {@link ZoneId} from which the time zone ID is obtained
525     * @return the specified {@code TimeZone}, or the GMT zone if the given ID
526     *         cannot be understood.
527     * @throws NullPointerException if {@code zoneId} is {@code null}
528     * @since 1.8
529     */
530    public static TimeZone getTimeZone(ZoneId zoneId) {
531        String tzid = zoneId.getId(); // throws an NPE if null
532        char c = tzid.charAt(0);
533        if (c == '+' || c == '-') {
534            tzid = "GMT" + tzid;
535        } else if (c == 'Z' && tzid.length() == 1) {
536            tzid = "UTC";
537        }
538        return getTimeZone(tzid, true);
539    }
540
541    /**
542     * Converts this {@code TimeZone} object to a {@code ZoneId}.
543     *
544     * @return a {@code ZoneId} representing the same time zone as this
545     *         {@code TimeZone}
546     * @since 1.8
547     */
548    public ZoneId toZoneId() {
549        ZoneId zId = zoneId;
550        if (zId == null) {
551            zoneId = zId = toZoneId0();
552        }
553        return zId;
554    }
555
556    private ZoneId toZoneId0() {
557        String id = getID();
558        TimeZone defaultZone = defaultTimeZone;
559        // are we not defaultTimeZone but our id is equal to default's?
560        if (defaultZone != this &&
561            defaultZone != null && id.equals(defaultZone.getID())) {
562            // delegate to default TZ which is effectively immutable
563            return defaultZone.toZoneId();
564        }
565        // derive it ourselves
566        if (ZoneInfoFile.useOldMapping() && id.length() == 3) {
567            if ("EST".equals(id))
568                return ZoneId.of("America/New_York");
569            if ("MST".equals(id))
570                return ZoneId.of("America/Denver");
571            if ("HST".equals(id))
572                return ZoneId.of("America/Honolulu");
573        }
574        return ZoneId.of(id, ZoneId.SHORT_IDS);
575    }
576
577    private static TimeZone getTimeZone(String ID, boolean fallback) {
578        TimeZone tz = ZoneInfo.getTimeZone(ID);
579        if (tz == null) {
580            tz = parseCustomTimeZone(ID);
581            if (tz == null && fallback) {
582                tz = new ZoneInfo(GMT_ID, 0);
583            }
584        }
585        return tz;
586    }
587
588    /**
589     * Gets the available IDs according to the given time zone offset in milliseconds.
590     *
591     * @param rawOffset the given time zone GMT offset in milliseconds.
592     * @return an array of IDs, where the time zone for that ID has
593     * the specified GMT offset. For example, "America/Phoenix" and "America/Denver"
594     * both have GMT-07:00, but differ in daylight saving behavior.
595     * @see #getRawOffset()
596     */
597    public static synchronized String[] getAvailableIDs(int rawOffset) {
598        return ZoneInfo.getAvailableIDs(rawOffset);
599    }
600
601    /**
602     * Gets all the available IDs supported.
603     * @return an array of IDs.
604     */
605    public static synchronized String[] getAvailableIDs() {
606        return ZoneInfo.getAvailableIDs();
607    }
608
609    /**
610     * Gets the platform defined TimeZone ID.
611     **/
612    private static native String getSystemTimeZoneID(String javaHome);
613
614    /**
615     * Gets the custom time zone ID based on the GMT offset of the
616     * platform. (e.g., "GMT+08:00")
617     */
618    private static native String getSystemGMTOffsetID();
619
620    /**
621     * Gets the default {@code TimeZone} of the Java virtual machine. If the
622     * cached default {@code TimeZone} is available, its clone is returned.
623     * Otherwise, the method takes the following steps to determine the default
624     * time zone.
625     *
626     * <ul>
627     * <li>Use the {@code user.timezone} property value as the default
628     * time zone ID if it's available.</li>
629     * <li>Detect the platform time zone ID. The source of the
630     * platform time zone and ID mapping may vary with implementation.</li>
631     * <li>Use {@code GMT} as the last resort if the given or detected
632     * time zone ID is unknown.</li>
633     * </ul>
634     *
635     * <p>The default {@code TimeZone} created from the ID is cached,
636     * and its clone is returned. The {@code user.timezone} property
637     * value is set to the ID upon return.
638     *
639     * @return the default {@code TimeZone}
640     * @see #setDefault(TimeZone)
641     */
642    public static TimeZone getDefault() {
643        return (TimeZone) getDefaultRef().clone();
644    }
645
646    /**
647     * Returns the reference to the default TimeZone object. This
648     * method doesn't create a clone.
649     */
650    static TimeZone getDefaultRef() {
651        TimeZone defaultZone = defaultTimeZone;
652        if (defaultZone == null) {
653            // Need to initialize the default time zone.
654            defaultZone = setDefaultZone();
655            assert defaultZone != null;
656        }
657        // Don't clone here.
658        return defaultZone;
659    }
660
661    private static synchronized TimeZone setDefaultZone() {
662        TimeZone tz;
663        // get the time zone ID from the system properties
664        Properties props = GetPropertyAction.privilegedGetProperties();
665        String zoneID = props.getProperty("user.timezone");
666
667        // if the time zone ID is not set (yet), perform the
668        // platform to Java time zone ID mapping.
669        if (zoneID == null || zoneID.isEmpty()) {
670            String javaHome = props.getProperty("java.home");
671            try {
672                zoneID = getSystemTimeZoneID(javaHome);
673                if (zoneID == null) {
674                    zoneID = GMT_ID;
675                }
676            } catch (NullPointerException e) {
677                zoneID = GMT_ID;
678            }
679        }
680
681        // Get the time zone for zoneID. But not fall back to
682        // "GMT" here.
683        tz = getTimeZone(zoneID, false);
684
685        if (tz == null) {
686            // If the given zone ID is unknown in Java, try to
687            // get the GMT-offset-based time zone ID,
688            // a.k.a. custom time zone ID (e.g., "GMT-08:00").
689            String gmtOffsetID = getSystemGMTOffsetID();
690            if (gmtOffsetID != null) {
691                zoneID = gmtOffsetID;
692            }
693            tz = getTimeZone(zoneID, true);
694        }
695        assert tz != null;
696
697        final String id = zoneID;
698        props.setProperty("user.timezone", id);
699
700        defaultTimeZone = tz;
701        return tz;
702    }
703
704    /**
705     * Sets the {@code TimeZone} that is returned by the {@code getDefault}
706     * method. {@code zone} is cached. If {@code zone} is null, the cached
707     * default {@code TimeZone} is cleared. This method doesn't change the value
708     * of the {@code user.timezone} property.
709     *
710     * @param zone the new default {@code TimeZone}, or null
711     * @throws SecurityException if the security manager's {@code checkPermission}
712     *                           denies {@code PropertyPermission("user.timezone",
713     *                           "write")}
714     * @see #getDefault
715     * @see PropertyPermission
716     */
717    public static void setDefault(TimeZone zone)
718    {
719        SecurityManager sm = System.getSecurityManager();
720        if (sm != null) {
721            sm.checkPermission(new PropertyPermission
722                               ("user.timezone", "write"));
723        }
724        // by saving a defensive clone and returning a clone in getDefault() too,
725        // the defaultTimeZone instance is isolated from user code which makes it
726        // effectively immutable. This is important to avoid races when the
727        // following is evaluated in ZoneId.systemDefault():
728        // TimeZone.getDefault().toZoneId().
729        defaultTimeZone = (zone == null) ? null : (TimeZone) zone.clone();
730    }
731
732    /**
733     * Returns true if this zone has the same rule and offset as another zone.
734     * That is, if this zone differs only in ID, if at all.  Returns false
735     * if the other zone is null.
736     * @param other the <code>TimeZone</code> object to be compared with
737     * @return true if the other zone is not null and is the same as this one,
738     * with the possible exception of the ID
739     * @since 1.2
740     */
741    public boolean hasSameRules(TimeZone other) {
742        return other != null && getRawOffset() == other.getRawOffset() &&
743            useDaylightTime() == other.useDaylightTime();
744    }
745
746    /**
747     * Creates a copy of this <code>TimeZone</code>.
748     *
749     * @return a clone of this <code>TimeZone</code>
750     */
751    public Object clone()
752    {
753        try {
754            return super.clone();
755        } catch (CloneNotSupportedException e) {
756            throw new InternalError(e);
757        }
758    }
759
760    /**
761     * The null constant as a TimeZone.
762     */
763    static final TimeZone NO_TIMEZONE = null;
764
765    // =======================privates===============================
766
767    /**
768     * The string identifier of this <code>TimeZone</code>.  This is a
769     * programmatic identifier used internally to look up <code>TimeZone</code>
770     * objects from the system table and also to map them to their localized
771     * display names.  <code>ID</code> values are unique in the system
772     * table but may not be for dynamically created zones.
773     * @serial
774     */
775    private String           ID;
776
777    /**
778     * Cached {@link ZoneId} for this TimeZone
779     */
780    private transient ZoneId zoneId;
781
782    private static volatile TimeZone defaultTimeZone;
783
784    static final String         GMT_ID        = "GMT";
785    private static final int    GMT_ID_LENGTH = 3;
786
787    // a static TimeZone we can reference if no AppContext is in place
788    private static volatile TimeZone mainAppContextDefault;
789
790    /**
791     * Parses a custom time zone identifier and returns a corresponding zone.
792     * This method doesn't support the RFC 822 time zone format. (e.g., +hhmm)
793     *
794     * @param id a string of the <a href="#CustomID">custom ID form</a>.
795     * @return a newly created TimeZone with the given offset and
796     * no daylight saving time, or null if the id cannot be parsed.
797     */
798    private static final TimeZone parseCustomTimeZone(String id) {
799        int length;
800
801        // Error if the length of id isn't long enough or id doesn't
802        // start with "GMT".
803        if ((length = id.length()) < (GMT_ID_LENGTH + 2) ||
804            id.indexOf(GMT_ID) != 0) {
805            return null;
806        }
807
808        ZoneInfo zi;
809
810        // First, we try to find it in the cache with the given
811        // id. Even the id is not normalized, the returned ZoneInfo
812        // should have its normalized id.
813        zi = ZoneInfoFile.getZoneInfo(id);
814        if (zi != null) {
815            return zi;
816        }
817
818        int index = GMT_ID_LENGTH;
819        boolean negative = false;
820        char c = id.charAt(index++);
821        if (c == '-') {
822            negative = true;
823        } else if (c != '+') {
824            return null;
825        }
826
827        int hours = 0;
828        int num = 0;
829        int countDelim = 0;
830        int len = 0;
831        while (index < length) {
832            c = id.charAt(index++);
833            if (c == ':') {
834                if (countDelim > 0) {
835                    return null;
836                }
837                if (len > 2) {
838                    return null;
839                }
840                hours = num;
841                countDelim++;
842                num = 0;
843                len = 0;
844                continue;
845            }
846            if (c < '0' || c > '9') {
847                return null;
848            }
849            num = num * 10 + (c - '0');
850            len++;
851        }
852        if (index != length) {
853            return null;
854        }
855        if (countDelim == 0) {
856            if (len <= 2) {
857                hours = num;
858                num = 0;
859            } else {
860                hours = num / 100;
861                num %= 100;
862            }
863        } else {
864            if (len != 2) {
865                return null;
866            }
867        }
868        if (hours > 23 || num > 59) {
869            return null;
870        }
871        int gmtOffset =  (hours * 60 + num) * 60 * 1000;
872
873        if (gmtOffset == 0) {
874            zi = ZoneInfoFile.getZoneInfo(GMT_ID);
875            if (negative) {
876                zi.setID("GMT-00:00");
877            } else {
878                zi.setID("GMT+00:00");
879            }
880        } else {
881            zi = ZoneInfoFile.getCustomTimeZone(id, negative ? -gmtOffset : gmtOffset);
882        }
883        return zi;
884    }
885}
886