1. /*
  2. * @(#)TimeZone.java 1.68 04/01/12
  3. *
  4. * Copyright 2004 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.DateFormatSymbols;
  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.68 01/12/04
  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. if (style != SHORT && style != LONG) {
  327. throw new IllegalArgumentException("Illegal style: " + style);
  328. }
  329. String id = getID();
  330. String[] names = getDisplayNames(id, locale);
  331. if (names == null) {
  332. if (id.startsWith("GMT")) {
  333. char sign = id.charAt(3);
  334. if (sign == '+' || sign == '-') {
  335. return id;
  336. }
  337. }
  338. int offset = getRawOffset();
  339. if (daylight) {
  340. offset += getDSTSavings();
  341. }
  342. return ZoneInfoFile.toCustomID(offset);
  343. }
  344. int index = daylight && useDaylightTime() ? 3 : 1;
  345. if (style == SHORT) {
  346. index++;
  347. }
  348. return names[index];
  349. }
  350. // Cache for managing display names per timezone per locale
  351. // The structure is:
  352. // Map(key=id, value=SoftReference(Map(key=locale, value=displaynames)))
  353. private static Map<String, SoftReference> displayNames;
  354. private static final String[] getDisplayNames(String id, Locale locale) {
  355. synchronized (TimeZone.class) {
  356. if (displayNames == null) {
  357. displayNames = new HashMap<String, SoftReference>();
  358. }
  359. }
  360. synchronized (displayNames) {
  361. String[] names;
  362. SoftReference ref = displayNames.get(id);
  363. Map<Locale, String[]> perLocale;
  364. if (ref != null) {
  365. perLocale = (Map<Locale, String[]>) ref.get();
  366. if (perLocale != null) {
  367. names = perLocale.get(locale);
  368. if (names != null) {
  369. return names;
  370. }
  371. names = retrieveDisplayNames(id, locale);
  372. if (names != null) {
  373. perLocale.put(locale, names);
  374. }
  375. return names;
  376. }
  377. }
  378. names = retrieveDisplayNames(id, locale);
  379. if (names != null) {
  380. perLocale = new HashMap<Locale, String[]>();
  381. perLocale.put(locale, names);
  382. ref = new SoftReference(perLocale);
  383. displayNames.put(id, ref);
  384. }
  385. return names;
  386. }
  387. }
  388. private static final String[] retrieveDisplayNames(String id, Locale locale) {
  389. String[][] tznames = new DateFormatSymbols(locale).getZoneStrings();
  390. for (int i = 0; i < tznames.length; i++) {
  391. String[] names = tznames[i];
  392. if (id.equals(names[0])) {
  393. return names;
  394. }
  395. }
  396. return null;
  397. }
  398. /**
  399. * Returns the amount of time to be added to local standard time
  400. * to get local wall clock time.
  401. * <p>
  402. * The default implementation always returns 3600000 milliseconds
  403. * (i.e., one hour) if this time zone observes Daylight Saving
  404. * Time. Otherwise, 0 (zero) is returned.
  405. * <p>
  406. * If an underlying TimeZone implementation subclass supports
  407. * historical Daylight Saving Time changes, this method returns
  408. * the known latest daylight saving value.
  409. *
  410. * @return the amount of saving time in milliseconds
  411. * @since 1.4
  412. */
  413. public int getDSTSavings() {
  414. if (useDaylightTime()) {
  415. return 3600000;
  416. }
  417. return 0;
  418. }
  419. /**
  420. * Queries if this time zone uses daylight savings time.
  421. * <p>
  422. * If an underlying <code>TimeZone</code> implementation subclass
  423. * supports historical Daylight Saving Time schedule changes, the
  424. * method refers to the latest Daylight Saving Time schedule
  425. * information.
  426. *
  427. * @return true if this time zone uses daylight savings time,
  428. * false, otherwise.
  429. */
  430. public abstract boolean useDaylightTime();
  431. /**
  432. * Queries if the given date is in daylight savings time in
  433. * this time zone.
  434. * @param date the given Date.
  435. * @return true if the given date is in daylight savings time,
  436. * false, otherwise.
  437. */
  438. abstract public boolean inDaylightTime(Date date);
  439. /**
  440. * Gets the <code>TimeZone</code> for the given ID.
  441. *
  442. * @param ID the ID for a <code>TimeZone</code>, either an abbreviation
  443. * such as "PST", a full name such as "America/Los_Angeles", or a custom
  444. * ID such as "GMT-8:00". Note that the support of abbreviations is
  445. * for JDK 1.1.x compatibility only and full names should be used.
  446. *
  447. * @return the specified <code>TimeZone</code>, or the GMT zone if the given ID
  448. * cannot be understood.
  449. */
  450. public static synchronized TimeZone getTimeZone(String ID) {
  451. return getTimeZone(ID, true);
  452. }
  453. private static TimeZone getTimeZone(String ID, boolean fallback) {
  454. TimeZone tz = ZoneInfo.getTimeZone(ID);
  455. if (tz == null) {
  456. tz = parseCustomTimeZone(ID);
  457. if (tz == null && fallback) {
  458. tz = new ZoneInfo(GMT_ID, 0);
  459. }
  460. }
  461. return tz;
  462. }
  463. /**
  464. * Gets the available IDs according to the given time zone offset.
  465. * @param rawOffset the given time zone GMT offset.
  466. * @return an array of IDs, where the time zone for that ID has
  467. * the specified GMT offset. For example, "America/Phoenix" and "America/Denver"
  468. * both have GMT-07:00, but differ in daylight savings behavior.
  469. */
  470. public static synchronized String[] getAvailableIDs(int rawOffset) {
  471. return ZoneInfo.getAvailableIDs(rawOffset);
  472. }
  473. /**
  474. * Gets all the available IDs supported.
  475. * @return an array of IDs.
  476. */
  477. public static synchronized String[] getAvailableIDs() {
  478. return ZoneInfo.getAvailableIDs();
  479. }
  480. /**
  481. * Gets the platform defined TimeZone ID.
  482. **/
  483. private static native String getSystemTimeZoneID(String javaHome,
  484. String country);
  485. /**
  486. * Gets the custom time zone ID based on the GMT offset of the
  487. * platform. (e.g., "GMT+08:00")
  488. */
  489. private static native String getSystemGMTOffsetID();
  490. /**
  491. * Gets the default <code>TimeZone</code> for this host.
  492. * The source of the default <code>TimeZone</code>
  493. * may vary with implementation.
  494. * @return a default <code>TimeZone</code>.
  495. * @see #setDefault
  496. */
  497. public static synchronized TimeZone getDefault() {
  498. TimeZone defaultZone = (TimeZone) defaultZoneTL.get();
  499. if (defaultZone == null) {
  500. setDefaultZone();
  501. defaultZone = (TimeZone) defaultZoneTL.get();
  502. }
  503. return (TimeZone) defaultZone.clone();
  504. }
  505. /**
  506. * Returns the reference to the default TimeZone object. This
  507. * method doesn't create a clone.
  508. */
  509. static synchronized TimeZone getDefaultRef() {
  510. TimeZone defaultZone = (TimeZone) defaultZoneTL.get();
  511. if (defaultZone == null) {
  512. setDefaultZone();
  513. defaultZone = (TimeZone) defaultZoneTL.get();
  514. }
  515. return defaultZone;
  516. }
  517. private static void setDefaultZone() {
  518. TimeZone tz = null;
  519. // get the time zone ID from the system properties
  520. String zoneID = (String) AccessController.doPrivileged(
  521. new GetPropertyAction("user.timezone"));
  522. // if the time zone ID is not set (yet), perform the
  523. // platform to Java time zone ID mapping.
  524. if (zoneID == null || zoneID.equals("")) {
  525. String country = (String) AccessController.doPrivileged(
  526. new GetPropertyAction("user.country"));
  527. String javaHome = (String) AccessController.doPrivileged(
  528. new GetPropertyAction("java.home"));
  529. try {
  530. zoneID = getSystemTimeZoneID(javaHome, country);
  531. if (zoneID == null) {
  532. zoneID = GMT_ID;
  533. }
  534. } catch (NullPointerException e) {
  535. zoneID = GMT_ID;
  536. }
  537. }
  538. // Get the time zone for zoneID. But not fall back to
  539. // "GMT" here.
  540. tz = getTimeZone(zoneID, false);
  541. if (tz == null) {
  542. // If the given zone ID is unknown in Java, try to
  543. // get the GMT-offset-based time zone ID,
  544. // a.k.a. custom time zone ID (e.g., "GMT-08:00").
  545. String gmtOffsetID = getSystemGMTOffsetID();
  546. if (gmtOffsetID != null) {
  547. zoneID = gmtOffsetID;
  548. }
  549. tz = getTimeZone(zoneID, true);
  550. }
  551. assert tz != null;
  552. final String id = zoneID;
  553. AccessController.doPrivileged(new PrivilegedAction() {
  554. public Object run() {
  555. System.setProperty("user.timezone", id);
  556. return null;
  557. }
  558. });
  559. defaultZoneTL.set(tz);
  560. }
  561. /**
  562. * Sets the <code>TimeZone</code> that is
  563. * returned by the <code>getDefault</code> method. If <code>zone</code>
  564. * is null, reset the default to the value it had originally when the
  565. * VM first started.
  566. * @param zone the new default time zone
  567. * @see #getDefault
  568. */
  569. public static synchronized void setDefault(TimeZone zone)
  570. {
  571. defaultZoneTL.set(zone);
  572. }
  573. /**
  574. * Returns true if this zone has the same rule and offset as another zone.
  575. * That is, if this zone differs only in ID, if at all. Returns false
  576. * if the other zone is null.
  577. * @param other the <code>TimeZone</code> object to be compared with
  578. * @return true if the other zone is not null and is the same as this one,
  579. * with the possible exception of the ID
  580. * @since 1.2
  581. */
  582. public boolean hasSameRules(TimeZone other) {
  583. return other != null && getRawOffset() == other.getRawOffset() &&
  584. useDaylightTime() == other.useDaylightTime();
  585. }
  586. /**
  587. * Creates a copy of this <code>TimeZone</code>.
  588. *
  589. * @return a clone of this <code>TimeZone</code>
  590. */
  591. public Object clone()
  592. {
  593. try {
  594. TimeZone other = (TimeZone) super.clone();
  595. other.ID = ID;
  596. return other;
  597. } catch (CloneNotSupportedException e) {
  598. throw new InternalError();
  599. }
  600. }
  601. /**
  602. * The null constant as a TimeZone.
  603. */
  604. static final TimeZone NO_TIMEZONE = null;
  605. // =======================privates===============================
  606. /**
  607. * The string identifier of this <code>TimeZone</code>. This is a
  608. * programmatic identifier used internally to look up <code>TimeZone</code>
  609. * objects from the system table and also to map them to their localized
  610. * display names. <code>ID</code> values are unique in the system
  611. * table but may not be for dynamically created zones.
  612. * @serial
  613. */
  614. private String ID;
  615. private static final InheritableThreadLocal defaultZoneTL
  616. = new InheritableThreadLocal();
  617. static final String GMT_ID = "GMT";
  618. private static final int GMT_ID_LENGTH = 3;
  619. /**
  620. * Parses a custom time zone identifier and returns a corresponding zone.
  621. * This method doesn't support the RFC 822 time zone format. (e.g., +hhmm)
  622. *
  623. * @param id a string of the <a href="#CustomID">custom ID form</a>.
  624. * @return a newly created TimeZone with the given offset and
  625. * no daylight saving time, or null if the id cannot be parsed.
  626. */
  627. private static final TimeZone parseCustomTimeZone(String id) {
  628. int length;
  629. // Error if the length of id isn't long enough or id doesn't
  630. // start with "GMT".
  631. if ((length = id.length()) < (GMT_ID_LENGTH + 2) ||
  632. id.indexOf(GMT_ID) != 0) {
  633. return null;
  634. }
  635. ZoneInfo zi;
  636. // First, we try to find it in the cache with the given
  637. // id. Even the id is not normalized, the returned ZoneInfo
  638. // should have its normalized id.
  639. zi = ZoneInfoFile.getZoneInfo(id);
  640. if (zi != null) {
  641. return zi;
  642. }
  643. int index = GMT_ID_LENGTH;
  644. boolean negative = false;
  645. char c = id.charAt(index++);
  646. if (c == '-') {
  647. negative = true;
  648. } else if (c != '+') {
  649. return null;
  650. }
  651. int hours = 0;
  652. int num = 0;
  653. int countDelim = 0;
  654. int len = 0;
  655. while (index < length) {
  656. c = id.charAt(index++);
  657. if (c == ':') {
  658. if (countDelim > 0) {
  659. return null;
  660. }
  661. if (len > 2) {
  662. return null;
  663. }
  664. hours = num;
  665. countDelim++;
  666. num = 0;
  667. len = 0;
  668. continue;
  669. }
  670. if (c < '0' || c > '9') {
  671. return null;
  672. }
  673. num = num * 10 + (c - '0');
  674. len++;
  675. }
  676. if (index != length) {
  677. return null;
  678. }
  679. if (countDelim == 0) {
  680. if (len <= 2) {
  681. hours = num;
  682. num = 0;
  683. } else {
  684. hours = num / 100;
  685. num %= 100;
  686. }
  687. } else {
  688. if (len != 2) {
  689. return null;
  690. }
  691. }
  692. if (hours > 23 || num > 59) {
  693. return null;
  694. }
  695. int gmtOffset = (hours * 60 + num) * 60 * 1000;
  696. if (gmtOffset == 0) {
  697. zi = ZoneInfoFile.getZoneInfo(GMT_ID);
  698. if (negative) {
  699. zi.setID("GMT-00:00");
  700. } else {
  701. zi.setID("GMT+00:00");
  702. }
  703. } else {
  704. zi = ZoneInfoFile.getCustomTimeZone(id, negative ? -gmtOffset : gmtOffset);
  705. }
  706. return zi;
  707. }
  708. }