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