1 /* 2 * Copyright (C) 2014 The Android Open Source Project 3 * Copyright (c) 1996, 2021, Oracle and/or its affiliates. All rights reserved. 4 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 5 * 6 * This code is free software; you can redistribute it and/or modify it 7 * under the terms of the GNU General Public License version 2 only, as 8 * published by the Free Software Foundation. Oracle designates this 9 * particular file as subject to the "Classpath" exception as provided 10 * by Oracle in the LICENSE file that accompanied this code. 11 * 12 * This code is distributed in the hope that it will be useful, but WITHOUT 13 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 14 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 15 * version 2 for more details (a copy is included in the LICENSE file that 16 * accompanied this code). 17 * 18 * You should have received a copy of the GNU General Public License version 19 * 2 along with this work; if not, write to the Free Software Foundation, 20 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 21 * 22 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 23 * or visit www.oracle.com if you need additional information or have any 24 * questions. 25 */ 26 27 /* 28 * (C) Copyright Taligent, Inc. 1996 - All Rights Reserved 29 * (C) Copyright IBM Corp. 1996 - All Rights Reserved 30 * 31 * The original version of this source code and documentation is copyrighted 32 * and owned by Taligent, Inc., a wholly-owned subsidiary of IBM. These 33 * materials are provided under terms of a License Agreement between Taligent 34 * and Sun. This technology is protected by multiple US and International 35 * patents. This notice and attribution to Taligent may not be removed. 36 * Taligent is a registered trademark of Taligent, Inc. 37 * 38 */ 39 40 package java.util; 41 42 import android.icu.text.TimeZoneNames; 43 import com.android.i18n.timezone.ZoneInfoData; 44 import com.android.i18n.timezone.ZoneInfoDb; 45 import com.android.icu.util.ExtendedTimeZone; 46 47 import java.io.IOException; 48 import java.io.Serializable; 49 import java.time.ZoneId; 50 import java.util.function.Supplier; 51 import java.util.regex.Matcher; 52 import java.util.regex.Pattern; 53 import libcore.io.IoUtils; 54 import libcore.util.ZoneInfo; 55 56 import dalvik.system.RuntimeHooks; 57 58 /** 59 * {@code TimeZone} represents a time zone offset, and also figures out daylight 60 * savings. 61 * 62 * <p> 63 * Typically, you get a {@code TimeZone} using {@code getDefault} 64 * which creates a {@code TimeZone} based on the time zone where the program 65 * is running. For example, for a program running in Japan, {@code getDefault} 66 * creates a {@code TimeZone} object based on Japanese Standard Time. 67 * 68 * <p> 69 * You can also get a {@code TimeZone} using {@code getTimeZone} 70 * along with a time zone ID. For instance, the time zone ID for the 71 * U.S. Pacific Time zone is "America/Los_Angeles". So, you can get a 72 * U.S. Pacific Time {@code TimeZone} object with: 73 * <blockquote><pre> 74 * TimeZone tz = TimeZone.getTimeZone("America/Los_Angeles"); 75 * </pre></blockquote> 76 * You can use the {@code getAvailableIDs} method to iterate through 77 * all the supported time zone IDs. You can then choose a 78 * supported ID to get a {@code TimeZone}. 79 * If the time zone you want is not represented by one of the 80 * supported IDs, then a custom time zone ID can be specified to 81 * produce a TimeZone. The syntax of a custom time zone ID is: 82 * 83 * <blockquote><pre> 84 * <a id="CustomID"><i>CustomID:</i></a> 85 * {@code GMT} <i>Sign</i> <i>Hours</i> {@code :} <i>Minutes</i> 86 * {@code GMT} <i>Sign</i> <i>Hours</i> <i>Minutes</i> 87 * {@code GMT} <i>Sign</i> <i>Hours</i> 88 * <i>Sign:</i> one of 89 * {@code + -} 90 * <i>Hours:</i> 91 * <i>Digit</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} 97 * </pre></blockquote> 98 * 99 * <i>Hours</i> must be between 0 to 23 and <i>Minutes</i> must be 100 * between 00 to 59. For example, "GMT+10" and "GMT+0010" mean ten 101 * hours and ten minutes ahead of GMT, respectively. 102 * <p> 103 * The format is locale independent and digits must be taken from the 104 * Basic Latin block of the Unicode standard. No daylight saving time 105 * transition schedule can be specified with a custom time zone ID. If 106 * the specified string doesn't match the syntax, {@code "GMT"} 107 * is used. 108 * <p> 109 * When creating a {@code TimeZone}, the specified custom time 110 * zone ID is normalized in the following syntax: 111 * <blockquote><pre> 112 * <a id="NormalizedCustomID"><i>NormalizedCustomID:</i></a> 113 * {@code GMT} <i>Sign</i> <i>TwoDigitHours</i> {@code :} <i>Minutes</i> 114 * <i>Sign:</i> one of 115 * {@code + -} 116 * <i>TwoDigitHours:</i> 117 * <i>Digit</i> <i>Digit</i> 118 * <i>Minutes:</i> 119 * <i>Digit</i> <i>Digit</i> 120 * <i>Digit:</i> one of 121 * {@code 0 1 2 3 4 5 6 7 8 9} 122 * </pre></blockquote> 123 * For example, TimeZone.getTimeZone("GMT-8").getID() returns "GMT-08:00". 124 * 125 * <h2>Three-letter time zone IDs</h2> 126 * 127 * For compatibility with JDK 1.1.x, some other three-letter time zone IDs 128 * (such as "PST", "CTT", "AST") are also supported. However, <strong>their 129 * use is deprecated</strong> because the same abbreviation is often used 130 * for multiple time zones (for example, "CST" could be U.S. "Central Standard 131 * Time" and "China Standard Time"), and the Java platform can then only 132 * recognize one of them. 133 * 134 * 135 * @see Calendar 136 * @see GregorianCalendar 137 * @see SimpleTimeZone 138 * @author Mark Davis, David Goldsmith, Chen-Lieh Huang, Alan Liu 139 * @since 1.1 140 */ 141 public abstract class TimeZone implements Serializable, Cloneable { 142 /** 143 * Sole constructor. (For invocation by subclass constructors, typically 144 * implicit.) 145 */ TimeZone()146 public TimeZone() { 147 } 148 149 /** 150 * A style specifier for {@code getDisplayName()} indicating 151 * a short name, such as "PST." 152 * @see #LONG 153 * @since 1.2 154 */ 155 public static final int SHORT = 0; 156 157 /** 158 * A style specifier for {@code getDisplayName()} indicating 159 * a long name, such as "Pacific Standard Time." 160 * @see #SHORT 161 * @since 1.2 162 */ 163 public static final int LONG = 1; 164 165 // Android-changed: Use a preload holder to allow compile-time initialization of TimeZone and 166 // dependents. 167 private static class NoImagePreloadHolder { 168 public static final Pattern CUSTOM_ZONE_ID_PATTERN = Pattern.compile("^GMT[-+](\\d{1,2})(:?(\\d\\d))?$"); 169 } 170 171 // Proclaim serialization compatibility with JDK 1.1 172 @java.io.Serial 173 static final long serialVersionUID = 3581463369166924961L; 174 175 // Android-changed: common timezone instances. 176 private static final TimeZone GMT = new SimpleTimeZone(0, "GMT"); 177 private static final TimeZone UTC = new SimpleTimeZone(0, "UTC"); 178 179 /** 180 * Gets the time zone offset, for current date, modified in case of 181 * daylight savings. This is the offset to add to UTC to get local time. 182 * <p> 183 * This method returns a historically correct offset if an 184 * underlying {@code TimeZone} implementation subclass 185 * supports historical Daylight Saving Time schedule and GMT 186 * offset changes. 187 * 188 * @param era the era of the given date. 189 * @param year the year in the given date. 190 * @param month the month in the given date. 191 * Month is 0-based. e.g., 0 for January. 192 * @param day the day-in-month of the given date. 193 * @param dayOfWeek the day-of-week of the given date. 194 * @param milliseconds the milliseconds in day in <em>standard</em> 195 * local time. 196 * 197 * @return the offset in milliseconds to add to GMT to get local time. 198 * 199 * @see Calendar#ZONE_OFFSET 200 * @see Calendar#DST_OFFSET 201 */ getOffset(int era, int year, int month, int day, int dayOfWeek, int milliseconds)202 public abstract int getOffset(int era, int year, int month, int day, 203 int dayOfWeek, int milliseconds); 204 205 /** 206 * Returns the offset of this time zone from UTC at the specified 207 * date. If Daylight Saving Time is in effect at the specified 208 * date, the offset value is adjusted with the amount of daylight 209 * saving. 210 * <p> 211 * This method returns a historically correct offset value if an 212 * underlying TimeZone implementation subclass supports historical 213 * Daylight Saving Time schedule and GMT offset changes. 214 * 215 * @param date the date represented in milliseconds since January 1, 1970 00:00:00 GMT 216 * @return the amount of time in milliseconds to add to UTC to get local time. 217 * 218 * @see Calendar#ZONE_OFFSET 219 * @see Calendar#DST_OFFSET 220 * @since 1.4 221 */ getOffset(long date)222 public int getOffset(long date) { 223 if (inDaylightTime(new Date(date))) { 224 return getRawOffset() + getDSTSavings(); 225 } 226 return getRawOffset(); 227 } 228 229 /** 230 * Gets the raw GMT offset and the amount of daylight saving of this 231 * time zone at the given time. 232 * @param date the milliseconds (since January 1, 1970, 233 * 00:00:00.000 GMT) at which the time zone offset and daylight 234 * saving amount are found 235 * @param offsets an array of int where the raw GMT offset 236 * (offset[0]) and daylight saving amount (offset[1]) are stored, 237 * or null if those values are not needed. The method assumes that 238 * the length of the given array is two or larger. 239 * @return the total amount of the raw GMT offset and daylight 240 * saving at the specified date. 241 * 242 * @see Calendar#ZONE_OFFSET 243 * @see Calendar#DST_OFFSET 244 */ getOffsets(long date, int[] offsets)245 int getOffsets(long date, int[] offsets) { 246 int rawoffset = getRawOffset(); 247 int dstoffset = 0; 248 if (inDaylightTime(new Date(date))) { 249 dstoffset = getDSTSavings(); 250 } 251 if (offsets != null) { 252 offsets[0] = rawoffset; 253 offsets[1] = dstoffset; 254 } 255 return rawoffset + dstoffset; 256 } 257 258 /** 259 * Sets the base time zone offset to GMT. 260 * This is the offset to add to UTC to get local time. 261 * <p> 262 * If an underlying {@code TimeZone} implementation subclass 263 * supports historical GMT offset changes, the specified GMT 264 * offset is set as the latest GMT offset and the difference from 265 * the known latest GMT offset value is used to adjust all 266 * historical GMT offset values. 267 * 268 * @param offsetMillis the given base time zone offset to GMT. 269 */ setRawOffset(int offsetMillis)270 public abstract void setRawOffset(int offsetMillis); 271 272 /** 273 * Returns the amount of time in milliseconds to add to UTC to get 274 * standard time in this time zone. Because this value is not 275 * affected by daylight saving time, it is called <I>raw 276 * offset</I>. 277 * <p> 278 * If an underlying {@code TimeZone} implementation subclass 279 * supports historical GMT offset changes, the method returns the 280 * raw offset value of the current date. In Honolulu, for example, 281 * its raw offset changed from GMT-10:30 to GMT-10:00 in 1947, and 282 * this method always returns -36000000 milliseconds (i.e., -10 283 * hours). 284 * 285 * @return the amount of raw offset time in milliseconds to add to UTC. 286 * @see Calendar#ZONE_OFFSET 287 */ getRawOffset()288 public abstract int getRawOffset(); 289 290 /** 291 * Gets the ID of this time zone. 292 * @return the ID of this time zone. 293 */ getID()294 public String getID() 295 { 296 return ID; 297 } 298 299 /** 300 * Sets the time zone ID. This does not change any other data in 301 * the time zone object. 302 * @param ID the new time zone ID. 303 */ setID(String ID)304 public void setID(String ID) 305 { 306 if (ID == null) { 307 throw new NullPointerException(); 308 } 309 this.ID = ID; 310 } 311 312 /** 313 * Returns a long standard time name of this {@code TimeZone} suitable for 314 * presentation to the user in the default locale. 315 * 316 * <p>This method is equivalent to: 317 * <blockquote><pre> 318 * getDisplayName(false, {@link #LONG}, 319 * Locale.getDefault({@link Locale.Category#DISPLAY})) 320 * </pre></blockquote> 321 * 322 * @return the human-readable name of this time zone in the default locale. 323 * @since 1.2 324 * @see #getDisplayName(boolean, int, Locale) 325 * @see Locale#getDefault(Locale.Category) 326 * @see Locale.Category 327 */ getDisplayName()328 public final String getDisplayName() { 329 return getDisplayName(false, LONG, 330 Locale.getDefault(Locale.Category.DISPLAY)); 331 } 332 333 /** 334 * Returns a long standard time name of this {@code TimeZone} suitable for 335 * presentation to the user in the specified {@code locale}. 336 * 337 * <p>This method is equivalent to: 338 * <blockquote><pre> 339 * getDisplayName(false, {@link #LONG}, locale) 340 * </pre></blockquote> 341 * 342 * @param locale the locale in which to supply the display name. 343 * @return the human-readable name of this time zone in the given locale. 344 * @throws NullPointerException if {@code locale} is {@code null}. 345 * @since 1.2 346 * @see #getDisplayName(boolean, int, Locale) 347 */ getDisplayName(Locale locale)348 public final String getDisplayName(Locale locale) { 349 return getDisplayName(false, LONG, locale); 350 } 351 352 /** 353 * Returns a name in the specified {@code style} of this {@code TimeZone} 354 * suitable for presentation to the user in the default locale. If the 355 * specified {@code daylight} is {@code true}, a Daylight Saving Time name 356 * is returned (even if this {@code TimeZone} doesn't observe Daylight Saving 357 * Time). Otherwise, a Standard Time name is returned. 358 * 359 * <p>This method is equivalent to: 360 * <blockquote><pre> 361 * getDisplayName(daylight, style, 362 * Locale.getDefault({@link Locale.Category#DISPLAY})) 363 * </pre></blockquote> 364 * 365 * @param daylight {@code true} specifying a Daylight Saving Time name, or 366 * {@code false} specifying a Standard Time name 367 * @param style either {@link #LONG} or {@link #SHORT} 368 * @return the human-readable name of this time zone in the default locale. 369 * @throws IllegalArgumentException if {@code style} is invalid. 370 * @since 1.2 371 * @see #getDisplayName(boolean, int, Locale) 372 * @see Locale#getDefault(Locale.Category) 373 * @see Locale.Category 374 * @see java.text.DateFormatSymbols#getZoneStrings() 375 */ getDisplayName(boolean daylight, int style)376 public final String getDisplayName(boolean daylight, int style) { 377 return getDisplayName(daylight, style, 378 Locale.getDefault(Locale.Category.DISPLAY)); 379 } 380 381 /** 382 * Returns the {@link #SHORT short} or {@link #LONG long} name of this time 383 * zone with either standard or daylight time, as written in {@code locale}. 384 * If the name is not available, the result is in the format 385 * {@code GMT[+-]hh:mm}. 386 * 387 * @param daylightTime true for daylight time, false for standard time. 388 * @param style either {@link TimeZone#LONG} or {@link TimeZone#SHORT}. 389 * @param locale the display locale. 390 */ getDisplayName(boolean daylightTime, int style, Locale locale)391 public String getDisplayName(boolean daylightTime, int style, Locale locale) { 392 // BEGIN Android-changed: implement using android.icu.text.TimeZoneNames 393 TimeZoneNames.NameType nameType; 394 switch (style) { 395 case SHORT: 396 nameType = daylightTime 397 ? TimeZoneNames.NameType.SHORT_DAYLIGHT 398 : TimeZoneNames.NameType.SHORT_STANDARD; 399 break; 400 case LONG: 401 nameType = daylightTime 402 ? TimeZoneNames.NameType.LONG_DAYLIGHT 403 : TimeZoneNames.NameType.LONG_STANDARD; 404 break; 405 default: 406 throw new IllegalArgumentException("Illegal style: " + style); 407 } 408 String canonicalID = android.icu.util.TimeZone.getCanonicalID(getID()); 409 if (canonicalID != null) { 410 TimeZoneNames names = TimeZoneNames.getInstance(locale); 411 long now = System.currentTimeMillis(); 412 String displayName = names.getDisplayName(canonicalID, nameType, now); 413 if (displayName != null) { 414 return displayName; 415 } 416 } 417 418 // We get here if this is a custom timezone or ICU doesn't have name data for the specific 419 // style and locale. 420 int offsetMillis = getRawOffset(); 421 if (daylightTime) { 422 offsetMillis += getDSTSavings(); 423 } 424 return createGmtOffsetString(true /* includeGmt */, true /* includeMinuteSeparator */, 425 offsetMillis); 426 // END Android-changed: implement using android.icu.text.TimeZoneNames 427 } 428 429 // BEGIN Android-added: utility method to format an offset as a GMT offset string. 430 /** 431 * Returns a string representation of an offset from UTC. 432 * 433 * <p>The format is "[GMT](+|-)HH[:]MM". The output is not localized. 434 * 435 * @param includeGmt true to include "GMT", false to exclude 436 * @param includeMinuteSeparator true to include the separator between hours and minutes, false 437 * to exclude. 438 * @param offsetMillis the offset from UTC 439 * 440 * @hide used internally by SimpleDateFormat 441 */ createGmtOffsetString(boolean includeGmt, boolean includeMinuteSeparator, int offsetMillis)442 public static String createGmtOffsetString(boolean includeGmt, 443 boolean includeMinuteSeparator, int offsetMillis) { 444 int offsetMinutes = offsetMillis / 60000; 445 char sign = '+'; 446 if (offsetMinutes < 0) { 447 sign = '-'; 448 offsetMinutes = -offsetMinutes; 449 } 450 StringBuilder builder = new StringBuilder(9); 451 if (includeGmt) { 452 builder.append("GMT"); 453 } 454 builder.append(sign); 455 appendNumber(builder, 2, offsetMinutes / 60); 456 if (includeMinuteSeparator) { 457 builder.append(':'); 458 } 459 appendNumber(builder, 2, offsetMinutes % 60); 460 return builder.toString(); 461 } 462 appendNumber(StringBuilder builder, int count, int value)463 private static void appendNumber(StringBuilder builder, int count, int value) { 464 String string = Integer.toString(value); 465 for (int i = 0; i < count - string.length(); i++) { 466 builder.append('0'); 467 } 468 builder.append(string); 469 } 470 // END Android-added: utility method to format an offset as a GMT offset string. 471 472 /** 473 * Returns the amount of time to be added to local standard time 474 * to get local wall clock time. 475 * 476 * <p>The default implementation returns 3600000 milliseconds 477 * (i.e., one hour) if a call to {@link #useDaylightTime()} 478 * returns {@code true}. Otherwise, 0 (zero) is returned. 479 * 480 * <p>If an underlying {@code TimeZone} implementation subclass 481 * supports historical and future Daylight Saving Time schedule 482 * changes, this method returns the amount of saving time of the 483 * last known Daylight Saving Time rule that can be a future 484 * prediction. 485 * 486 * <p>If the amount of saving time at any given time stamp is 487 * required, construct a {@link Calendar} with this {@code 488 * TimeZone} and the time stamp, and call {@link Calendar#get(int) 489 * Calendar.get}{@code (}{@link Calendar#DST_OFFSET}{@code )}. 490 * 491 * @return the amount of saving time in milliseconds 492 * @since 1.4 493 * @see #inDaylightTime(Date) 494 * @see #getOffset(long) 495 * @see #getOffset(int,int,int,int,int,int) 496 * @see Calendar#ZONE_OFFSET 497 */ getDSTSavings()498 public int getDSTSavings() { 499 if (useDaylightTime()) { 500 return 3600000; 501 } 502 return 0; 503 } 504 505 /** 506 * Queries if this {@code TimeZone} uses Daylight Saving Time. 507 * 508 * <p>If an underlying {@code TimeZone} implementation subclass 509 * supports historical and future Daylight Saving Time schedule 510 * changes, this method refers to the last known Daylight Saving Time 511 * rule that can be a future prediction and may not be the same as 512 * the current rule. Consider calling {@link #observesDaylightTime()} 513 * if the current rule should also be taken into account. 514 * 515 * @return {@code true} if this {@code TimeZone} uses Daylight Saving Time, 516 * {@code false}, otherwise. 517 * @see #inDaylightTime(Date) 518 * @see Calendar#DST_OFFSET 519 */ useDaylightTime()520 public abstract boolean useDaylightTime(); 521 522 /** 523 * Returns {@code true} if this {@code TimeZone} is currently in 524 * Daylight Saving Time, or if a transition from Standard Time to 525 * Daylight Saving Time occurs at any future time. 526 * 527 * <p>The default implementation returns {@code true} if 528 * {@code useDaylightTime()} or {@code inDaylightTime(new Date())} 529 * returns {@code true}. 530 * 531 * @return {@code true} if this {@code TimeZone} is currently in 532 * Daylight Saving Time, or if a transition from Standard Time to 533 * Daylight Saving Time occurs at any future time; {@code false} 534 * otherwise. 535 * @since 1.7 536 * @see #useDaylightTime() 537 * @see #inDaylightTime(Date) 538 * @see Calendar#DST_OFFSET 539 */ observesDaylightTime()540 public boolean observesDaylightTime() { 541 return useDaylightTime() || inDaylightTime(new Date()); 542 } 543 544 /** 545 * Queries if the given {@code date} is in Daylight Saving Time in 546 * this time zone. 547 * 548 * @param date the given Date. 549 * @return {@code true} if the given date is in Daylight Saving Time, 550 * {@code false}, otherwise. 551 */ inDaylightTime(Date date)552 public abstract boolean inDaylightTime(Date date); 553 554 /** 555 * Gets the {@code TimeZone} for the given ID. 556 * 557 * @param id the ID for a <code>TimeZone</code>, either an abbreviation 558 * such as "PST", a full name such as "America/Los_Angeles", or a custom 559 * ID such as "GMT-8:00". Note that the support of abbreviations is 560 * for JDK 1.1.x compatibility only and full names should be used. 561 * 562 * @return the specified {@code TimeZone}, or the GMT zone if the given ID 563 * cannot be understood. 564 */ 565 // Android-changed: param s/ID/id; use ZoneInfoDb instead of ZoneInfo class. getTimeZone(String id)566 public static synchronized TimeZone getTimeZone(String id) { 567 if (id == null) { 568 throw new NullPointerException("id == null"); 569 } 570 571 // Special cases? These can clone an existing instance. 572 if (id.length() == 3) { 573 if (id.equals("GMT")) { 574 return (TimeZone) GMT.clone(); 575 } 576 if (id.equals("UTC")) { 577 return (TimeZone) UTC.clone(); 578 } 579 } 580 581 // In the database? 582 583 ZoneInfoData zoneInfoData = ZoneInfoDb.getInstance().makeZoneInfoData(id); 584 TimeZone zone = zoneInfoData == null ? null : ZoneInfo.createZoneInfo(zoneInfoData); 585 586 // Custom time zone? 587 if (zone == null && id.length() > 3 && id.startsWith("GMT")) { 588 zone = getCustomTimeZone(id); 589 } 590 591 // We never return null; on failure we return the equivalent of "GMT". 592 return (zone != null) ? zone : (TimeZone) GMT.clone(); 593 } 594 595 /** 596 * Gets the {@code TimeZone} for the given {@code zoneId}. 597 * 598 * @param zoneId a {@link ZoneId} from which the time zone ID is obtained 599 * @return the specified {@code TimeZone}, or the GMT zone if the given ID 600 * cannot be understood. 601 * @throws NullPointerException if {@code zoneId} is {@code null} 602 * @since 1.8 603 */ getTimeZone(ZoneId zoneId)604 public static TimeZone getTimeZone(ZoneId zoneId) { 605 String tzid = zoneId.getId(); // throws an NPE if null 606 char c = tzid.charAt(0); 607 if (c == '+' || c == '-') { 608 tzid = "GMT" + tzid; 609 } else if (c == 'Z' && tzid.length() == 1) { 610 tzid = "UTC"; 611 } 612 return getTimeZone(tzid); 613 } 614 615 /** 616 * Converts this {@code TimeZone} object to a {@code ZoneId}. 617 * 618 * @return a {@code ZoneId} representing the same time zone as this 619 * {@code TimeZone} 620 * @since 1.8 621 */ toZoneId()622 public ZoneId toZoneId() { 623 // Android-changed: don't support "old mapping" 624 return ZoneId.of(getID(), ZoneId.SHORT_IDS); 625 } 626 627 /** 628 * Returns a new SimpleTimeZone for an ID of the form "GMT[+|-]hh[[:]mm]", or null. 629 */ getCustomTimeZone(String id)630 private static TimeZone getCustomTimeZone(String id) { 631 Matcher m = NoImagePreloadHolder.CUSTOM_ZONE_ID_PATTERN.matcher(id); 632 if (!m.matches()) { 633 return null; 634 } 635 636 int hour; 637 int minute = 0; 638 try { 639 hour = Integer.parseInt(m.group(1)); 640 if (m.group(3) != null) { 641 minute = Integer.parseInt(m.group(3)); 642 } 643 } catch (NumberFormatException impossible) { 644 throw new AssertionError(impossible); 645 } 646 647 if (hour < 0 || hour > 23 || minute < 0 || minute > 59) { 648 return null; 649 } 650 651 char sign = id.charAt(3); 652 int raw = (hour * 3600000) + (minute * 60000); 653 if (sign == '-') { 654 raw = -raw; 655 } 656 657 String cleanId = String.format(Locale.ROOT, "GMT%c%02d:%02d", sign, hour, minute); 658 659 return new SimpleTimeZone(raw, cleanId); 660 } 661 662 /** 663 * Gets the available IDs according to the given time zone offset in milliseconds. 664 * 665 * @param rawOffset the given time zone GMT offset in milliseconds. 666 * @return an array of IDs, where the time zone for that ID has 667 * the specified GMT offset. For example, "America/Phoenix" and "America/Denver" 668 * both have GMT-07:00, but differ in daylight saving behavior. 669 * @see #getRawOffset() 670 */ getAvailableIDs(int rawOffset)671 public static synchronized String[] getAvailableIDs(int rawOffset) { 672 return ZoneInfoDb.getInstance().getAvailableIDs(rawOffset); 673 } 674 675 /** 676 * Gets all the available IDs supported. 677 * @return an array of IDs. 678 */ getAvailableIDs()679 public static synchronized String[] getAvailableIDs() { 680 return ZoneInfoDb.getInstance().getAvailableIDs(); 681 } 682 683 /** 684 * Gets the platform defined TimeZone ID. 685 **/ getSystemTimeZoneID(String javaHome, String country)686 private static native String getSystemTimeZoneID(String javaHome, 687 String country); 688 689 /** 690 * Gets the custom time zone ID based on the GMT offset of the 691 * platform. (e.g., "GMT+08:00") 692 */ getSystemGMTOffsetID()693 private static native String getSystemGMTOffsetID(); 694 695 /** 696 * Gets the default <code>TimeZone</code> for this host. 697 * The source of the default <code>TimeZone</code> 698 * may vary with implementation. 699 * @return a default <code>TimeZone</code>. 700 * @see #setDefault 701 */ getDefault()702 public static TimeZone getDefault() { 703 return (TimeZone) getDefaultRef().clone(); 704 } 705 706 /** 707 * Returns the reference to the default TimeZone object. This 708 * method doesn't create a clone. 709 */ getDefaultRef()710 static synchronized TimeZone getDefaultRef() { 711 if (defaultTimeZone == null) { 712 Supplier<String> tzGetter = RuntimeHooks.getTimeZoneIdSupplier(); 713 String zoneName = (tzGetter != null) ? tzGetter.get() : null; 714 if (zoneName != null) { 715 zoneName = zoneName.trim(); 716 } 717 if (zoneName == null || zoneName.isEmpty()) { 718 try { 719 // On the host, we can find the configured timezone here. 720 zoneName = IoUtils.readFileAsString("/etc/timezone"); 721 } catch (IOException ex) { 722 // "vogar --mode device" can end up here. 723 // TODO: give libcore access to Android system properties and read "persist.sys.timezone". 724 zoneName = "GMT"; 725 } 726 } 727 defaultTimeZone = TimeZone.getTimeZone(zoneName); 728 } 729 return defaultTimeZone; 730 } 731 732 /** 733 * Sets the {@code TimeZone} that is returned by the {@code getDefault} 734 * method. {@code timeZone} is cached. If {@code timeZone} is null, the cached 735 * default {@code TimeZone} is cleared. This method doesn't change the value 736 * of the {@code user.timezone} property. 737 * 738 * @param timeZone the new default {@code TimeZone}, or null 739 * @see #getDefault 740 */ 741 // Android-changed: s/zone/timeZone, synchronized, removed mention of SecurityException setDefault(TimeZone timeZone)742 public synchronized static void setDefault(TimeZone timeZone) 743 { 744 @SuppressWarnings("removal") 745 SecurityManager sm = System.getSecurityManager(); 746 if (sm != null) { 747 sm.checkPermission(new PropertyPermission 748 ("user.timezone", "write")); 749 } 750 defaultTimeZone = timeZone != null ? (TimeZone) timeZone.clone() : null; 751 // Android-changed: notify ICU4J of changed default TimeZone. 752 ExtendedTimeZone.clearDefaultTimeZone(); 753 } 754 755 /** 756 * Returns true if this zone has the same rule and offset as another zone. 757 * That is, if this zone differs only in ID, if at all. Returns false 758 * if the other zone is null. 759 * @param other the {@code TimeZone} object to be compared with 760 * @return true if the other zone is not null and is the same as this one, 761 * with the possible exception of the ID 762 * @since 1.2 763 */ hasSameRules(TimeZone other)764 public boolean hasSameRules(TimeZone other) { 765 return other != null && getRawOffset() == other.getRawOffset() && 766 useDaylightTime() == other.useDaylightTime(); 767 } 768 769 /** 770 * Creates a copy of this {@code TimeZone}. 771 * 772 * @return a clone of this {@code TimeZone} 773 */ clone()774 public Object clone() 775 { 776 try { 777 return super.clone(); 778 } catch (CloneNotSupportedException e) { 779 throw new InternalError(e); 780 } 781 } 782 783 /** 784 * The null constant as a TimeZone. 785 */ 786 static final TimeZone NO_TIMEZONE = null; 787 788 // =======================privates=============================== 789 790 /** 791 * The string identifier of this {@code TimeZone}. This is a 792 * programmatic identifier used internally to look up {@code TimeZone} 793 * objects from the system table and also to map them to their localized 794 * display names. {@code ID} values are unique in the system 795 * table but may not be for dynamically created zones. 796 * @serial 797 */ 798 private String ID; 799 private static volatile TimeZone defaultTimeZone; 800 } 801