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