TimeZone.java revision 1c422fc0ab0692e10a05af6f48c6276c4dad4bea
1/* 2 * Licensed to the Apache Software Foundation (ASF) under one or more 3 * contributor license agreements. See the NOTICE file distributed with 4 * this work for additional information regarding copyright ownership. 5 * The ASF licenses this file to You under the Apache License, Version 2.0 6 * (the "License"); you may not use this file except in compliance with 7 * the License. You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 */ 17 18package java.util; 19 20import java.io.Serializable; 21 22// BEGIN android-added 23import org.apache.harmony.luni.internal.util.ZoneInfo; 24import org.apache.harmony.luni.internal.util.ZoneInfoDB; 25import com.ibm.icu4jni.util.Resources; 26// END android-added 27 28/** 29 * {@code TimeZone} represents a time zone offset, taking into account 30 * daylight savings. 31 * <p> 32 * Typically, you get a {@code TimeZone} using {@code getDefault} 33 * which creates a {@code TimeZone} based on the time zone where the 34 * program is running. For example, for a program running in Japan, 35 * {@code getDefault} creates a {@code TimeZone} object based on 36 * Japanese Standard Time. 37 * <p> 38 * You can also get a {@code TimeZone} using {@code getTimeZone} 39 * along with a time zone ID. For instance, the time zone ID for the U.S. 40 * Pacific Time zone is "America/Los_Angeles". So, you can get a U.S. Pacific 41 * Time {@code TimeZone} object with the following: <blockquote> 42 * 43 * <pre> 44 * TimeZone tz = TimeZone.getTimeZone("America/Los_Angeles"); 45 * </pre> 46 * 47 * </blockquote> You can use the {@code getAvailableIDs} method to iterate 48 * through all the supported time zone IDs. You can then choose a supported ID 49 * to get a {@code TimeZone}. If the time zone you want is not 50 * represented by one of the supported IDs, then you can create a custom time 51 * zone ID with the following syntax: <blockquote> 52 * 53 * <pre> 54 * GMT[+|-]hh[[:]mm] 55 * </pre> 56 * 57 * </blockquote> For example, you might specify GMT+14:00 as a custom time zone 58 * ID. The {@code TimeZone} that is returned when you specify a custom 59 * time zone ID does not include daylight savings time. 60 * <p> 61 * For compatibility with JDK 1.1.x, some other three-letter time zone IDs (such 62 * as "PST", "CTT", "AST") are also supported. However, <strong>their use is 63 * deprecated</strong> because the same abbreviation is often used for multiple 64 * time zones (for example, "CST" could be U.S. "Central Standard Time" and 65 * "China Standard Time"), and the Java platform can then only recognize one of 66 * them. 67 * <p> 68 * Please note the type returned by factory methods, i.e. {@code getDefault()} 69 * and {@code getTimeZone(String)}, is implementation dependent, so it may 70 * introduce serialization incompatibility issues between different 71 * implementations. Android returns instances of {@link SimpleTimeZone} so that 72 * the bytes serialized by Android can be deserialized successfully on other 73 * implementations, but the reverse compatibility cannot be guaranteed. 74 * 75 * @see GregorianCalendar 76 * @see SimpleTimeZone 77 */ 78public abstract class TimeZone implements Serializable, Cloneable { 79 private static final long serialVersionUID = 3581463369166924961L; 80 81 /** 82 * The SHORT display name style. 83 */ 84 public static final int SHORT = 0; 85 86 /** 87 * The LONG display name style. 88 */ 89 public static final int LONG = 1; 90 91 // BEGIN android-removed 92 // private static HashMap<String, TimeZone> AvailableZones; 93 // END android-removed 94 95 private static TimeZone Default; 96 97 static TimeZone GMT = new SimpleTimeZone(0, "GMT"); // Greenwich Mean Time 98 99 private String ID; 100 101 // BEGIN android-removed 102 // private com.ibm.icu.util.TimeZone icuTimeZone = null; 103 // 104 // private static void initializeAvailable() { 105 // TimeZone[] zones = TimeZones.getTimeZones(); 106 // AvailableZones = new HashMap<String, TimeZone>( 107 // (zones.length + 1) * 4 / 3); 108 // AvailableZones.put(GMT.getID(), GMT); 109 // for (int i = 0; i < zones.length; i++) { 110 // AvailableZones.put(zones[i].getID(), zones[i]); 111 // } 112 //} 113 // 114 // private static boolean isAvailableIDInICU(String name) { 115 // String[] availableIDs = com.ibm.icu.util.TimeZone.getAvailableIDs(); 116 // for (int i = 0; i < availableIDs.length; i++) { 117 // if (availableIDs[i].equals(name)) { 118 // return true; 119 // } 120 // } 121 // return false; 122 // } 123 // 124 // private static void appendAvailableZones(String name) { 125 // com.ibm.icu.util.TimeZone icuTZ = com.ibm.icu.util.TimeZone 126 // .getTimeZone(name); 127 // int raw = icuTZ.getRawOffset(); 128 // TimeZone zone = new SimpleTimeZone(raw, name); 129 // AvailableZones.put(name, zone); 130 // } 131 // END android-removed 132 133 /** 134 * Constructs a new instance of this class. 135 */ 136 public TimeZone() { 137 } 138 139 /** 140 * Returns a new {@code TimeZone} with the same ID, {@code rawOffset} and daylight savings 141 * time rules as this {@code TimeZone}. 142 * 143 * @return a shallow copy of this {@code TimeZone}. 144 * @see java.lang.Cloneable 145 */ 146 @Override 147 public Object clone() { 148 try { 149 TimeZone zone = (TimeZone) super.clone(); 150 return zone; 151 } catch (CloneNotSupportedException e) { 152 throw new AssertionError(e); // android-changed 153 } 154 } 155 156 /** 157 * Gets the available time zone IDs. Any one of these IDs can be passed to 158 * {@code get()} to create the corresponding {@code TimeZone} instance. 159 * 160 * @return an array of time zone ID strings. 161 */ 162 public static synchronized String[] getAvailableIDs() { 163 // BEGIN android-changed 164 // return com.ibm.icu.util.TimeZone.getAvailableIDs(); 165 return ZoneInfoDB.getAvailableIDs(); 166 // END android-changed 167 } 168 169 /** 170 * Gets the available time zone IDs which match the specified offset from 171 * GMT. Any one of these IDs can be passed to {@code get()} to create the corresponding 172 * {@code TimeZone} instance. 173 * 174 * @param offset 175 * the offset from GMT in milliseconds. 176 * @return an array of time zone ID strings. 177 */ 178 public static synchronized String[] getAvailableIDs(int offset) { 179 // BEGIN android-changed 180 // String[] availableIDs = com.ibm.icu.util.TimeZone.getAvailableIDs(); 181 // int count = 0; 182 // int length = availableIDs.length; 183 // String[] all = new String[length]; 184 // for (int i = 0; i < length; i++) { 185 // com.ibm.icu.util.TimeZone tz = com.ibm.icu.util.TimeZone 186 // .getTimeZone(availableIDs[i]); 187 // if (tz.getRawOffset() == offset) { 188 // all[count++] = tz.getID(); 189 // } 190 // } 191 // String[] answer = new String[count]; 192 // System.arraycopy(all, 0, answer, 0, count); 193 // return answer; 194 return ZoneInfoDB.getAvailableIDs(offset); 195 // END android-changed 196 } 197 198 /** 199 * Gets the default time zone. 200 * 201 * @return the default time zone. 202 */ 203 public static synchronized TimeZone getDefault() { 204 if (Default == null) { 205 // BEGIN android-changed 206 // setDefault(null); 207 Default = ZoneInfoDB.getDefault(); 208 // END android-changed 209 } 210 return (TimeZone) Default.clone(); 211 } 212 213 /** 214 * Gets the LONG name for this {@code TimeZone} for the default {@code Locale} in standard 215 * time. If the name is not available, the result is in the format 216 * {@code GMT[+-]hh:mm}. 217 * 218 * @return the {@code TimeZone} name. 219 */ 220 public final String getDisplayName() { 221 return getDisplayName(false, LONG, Locale.getDefault()); 222 } 223 224 /** 225 * Gets the LONG name for this {@code TimeZone} for the specified {@code Locale} in standard 226 * time. If the name is not available, the result is in the format 227 * {@code GMT[+-]hh:mm}. 228 * 229 * @param locale 230 * the {@code Locale}. 231 * @return the {@code TimeZone} name. 232 */ 233 public final String getDisplayName(Locale locale) { 234 return getDisplayName(false, LONG, locale); 235 } 236 237 /** 238 * Gets the specified style of name ({@code LONG} or {@code SHORT}) for this {@code TimeZone} for 239 * the default {@code Locale} in either standard or daylight time as specified. If 240 * the name is not available, the result is in the format {@code GMT[+-]hh:mm}. 241 * 242 * @param daylightTime 243 * {@code true} for daylight time, {@code false} for standard 244 * time. 245 * @param style 246 * either {@code LONG} or {@code SHORT}. 247 * @return the {@code TimeZone} name. 248 */ 249 public final String getDisplayName(boolean daylightTime, int style) { 250 return getDisplayName(daylightTime, style, Locale.getDefault()); 251 } 252 253 /** 254 * Gets the specified style of name ({@code LONG} or {@code SHORT}) for this {@code TimeZone} for 255 * the specified {@code Locale} in either standard or daylight time as specified. If 256 * the name is not available, the result is in the format {@code GMT[+-]hh:mm}. 257 * 258 * @param daylightTime 259 * {@code true} for daylight time, {@code false} for standard 260 * time. 261 * @param style 262 * either LONG or SHORT. 263 * @param locale 264 * either {@code LONG} or {@code SHORT}. 265 * @return the {@code TimeZone} name. 266 */ 267 public String getDisplayName(boolean daylightTime, int style, Locale locale) { 268 // BEGIN android-changed 269 // if(icuTimeZone == null || !ID.equals(icuTimeZone.getID())){ 270 // icuTimeZone = com.ibm.icu.util.TimeZone.getTimeZone(ID); 271 // } 272 // return icuTimeZone.getDisplayName( 273 // daylightTime, style, locale); 274 if (style == SHORT || style == LONG) { 275 boolean useDaylight = daylightTime && useDaylightTime(); 276 277 String result = Resources.getDisplayTimeZone(getID(), daylightTime, style, locale.toString()); 278 if (result != null) { 279 return result; 280 } 281 282 int offset = getRawOffset(); 283 if (useDaylight && this instanceof SimpleTimeZone) { 284 offset += ((SimpleTimeZone) this).getDSTSavings(); 285 } 286 offset /= 60000; 287 char sign = '+'; 288 if (offset < 0) { 289 sign = '-'; 290 offset = -offset; 291 } 292 StringBuffer buffer = new StringBuffer(9); 293 buffer.append("GMT"); 294 buffer.append(sign); 295 appendNumber(buffer, 2, offset / 60); 296 buffer.append(':'); 297 appendNumber(buffer, 2, offset % 60); 298 return buffer.toString(); 299 } 300 throw new IllegalArgumentException(); 301 // END android-changed 302 } 303 304 // BEGIN android-added 305 private void appendNumber(StringBuffer buffer, int count, int value) { 306 String string = Integer.toString(value); 307 if (count > string.length()) { 308 for (int i = 0; i < count - string.length(); i++) { 309 buffer.append('0'); 310 } 311 } 312 buffer.append(string); 313 } 314 // END android-added 315 316 /** 317 * Gets the ID of this {@code TimeZone}. 318 * 319 * @return the time zone ID string. 320 */ 321 public String getID() { 322 return ID; 323 } 324 325 /** 326 * Gets the daylight savings offset in milliseconds for this {@code TimeZone}. 327 * <p> 328 * This implementation returns 3600000 (1 hour), or 0 if the time zone does 329 * not observe daylight savings. 330 * <p> 331 * Subclasses may override to return daylight savings values other than 1 332 * hour. 333 * <p> 334 * 335 * @return the daylight savings offset in milliseconds if this {@code TimeZone} 336 * observes daylight savings, zero otherwise. 337 */ 338 public int getDSTSavings() { 339 if (useDaylightTime()) { 340 return 3600000; 341 } 342 return 0; 343 } 344 345 /** 346 * Gets the offset from GMT of this {@code TimeZone} for the specified date. The 347 * offset includes daylight savings time if the specified date is within the 348 * daylight savings time period. 349 * 350 * @param time 351 * the date in milliseconds since January 1, 1970 00:00:00 GMT 352 * @return the offset from GMT in milliseconds. 353 */ 354 public int getOffset(long time) { 355 if (inDaylightTime(new Date(time))) { 356 return getRawOffset() + getDSTSavings(); 357 } 358 return getRawOffset(); 359 } 360 361 /** 362 * Gets the offset from GMT of this {@code TimeZone} for the specified date and 363 * time. The offset includes daylight savings time if the specified date and 364 * time are within the daylight savings time period. 365 * 366 * @param era 367 * the {@code GregorianCalendar} era, either {@code GregorianCalendar.BC} or 368 * {@code GregorianCalendar.AD}. 369 * @param year 370 * the year. 371 * @param month 372 * the {@code Calendar} month. 373 * @param day 374 * the day of the month. 375 * @param dayOfWeek 376 * the {@code Calendar} day of the week. 377 * @param time 378 * the time of day in milliseconds. 379 * @return the offset from GMT in milliseconds. 380 */ 381 abstract public int getOffset(int era, int year, int month, int day, 382 int dayOfWeek, int time); 383 384 /** 385 * Gets the offset for standard time from GMT for this {@code TimeZone}. 386 * 387 * @return the offset from GMT in milliseconds. 388 */ 389 abstract public int getRawOffset(); 390 391 /** 392 * Gets the {@code TimeZone} with the specified ID. 393 * 394 * @param name 395 * a time zone string ID. 396 * @return the {@code TimeZone} with the specified ID or null if no {@code TimeZone} with 397 * the specified ID exists. 398 */ 399 public static synchronized TimeZone getTimeZone(String name) { 400 // BEGIN android-changed 401 // if (AvailableZones == null) { 402 // initializeAvailable(); 403 // } 404 // 405 // TimeZone zone = AvailableZones.get(name); 406 // if(zone == null && isAvailableIDInICU(name)){ 407 // appendAvailableZones(name); 408 // zone = AvailableZones.get(name); 409 // } 410 TimeZone zone = ZoneInfo.getTimeZone(name); 411 // END android-changed 412 if (zone == null) { 413 if (name.startsWith("GMT") && name.length() > 3) { 414 char sign = name.charAt(3); 415 if (sign == '+' || sign == '-') { 416 int[] position = new int[1]; 417 String formattedName = formatTimeZoneName(name, 4); 418 int hour = parseNumber(formattedName, 4, position); 419 if (hour < 0 || hour > 23) { 420 return (TimeZone) GMT.clone(); 421 } 422 int index = position[0]; 423 if (index != -1) { 424 int raw = hour * 3600000; 425 if (index < formattedName.length() 426 && formattedName.charAt(index) == ':') { 427 int minute = parseNumber(formattedName, index + 1, 428 position); 429 if (position[0] == -1 || minute < 0 || minute > 59) { 430 return (TimeZone) GMT.clone(); 431 } 432 raw += minute * 60000; 433 } else if (hour >= 30 || index > 6) { 434 raw = (hour / 100 * 3600000) + (hour % 100 * 60000); 435 } 436 if (sign == '-') { 437 raw = -raw; 438 } 439 return new SimpleTimeZone(raw, formattedName); 440 } 441 } 442 } 443 zone = GMT; 444 } 445 return (TimeZone) zone.clone(); 446 } 447 448 private static String formatTimeZoneName(String name, int offset) { 449 StringBuilder buf = new StringBuilder(); 450 int index = offset, length = name.length(); 451 buf.append(name.substring(0, offset)); 452 453 while (index < length) { 454 if (Character.digit(name.charAt(index), 10) != -1) { 455 buf.append(name.charAt(index)); 456 if ((length - (index + 1)) == 2) { 457 buf.append(':'); 458 } 459 } else if (name.charAt(index) == ':') { 460 buf.append(':'); 461 } 462 index++; 463 } 464 465 if (buf.toString().indexOf(":") == -1) { 466 buf.append(':'); 467 buf.append("00"); 468 } 469 470 if (buf.toString().indexOf(":") == 5) { 471 buf.insert(4, '0'); 472 } 473 474 return buf.toString(); 475 } 476 477 /** 478 * Returns whether the specified {@code TimeZone} has the same raw offset as this 479 * {@code TimeZone}. 480 * 481 * @param zone 482 * a {@code TimeZone}. 483 * @return {@code true} when the {@code TimeZone} have the same raw offset, {@code false} 484 * otherwise. 485 */ 486 public boolean hasSameRules(TimeZone zone) { 487 if (zone == null) { 488 return false; 489 } 490 return getRawOffset() == zone.getRawOffset(); 491 } 492 493 /** 494 * Returns whether the specified {@code Date} is in the daylight savings time period for 495 * this {@code TimeZone}. 496 * 497 * @param time 498 * a {@code Date}. 499 * @return {@code true} when the {@code Date} is in the daylight savings time period, {@code false} 500 * otherwise. 501 */ 502 abstract public boolean inDaylightTime(Date time); 503 504 private static int parseNumber(String string, int offset, int[] position) { 505 int index = offset, length = string.length(), digit, result = 0; 506 while (index < length 507 && (digit = Character.digit(string.charAt(index), 10)) != -1) { 508 index++; 509 result = result * 10 + digit; 510 } 511 position[0] = index == offset ? -1 : index; 512 return result; 513 } 514 515 /** 516 * Sets the default time zone. If passed {@code null}, then the next 517 * time {@link #getDefault} is called, the default time zone will be 518 * determined. This behavior is slightly different than the canonical 519 * description of this method, but it follows the spirit of it. 520 * 521 * @param timezone 522 * a {@code TimeZone} object. 523 */ 524 public static synchronized void setDefault(TimeZone timezone) { 525 // BEGIN android-removed 526 // if (timezone != null) { 527 // setICUDefaultTimeZone(timezone); 528 // Default = timezone; 529 // return; 530 // } 531 // 532 // String zone = AccessController.doPrivileged(new PriviAction<String>( 533 // "user.timezone")); 534 // 535 // // sometimes DRLVM incorrectly adds "\n" to the end of timezone ID 536 // if (zone != null && zone.contains("\n")) { 537 // zone = zone.substring(0, zone.indexOf("\n")); 538 // } 539 // 540 // // if property user.timezone is not set, we call the native method 541 // // getCustomTimeZone 542 // if (zone == null || zone.length() == 0) { 543 // int[] tzinfo = new int[10]; 544 // boolean[] isCustomTimeZone = new boolean[1]; 545 // 546 // String zoneId = getCustomTimeZone(tzinfo, isCustomTimeZone); 547 // 548 // // if returned TimeZone is a user customized TimeZone 549 // if (isCustomTimeZone[0]) { 550 // // build a new SimpleTimeZone 551 // switch (tzinfo[1]) { 552 // case 0: 553 // // does not observe DST 554 // Default = new SimpleTimeZone(tzinfo[0], zoneId); 555 // break; 556 // default: 557 // // observes DST 558 // Default = new SimpleTimeZone(tzinfo[0], zoneId, tzinfo[5], 559 // tzinfo[4], tzinfo[3], tzinfo[2], tzinfo[9], 560 // tzinfo[8], tzinfo[7], tzinfo[6], tzinfo[1]); 561 // } 562 // } else { 563 // // get TimeZone 564 // Default = getTimeZone(zoneId); 565 // } 566 // } else { 567 // // if property user.timezone is set in command line (with -D option) 568 // Default = getTimeZone(zone); 569 // } 570 // setICUDefaultTimeZone(Default); 571 // END android-removed 572 573 // BEGIN android-added 574 Default = timezone; 575 576 // TODO Not sure if this is spec-compliant. Shouldn't be persistent. 577 ZoneInfoDB.setDefault(timezone); 578 // END android-added 579 } 580 581 // BEGIN android-removed 582 // private static void setICUDefaultTimeZone(TimeZone timezone) { 583 // final com.ibm.icu.util.TimeZone icuTZ = com.ibm.icu.util.TimeZone 584 // .getTimeZone(timezone.getID()); 585 // 586 // AccessController 587 // .doPrivileged(new PrivilegedAction<java.lang.reflect.Field>() { 588 // public java.lang.reflect.Field run() { 589 // java.lang.reflect.Field field = null; 590 // try { 591 // field = com.ibm.icu.util.TimeZone.class 592 // .getDeclaredField("defaultZone"); 593 // field.setAccessible(true); 594 // field.set("defaultZone", icuTZ); 595 // } catch (Exception e) { 596 // return null; 597 // } 598 // return field; 599 // } 600 // }); 601 // } 602 // END android-removed 603 604 /** 605 * Sets the ID of this {@code TimeZone}. 606 * 607 * @param name 608 * a string which is the time zone ID. 609 */ 610 public void setID(String name) { 611 if (name == null) { 612 throw new NullPointerException(); 613 } 614 ID = name; 615 } 616 617 /** 618 * Sets the offset for standard time from GMT for this {@code TimeZone}. 619 * 620 * @param offset 621 * the offset from GMT in milliseconds. 622 */ 623 abstract public void setRawOffset(int offset); 624 625 /** 626 * Returns whether this {@code TimeZone} has a daylight savings time period. 627 * 628 * @return {@code true} if this {@code TimeZone} has a daylight savings time period, {@code false} 629 * otherwise. 630 */ 631 abstract public boolean useDaylightTime(); 632 633 /** 634 * Gets the name and the details of the user-selected TimeZone on the 635 * device. 636 * 637 * @param tzinfo 638 * int array of 10 elements to be filled with the TimeZone 639 * information. Once filled, the contents of the array are 640 * formatted as follows: tzinfo[0] -> the timezone offset; 641 * tzinfo[1] -> the dst adjustment; tzinfo[2] -> the dst start 642 * hour; tzinfo[3] -> the dst start day of week; tzinfo[4] -> the 643 * dst start week of month; tzinfo[5] -> the dst start month; 644 * tzinfo[6] -> the dst end hour; tzinfo[7] -> the dst end day of 645 * week; tzinfo[8] -> the dst end week of month; tzinfo[9] -> the 646 * dst end month; 647 * @param isCustomTimeZone 648 * boolean array of size 1 that indicates if a timezone match is 649 * found 650 * @return the name of the TimeZone or null if error occurs in native 651 * method. 652 */ 653 private static native String getCustomTimeZone(int[] tzinfo, 654 boolean[] isCustomTimeZone); 655} 656