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