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