1 /* 2 * Copyright (c) 2000, 2012, 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 import java.io.IOException; 27 import java.io.ObjectInputStream; 28 import java.lang.ref.SoftReference; 29 import java.time.ZoneOffset; 30 import java.time.LocalDateTime; 31 import java.util.Arrays; 32 import java.util.ArrayList; 33 import java.util.Date; 34 import java.util.List; 35 import java.util.Locale; 36 import java.util.Map; 37 import java.util.SimpleTimeZone; 38 import java.util.TimeZone; 39 40 import sun.util.calendar.CalendarSystem; 41 import sun.util.calendar.CalendarDate; 42 43 /** 44 * <code>ZoneInfoOld</code> is an implementation subclass of {@link 45 * java.util.TimeZone TimeZone} that represents GMT offsets and 46 * daylight saving time transitions of a time zone. 47 * <p> 48 * The daylight saving time transitions are described in the {@link 49 * #transitions transitions} table consisting of a chronological 50 * sequence of transitions of GMT offset and/or daylight saving time 51 * changes. Since all transitions are represented in UTC, in theory, 52 * <code>ZoneInfoOld</code> can be used with any calendar systems except 53 * for the {@link #getOffset(int,int,int,int,int,int) getOffset} 54 * method that takes Gregorian calendar date fields. 55 * <p> 56 * This table covers transitions from 1900 until 2037 (as of version 57 * 1.4), Before 1900, it assumes that there was no daylight saving 58 * time and the <code>getOffset</code> methods always return the 59 * {@link #getRawOffset} value. No Local Mean Time is supported. If a 60 * specified date is beyond the transition table and this time zone is 61 * supposed to observe daylight saving time in 2037, it delegates 62 * operations to a {@link java.util.SimpleTimeZone SimpleTimeZone} 63 * object created using the daylight saving time schedule as of 2037. 64 * <p> 65 * The date items, transitions, GMT offset(s), etc. are read from a database 66 * file. See {@link ZoneInfoFile} for details. 67 * @see java.util.SimpleTimeZone 68 * @since 1.4 69 */ 70 71 public class ZoneInfoOld extends TimeZone { 72 73 // The constants assume no leap seconds support. 74 static final int SECOND_IN_MILLIS = 1000; 75 static final int MINUTE_IN_MILLIS = SECOND_IN_MILLIS * 60; 76 static final int HOUR_IN_MILLIS = MINUTE_IN_MILLIS * 60; 77 static final int DAY_IN_MILLIS = HOUR_IN_MILLIS * 24; 78 79 private static final int UTC_TIME = 0; 80 private static final int STANDARD_TIME = 1; 81 private static final int WALL_TIME = 2; 82 83 private static final long OFFSET_MASK = 0x0fL; 84 private static final long DST_MASK = 0xf0L; 85 private static final int DST_NSHIFT = 4; 86 // this bit field is reserved for abbreviation support 87 private static final long ABBR_MASK = 0xf00L; 88 private static final int TRANSITION_NSHIFT = 12; 89 90 // Flag for supporting JDK backward compatible IDs, such as "EST". 91 static final boolean USE_OLDMAPPING; 92 static { 93 String oldmapping = System.getProperty("sun.timezone.ids.oldmapping", "false").toLowerCase(Locale.ROOT); 94 USE_OLDMAPPING = (oldmapping.equals("yes") || oldmapping.equals("true")); 95 } 96 97 // IDs having conflicting data between Olson and JDK 1.1 98 static final String[] conflictingIDs = { 99 "EST", "MST", "HST" 100 }; 101 102 private static final CalendarSystem gcal = CalendarSystem.getGregorianCalendar(); 103 104 /** 105 * The raw GMT offset in milliseconds between this zone and GMT. 106 * Negative offsets are to the west of Greenwich. To obtain local 107 * <em>standard</em> time, add the offset to GMT time. 108 * @serial 109 */ 110 int rawOffset; 111 112 /** 113 * Difference in milliseconds from the original GMT offset in case 114 * the raw offset value has been modified by calling {@link 115 * #setRawOffset}. The initial value is 0. 116 * @serial 117 */ 118 int rawOffsetDiff = 0; 119 120 /** 121 * A CRC32 value of all pairs of transition time (in milliseconds 122 * in <code>long</code>) in local time and its GMT offset (in 123 * seconds in <code>int</code>) in the chronological order. Byte 124 * values of each <code>long</code> and <code>int</code> are taken 125 * in the big endian order (i.e., MSB to LSB). 126 * @serial 127 */ 128 int checksum; 129 130 /** 131 * The amount of time in milliseconds saved during daylight saving 132 * time. If <code>useDaylight</code> is false, this value is 0. 133 * @serial 134 */ 135 int dstSavings; 136 137 /** 138 * This array describes transitions of GMT offsets of this time 139 * zone, including both raw offset changes and daylight saving 140 * time changes. 141 * A long integer consists of four bit fields. 142 * <ul> 143 * <li>The most significant 52-bit field represents transition 144 * time in milliseconds from Gregorian January 1 1970, 00:00:00 145 * GMT.</li> 146 * <li>The next 4-bit field is reserved and must be 0.</li> 147 * <li>The next 4-bit field is an index value to {@link #offsets 148 * offsets[]} for the amount of daylight saving at the 149 * transition. If this value is zero, it means that no daylight 150 * saving, not the index value zero.</li> 151 * <li>The least significant 4-bit field is an index value to 152 * {@link #offsets offsets[]} for <em>total</em> GMT offset at the 153 * transition.</li> 154 * </ul> 155 * If this time zone doesn't observe daylight saving time and has 156 * never changed any GMT offsets in the past, this value is null. 157 * @serial 158 */ 159 long[] transitions; 160 161 /** 162 * This array holds all unique offset values in 163 * milliseconds. Index values to this array are stored in the 164 * transitions array elements. 165 * @serial 166 */ 167 int[] offsets; 168 169 /** 170 * SimpleTimeZone parameter values. It has to have either 8 for 171 * {@link java.util.SimpleTimeZone#SimpleTimeZone(int, String, 172 * int, int , int , int , int , int , int , int , int) the 173 * 11-argument SimpleTimeZone constructor} or 10 for {@link 174 * java.util.SimpleTimeZone#SimpleTimeZone(int, String, int, int, 175 * int , int , int , int , int , int , int, int, int) the 176 * 13-argument SimpleTimeZone constructor} parameters. 177 * @serial 178 */ 179 int[] simpleTimeZoneParams; 180 181 /** 182 * True if the raw GMT offset value would change after the time 183 * zone data has been generated; false, otherwise. The default 184 * value is false. 185 * @serial 186 */ 187 boolean willGMTOffsetChange = false; 188 189 /** 190 * True if the object has been modified after its instantiation. 191 */ 192 transient private boolean dirty = false; 193 194 private static final long serialVersionUID = 2653134537216586139L; 195 196 /** 197 * A constructor. 198 */ 199 public ZoneInfoOld() { 200 } 201 202 /** 203 * A Constructor for CustomID. 204 */ 205 public ZoneInfoOld(String ID, int rawOffset) { 206 this(ID, rawOffset, 0, 0, null, null, null, false); 207 } 208 209 /** 210 * Constructs a ZoneInfoOld instance. 211 * 212 * @param ID time zone name 213 * @param rawOffset GMT offset in milliseconds 214 * @param dstSavings daylight saving value in milliseconds or 0 215 * (zero) if this time zone doesn't observe Daylight Saving Time. 216 * @param checksum CRC32 value with all transitions table entry 217 * values 218 * @param transitions transition table 219 * @param offsets offset value table 220 * @param simpleTimeZoneParams parameter values for constructing 221 * SimpleTimeZone 222 * @param willGMTOffsetChange the value of willGMTOffsetChange 223 */ 224 ZoneInfoOld(String ID, 225 int rawOffset, 226 int dstSavings, 227 int checksum, 228 long[] transitions, 229 int[] offsets, 230 int[] simpleTimeZoneParams, 231 boolean willGMTOffsetChange) { 232 setID(ID); 233 this.rawOffset = rawOffset; 234 this.dstSavings = dstSavings; 235 this.checksum = checksum; 236 this.transitions = transitions; 237 this.offsets = offsets; 238 this.simpleTimeZoneParams = simpleTimeZoneParams; 239 this.willGMTOffsetChange = willGMTOffsetChange; 240 } 241 242 /** 243 * Returns the difference in milliseconds between local time and UTC 244 * of given time, taking into account both the raw offset and the 245 * effect of daylight savings. 246 * 247 * @param date the milliseconds in UTC 248 * @return the milliseconds to add to UTC to get local wall time 249 */ 250 public int getOffset(long date) { 251 return getOffsets(date, null, UTC_TIME); 252 } 253 254 public int getOffsets(long utc, int[] offsets) { 255 return getOffsets(utc, offsets, UTC_TIME); 256 } 257 258 public int getOffsetsByStandard(long standard, int[] offsets) { 259 return getOffsets(standard, offsets, STANDARD_TIME); 260 } 261 262 public int getOffsetsByWall(long wall, int[] offsets) { 263 return getOffsets(wall, offsets, WALL_TIME); 264 } 265 266 private int getOffsets(long date, int[] offsets, int type) { 267 // if dst is never observed, there is no transition. 268 if (transitions == null) { 269 int offset = getLastRawOffset(); 270 if (offsets != null) { 271 offsets[0] = offset; 272 offsets[1] = 0; 273 } 274 return offset; 275 } 276 277 date -= rawOffsetDiff; 278 int index = getTransitionIndex(date, type); 279 280 // prior to the transition table, returns the raw offset. 281 // FIXME: should support LMT. 282 if (index < 0) { 283 int offset = getLastRawOffset(); 284 if (offsets != null) { 285 offsets[0] = offset; 286 offsets[1] = 0; 287 } 288 return offset; 289 } 290 291 if (index < transitions.length) { 292 long val = transitions[index]; 293 int offset = this.offsets[(int)(val & OFFSET_MASK)] + rawOffsetDiff; 294 if (offsets != null) { 295 int dst = (int)((val >>> DST_NSHIFT) & 0xfL); 296 int save = (dst == 0) ? 0 : this.offsets[dst]; 297 offsets[0] = offset - save; 298 offsets[1] = save; 299 } 300 return offset; 301 } 302 303 // beyond the transitions, delegate to SimpleTimeZone if there 304 // is a rule; otherwise, return rawOffset. 305 SimpleTimeZone tz = getLastRule(); 306 if (tz != null) { 307 int rawoffset = tz.getRawOffset(); 308 long msec = date; 309 if (type != UTC_TIME) { 310 msec -= rawOffset; 311 } 312 int dstoffset = tz.getOffset(msec) - rawOffset; 313 314 // Check if it's in a standard-to-daylight transition. 315 if (dstoffset > 0 && tz.getOffset(msec - dstoffset) == rawoffset) { 316 dstoffset = 0; 317 } 318 319 if (offsets != null) { 320 offsets[0] = rawoffset; 321 offsets[1] = dstoffset; 322 } 323 return rawoffset + dstoffset; 324 } 325 int offset = getLastRawOffset(); 326 if (offsets != null) { 327 offsets[0] = offset; 328 offsets[1] = 0; 329 } 330 return offset; 331 } 332 333 private int getTransitionIndex(long date, int type) { 334 int low = 0; 335 int high = transitions.length - 1; 336 337 while (low <= high) { 338 int mid = (low + high) / 2; 339 long val = transitions[mid]; 340 long midVal = val >> TRANSITION_NSHIFT; // sign extended 341 if (type != UTC_TIME) { 342 midVal += offsets[(int)(val & OFFSET_MASK)]; // wall time 343 } 344 if (type == STANDARD_TIME) { 345 int dstIndex = (int)((val >>> DST_NSHIFT) & 0xfL); 346 if (dstIndex != 0) { 347 midVal -= offsets[dstIndex]; // make it standard time 348 } 349 } 350 351 if (midVal < date) { 352 low = mid + 1; 353 } else if (midVal > date) { 354 high = mid - 1; 355 } else { 356 return mid; 357 } 358 } 359 360 // if beyond the transitions, returns that index. 361 if (low >= transitions.length) { 362 return low; 363 } 364 return low - 1; 365 } 366 367 /** 368 * Returns the difference in milliseconds between local time and 369 * UTC, taking into account both the raw offset and the effect of 370 * daylight savings, for the specified date and time. This method 371 * assumes that the start and end month are distinct. This method 372 * assumes a Gregorian calendar for calculations. 373 * <p> 374 * <em>Note: In general, clients should use 375 * {@link Calendar#ZONE_OFFSET Calendar.get(ZONE_OFFSET)} + 376 * {@link Calendar#DST_OFFSET Calendar.get(DST_OFFSET)} 377 * instead of calling this method.</em> 378 * 379 * @param era The era of the given date. The value must be either 380 * GregorianCalendar.AD or GregorianCalendar.BC. 381 * @param year The year in the given date. 382 * @param month The month in the given date. Month is 0-based. e.g., 383 * 0 for January. 384 * @param day The day-in-month of the given date. 385 * @param dayOfWeek The day-of-week of the given date. 386 * @param millis The milliseconds in day in <em>standard</em> local time. 387 * @return The milliseconds to add to UTC to get local time. 388 */ 389 public int getOffset(int era, int year, int month, int day, 390 int dayOfWeek, int milliseconds) { 391 if (milliseconds < 0 || milliseconds >= DAY_IN_MILLIS) { 392 throw new IllegalArgumentException(); 393 } 394 395 if (era == java.util.GregorianCalendar.BC) { // BC 396 year = 1 - year; 397 } else if (era != java.util.GregorianCalendar.AD) { 398 throw new IllegalArgumentException(); 399 } 400 401 CalendarDate date = gcal.newCalendarDate(null); 402 date.setDate(year, month + 1, day); 403 if (gcal.validate(date) == false) { 404 throw new IllegalArgumentException(); 405 } 406 407 // bug-for-bug compatible argument checking 408 if (dayOfWeek < java.util.GregorianCalendar.SUNDAY 409 || dayOfWeek > java.util.GregorianCalendar.SATURDAY) { 410 throw new IllegalArgumentException(); 411 } 412 413 if (transitions == null) { 414 return getLastRawOffset(); 415 } 416 417 long dateInMillis = gcal.getTime(date) + milliseconds; 418 dateInMillis -= (long) rawOffset; // make it UTC 419 return getOffsets(dateInMillis, null, UTC_TIME); 420 } 421 422 /** 423 * Sets the base time zone offset from GMT. This operation 424 * modifies all the transitions of this ZoneInfoOld object, including 425 * historical ones, if applicable. 426 * 427 * @param offsetMillis the base time zone offset to GMT. 428 * @see getRawOffset 429 */ 430 public synchronized void setRawOffset(int offsetMillis) { 431 if (offsetMillis == rawOffset + rawOffsetDiff) { 432 return; 433 } 434 rawOffsetDiff = offsetMillis - rawOffset; 435 if (lastRule != null) { 436 lastRule.setRawOffset(offsetMillis); 437 } 438 dirty = true; 439 } 440 441 /** 442 * Returns the GMT offset of the current date. This GMT offset 443 * value is not modified during Daylight Saving Time. 444 * 445 * @return the GMT offset value in milliseconds to add to UTC time 446 * to get local standard time 447 */ 448 public int getRawOffset() { 449 if (!willGMTOffsetChange) { 450 return rawOffset + rawOffsetDiff; 451 } 452 453 int[] offsets = new int[2]; 454 getOffsets(System.currentTimeMillis(), offsets, UTC_TIME); 455 return offsets[0]; 456 } 457 458 public boolean isDirty() { 459 return dirty; 460 } 461 462 int getLastRawOffset() { 463 return rawOffset + rawOffsetDiff; 464 } 465 466 /** 467 * Queries if this time zone uses Daylight Saving Time in the last known rule. 468 */ 469 public boolean useDaylightTime() { 470 return (simpleTimeZoneParams != null); 471 } 472 473 @Override 474 public boolean observesDaylightTime() { 475 if (simpleTimeZoneParams != null) { 476 return true; 477 } 478 if (transitions == null) { 479 return false; 480 } 481 482 // Look up the transition table to see if it's in DST right 483 // now or if there's any standard-to-daylight transition at 484 // any future. 485 long utc = System.currentTimeMillis() - rawOffsetDiff; 486 int index = getTransitionIndex(utc, UTC_TIME); 487 488 // before transitions in the transition table 489 if (index < 0) { 490 return false; 491 } 492 493 // the time is in the table range. 494 for (int i = index; i < transitions.length; i++) { 495 if ((transitions[i] & DST_MASK) != 0) { 496 return true; 497 } 498 } 499 // No further DST is observed. 500 return false; 501 } 502 503 /** 504 * Queries if the specified date is in Daylight Saving Time. 505 */ 506 public boolean inDaylightTime(Date date) { 507 if (date == null) { 508 throw new NullPointerException(); 509 } 510 511 if (transitions == null) { 512 return false; 513 } 514 515 long utc = date.getTime() - rawOffsetDiff; 516 int index = getTransitionIndex(utc, UTC_TIME); 517 518 // before transitions in the transition table 519 if (index < 0) { 520 return false; 521 } 522 523 // the time is in the table range. 524 if (index < transitions.length) { 525 return (transitions[index] & DST_MASK) != 0; 526 } 527 528 // beyond the transition table 529 SimpleTimeZone tz = getLastRule(); 530 if (tz != null) { 531 return tz.inDaylightTime(date); 532 } 533 return false; 534 } 535 536 /** 537 * Returns the amount of time in milliseconds that the clock is advanced 538 * during daylight saving time is in effect in its last daylight saving time rule. 539 * 540 * @return the number of milliseconds the time is advanced with respect to 541 * standard time when daylight saving time is in effect. 542 */ 543 public int getDSTSavings() { 544 return dstSavings; 545 } 546 547 // /** 548 // * @return the last year in the transition table or -1 if this 549 // * time zone doesn't observe any daylight saving time. 550 // */ 551 // public int getMaxTransitionYear() { 552 // if (transitions == null) { 553 // return -1; 554 // } 555 // long val = transitions[transitions.length - 1]; 556 // int offset = this.offsets[(int)(val & OFFSET_MASK)] + rawOffsetDiff; 557 // val = (val >> TRANSITION_NSHIFT) + offset; 558 // CalendarDate lastDate = Gregorian.getCalendarDate(val); 559 // return lastDate.getYear(); 560 // } 561 562 /** 563 * Returns a string representation of this time zone. 564 * @return the string 565 */ 566 public String toString() { 567 return getClass().getName() + 568 "[id=\"" + getID() + "\"" + 569 ",offset=" + getLastRawOffset() + 570 ",dstSavings=" + dstSavings + 571 ",useDaylight=" + useDaylightTime() + 572 ",transitions=" + ((transitions != null) ? transitions.length : 0) + 573 ",lastRule=" + (lastRule == null ? getLastRuleInstance() : lastRule) + 574 "]"; 575 } 576 577 /** 578 * Gets all available IDs supported in the Java run-time. 579 * 580 * @return an array of time zone IDs. 581 */ 582 public static String[] getAvailableIDs() { 583 List<String> idList = ZoneInfoFile.getZoneIDs(); 584 List<String> excluded = ZoneInfoFile.getExcludedZones(); 585 if (excluded != null) { 586 // List all zones from the idList and excluded lists 587 List<String> list = new ArrayList<>(idList.size() + excluded.size()); 588 list.addAll(idList); 589 list.addAll(excluded); 590 idList = list; 591 } 592 String[] ids = new String[idList.size()]; 593 return idList.toArray(ids); 594 } 595 596 /** 597 * Gets all available IDs that have the same value as the 598 * specified raw GMT offset. 599 * 600 * @param rawOffset the GMT offset in milliseconds. This 601 * value should not include any daylight saving time. 602 * 603 * @return an array of time zone IDs. 604 */ 605 public static String[] getAvailableIDs(int rawOffset) { 606 String[] result; 607 List<String> matched = new ArrayList<>(); 608 List<String> IDs = ZoneInfoFile.getZoneIDs(); 609 int[] rawOffsets = ZoneInfoFile.getRawOffsets(); 610 611 loop: 612 for (int index = 0; index < rawOffsets.length; index++) { 613 if (rawOffsets[index] == rawOffset) { 614 byte[] indices = ZoneInfoFile.getRawOffsetIndices(); 615 for (int i = 0; i < indices.length; i++) { 616 if (indices[i] == index) { 617 matched.add(IDs.get(i++)); 618 while (i < indices.length && indices[i] == index) { 619 matched.add(IDs.get(i++)); 620 } 621 break loop; 622 } 623 } 624 } 625 } 626 627 // We need to add any zones from the excluded zone list that 628 // currently have the same GMT offset as the specified 629 // rawOffset. The zones returned by this method may not be 630 // correct as of return to the caller if any GMT offset 631 // transition is happening during this GMT offset checking... 632 List<String> excluded = ZoneInfoFile.getExcludedZones(); 633 if (excluded != null) { 634 for (String id : excluded) { 635 TimeZone zi = getTimeZone(id); 636 if (zi != null && zi.getRawOffset() == rawOffset) { 637 matched.add(id); 638 } 639 } 640 } 641 642 result = new String[matched.size()]; 643 matched.toArray(result); 644 return result; 645 } 646 647 /** 648 * Gets the ZoneInfoOld for the given ID. 649 * 650 * @param ID the ID for a ZoneInfoOld. See TimeZone for detail. 651 * 652 * @return the specified ZoneInfoOld object, or null if there is no 653 * time zone of the ID. 654 */ 655 public static TimeZone getTimeZone(String ID) { 656 String givenID = null; 657 658 /* 659 * If old JDK compatibility is specified, get the old alias 660 * name. 661 */ 662 if (USE_OLDMAPPING) { 663 String compatibleID = TzIDOldMapping.MAP.get(ID); 664 if (compatibleID != null) { 665 givenID = ID; 666 ID = compatibleID; 667 } 668 } 669 670 ZoneInfoOld zi = ZoneInfoFile.getZoneInfoOld(ID); 671 if (zi == null) { 672 // if we can't create an object for the ID, try aliases. 673 try { 674 Map<String, String> map = getAliasTable(); 675 String alias = ID; 676 while ((alias = map.get(alias)) != null) { 677 zi = ZoneInfoFile.getZoneInfoOld(alias); 678 if (zi != null) { 679 zi.setID(ID); 680 zi = ZoneInfoFile.addToCache(ID, zi); 681 zi = (ZoneInfoOld) zi.clone(); 682 break; 683 } 684 } 685 } catch (Exception e) { 686 // ignore exceptions 687 } 688 } 689 690 if (givenID != null && zi != null) { 691 zi.setID(givenID); 692 } 693 return zi; 694 } 695 696 private transient SimpleTimeZone lastRule; 697 698 /** 699 * Returns a SimpleTimeZone object representing the last GMT 700 * offset and DST schedule or null if this time zone doesn't 701 * observe DST. 702 */ 703 synchronized SimpleTimeZone getLastRule() { 704 if (lastRule == null) { 705 lastRule = getLastRuleInstance(); 706 } 707 return lastRule; 708 } 709 710 /** 711 * Returns a SimpleTimeZone object that represents the last 712 * known daylight saving time rules. 713 * 714 * @return a SimpleTimeZone object or null if this time zone 715 * doesn't observe DST. 716 */ 717 public SimpleTimeZone getLastRuleInstance() { 718 if (simpleTimeZoneParams == null) { 719 return null; 720 } 721 if (simpleTimeZoneParams.length == 10) { 722 return new SimpleTimeZone(getLastRawOffset(), getID(), 723 simpleTimeZoneParams[0], 724 simpleTimeZoneParams[1], 725 simpleTimeZoneParams[2], 726 simpleTimeZoneParams[3], 727 simpleTimeZoneParams[4], 728 simpleTimeZoneParams[5], 729 simpleTimeZoneParams[6], 730 simpleTimeZoneParams[7], 731 simpleTimeZoneParams[8], 732 simpleTimeZoneParams[9], 733 dstSavings); 734 } 735 return new SimpleTimeZone(getLastRawOffset(), getID(), 736 simpleTimeZoneParams[0], 737 simpleTimeZoneParams[1], 738 simpleTimeZoneParams[2], 739 simpleTimeZoneParams[3], 740 simpleTimeZoneParams[4], 741 simpleTimeZoneParams[5], 742 simpleTimeZoneParams[6], 743 simpleTimeZoneParams[7], 744 dstSavings); 745 } 746 747 /** 748 * Returns a copy of this <code>ZoneInfoOld</code>. 749 */ 750 public Object clone() { 751 ZoneInfoOld zi = (ZoneInfoOld) super.clone(); 752 zi.lastRule = null; 753 return zi; 754 } 755 756 /** 757 * Returns a hash code value calculated from the GMT offset and 758 * transitions. 759 * @return a hash code of this time zone 760 */ 761 public int hashCode() { 762 return getLastRawOffset() ^ checksum; 763 } 764 765 /** 766 * Compares the equity of two ZoneInfoOld objects. 767 * 768 * @param obj the object to be compared with 769 * @return true if given object is same as this ZoneInfoOld object, 770 * false otherwise. 771 */ 772 public boolean equals(Object obj) { 773 if (this == obj) { 774 return true; 775 } 776 if (!(obj instanceof ZoneInfoOld)) { 777 return false; 778 } 779 ZoneInfoOld that = (ZoneInfoOld) obj; 780 return (getID().equals(that.getID()) 781 && (getLastRawOffset() == that.getLastRawOffset()) 782 && (checksum == that.checksum)); 783 } 784 785 /** 786 * Returns true if this zone has the same raw GMT offset value and 787 * transition table as another zone info. If the specified 788 * TimeZone object is not a ZoneInfoOld instance, this method returns 789 * true if the specified TimeZone object has the same raw GMT 790 * offset value with no daylight saving time. 791 * 792 * @param other the ZoneInfoOld object to be compared with 793 * @return true if the given <code>TimeZone</code> has the same 794 * GMT offset and transition information; false, otherwise. 795 */ 796 public boolean hasSameRules(TimeZone other) { 797 if (this == other) { 798 return true; 799 } 800 if (other == null) { 801 return false; 802 } 803 if (!(other instanceof ZoneInfoOld)) { 804 if (getRawOffset() != other.getRawOffset()) { 805 return false; 806 } 807 // if both have the same raw offset and neither observes 808 // DST, they have the same rule. 809 if ((transitions == null) 810 && (useDaylightTime() == false) 811 && (other.useDaylightTime() == false)) { 812 return true; 813 } 814 return false; 815 } 816 if (getLastRawOffset() != ((ZoneInfoOld)other).getLastRawOffset()) { 817 return false; 818 } 819 return (checksum == ((ZoneInfoOld)other).checksum); 820 } 821 822 private static SoftReference<Map<String, String>> aliasTable; 823 824 static Map<String, String> getCachedAliasTable() { 825 Map<String, String> aliases = null; 826 827 SoftReference<Map<String, String>> cache = aliasTable; 828 if (cache != null) { 829 aliases = cache.get(); 830 } 831 return aliases; 832 } 833 834 /** 835 * Returns a Map from alias time zone IDs to their standard 836 * time zone IDs. 837 * 838 * @return the Map that holds the mappings from alias time zone IDs 839 * to their standard time zone IDs, or null if 840 * <code>ZoneInfoOldMappings</code> file is not available. 841 */ 842 public synchronized static Map<String, String> getAliasTable() { 843 Map<String, String> aliases = getCachedAliasTable(); 844 if (aliases == null) { 845 aliases = ZoneInfoFile.getZoneAliases(); 846 if (aliases != null) { 847 if (!USE_OLDMAPPING) { 848 // Remove the conflicting IDs from the alias table. 849 for (String key : conflictingIDs) { 850 aliases.remove(key); 851 } 852 } 853 aliasTable = new SoftReference<Map<String, String>>(aliases); 854 } 855 } 856 return aliases; 857 } 858 859 private void readObject(ObjectInputStream stream) 860 throws IOException, ClassNotFoundException { 861 stream.defaultReadObject(); 862 // We don't know how this object from 1.4.x or earlier has 863 // been mutated. So it should always be marked as `dirty'. 864 dirty = true; 865 } 866 867 ////////////////////////////////////////////////////////////// 868 public boolean equalsTo(ZoneInfoOld other) { 869 return (getID().equals(other.getID()) 870 && (getLastRawOffset() == other.getLastRawOffset()) 871 && (dstSavings == other.dstSavings) 872 && (willGMTOffsetChange == other.willGMTOffsetChange) 873 && (checksum == other.checksum) 874 && equalsTransOffsets(other) 875 && (Arrays.equals(simpleTimeZoneParams, other.simpleTimeZoneParams) || 876 getLastRule().equals(other.getLastRule()))); 877 } 878 879 private boolean equalsTransOffsets(ZoneInfoOld other) { 880 if (transitions == null) { 881 return (other.transitions == null && 882 Arrays.equals(offsets, other.offsets)); 883 } 884 if (other.transitions == null || 885 transitions.length != other.transitions.length) { 886 return false; 887 } 888 // if offsets and other.offsets have different order 889 // the last 4-bit in trans are different. 890 for (int i = 0; i < transitions.length; i++) { 891 long val = transitions[i]; 892 int dst = (int)((val >>> DST_NSHIFT) & 0xfL); 893 int save = (dst == 0) ? 0 : offsets[dst] / 1000; 894 int off = offsets[(int)(val & OFFSET_MASK)]/1000; 895 long second = (val >> TRANSITION_NSHIFT)/1000; 896 897 val = other.transitions[i]; 898 int dstO = (int)((val >>> DST_NSHIFT) & 0xfL); 899 int saveO = (dstO == 0) ? 0 : other.offsets[dstO] / 1000; 900 int offO = other.offsets[(int)(val & OFFSET_MASK)]/1000; 901 long secondO = (val >> TRANSITION_NSHIFT)/1000; 902 if ((dst == 0) != (dstO == 0) || save != saveO || off != offO || second != secondO) 903 return false; 904 } 905 return true; 906 } 907 908 private int transToString(long val, int off_old, int[] offsets, StringBuilder sb) { 909 int dst = (int)((val >>> DST_NSHIFT) & 0xfL); 910 int save = (dst == 0) ? 0 : offsets[dst] / 1000; 911 int off = offsets[(int)(val & OFFSET_MASK)]/1000; 912 long second = (val >> TRANSITION_NSHIFT)/1000; 913 ZoneOffset offset_old = ZoneOffset.ofTotalSeconds(off_old); 914 ZoneOffset offset = ZoneOffset.ofTotalSeconds(off); 915 sb.append(" " + LocalDateTime.ofEpochSecond(second, 0, offset_old)); 916 917 sb.append(" [utc=" + second + 918 " raw=" + Long.toHexString(val >> TRANSITION_NSHIFT) + 919 ", offset=" + off + "/" + offset + ", saving=" + save + "]"); 920 return off; 921 } 922 923 public String diffsTo(ZoneInfoOld other) { 924 925 int rawOffset0 = other.rawOffset; 926 int checksum0 = other.checksum; 927 int dstSavings0 = other.dstSavings; 928 long[] transitions0 = other.transitions; 929 int[] offsets0 = other.offsets; 930 int[] simpleTimeZoneParams0 = other.simpleTimeZoneParams; 931 boolean willGMTOffsetChange0 = other.willGMTOffsetChange; 932 933 934 //return getClass().getName() + 935 StringBuilder sb = new StringBuilder(); 936 sb.append("******************************\n" + 937 getID() + " : " + other.getID()); 938 // ROC is excluded by ZoneInfoOld 939 if ("ROC".equals(getID())) { 940 return sb.toString(); 941 } 942 if (rawOffset != rawOffset0 || 943 dstSavings != dstSavings0 || 944 checksum != checksum0 || 945 willGMTOffsetChange != willGMTOffsetChange0 || 946 (simpleTimeZoneParams != null ) != (simpleTimeZoneParams0 != null) || 947 (transitions != null && transitions0 != null && 948 transitions.length != transitions0.length)) 949 { 950 sb.append("\n offset=" + getLastRawOffset() + 951 ",dstSavings=" + dstSavings + 952 ",useDaylight=" + useDaylightTime() + 953 ",transitions=" + ((transitions != null) ? transitions.length : 0) + 954 ",offsets=" + ((offsets != null) ? offsets.length : 0) + 955 ",checksum=" + checksum + 956 ",gmtChanged=" + willGMTOffsetChange) 957 .append("\n[NG]offset=" + rawOffset0 + 958 ",dstSavings=" + dstSavings0 + 959 ",useDaylight=" + (simpleTimeZoneParams != null) + 960 ",transitions=" + ((transitions0 != null) ? transitions0.length : 0) + 961 ",offsets=" + ((offsets0 != null) ? offsets0.length : 0) + 962 ",checksum=" + checksum0 + 963 ",gmtChanged=" + willGMTOffsetChange0 + 964 ""); 965 } 966 // offsets 967 if (!Arrays.equals(offsets, offsets0)) { 968 sb.append("\n offset.len=" + ((offsets != null)? offsets.length : "null") + 969 " " + ((offsets0 != null)? offsets0.length : "null")); 970 if (offsets != null && offsets0.length != 0) { 971 int len = Math.min(offsets.length, offsets0.length); 972 int i = 0; 973 for (i = 0; i < len; i++) { 974 sb.append("\n " + 975 ZoneOffset.ofTotalSeconds(offsets[i]/1000) + " " + 976 ZoneOffset.ofTotalSeconds(offsets0[i]/1000)); 977 } 978 for (; i < offsets0.length; i++) { 979 sb.append("\n " + ZoneOffset.ofTotalSeconds(offsets0[i]/1000)); 980 } 981 } 982 } 983 // trans 984 int offset = 0; 985 int offset0 = 0; 986 if (!equalsTransOffsets(other)) { 987 sb.append("\n -------------"); 988 if ((transitions == null) != (transitions0 == null)) { 989 sb.append("\n (NG) Different trans(null) :" + 990 transitions + ", " + transitions0); 991 if (transitions != null) { 992 for (int i = 0; i < transitions.length; i++) { 993 sb.append("\n (NG)"); 994 offset = transToString(transitions[i], offset, offsets, sb); 995 } 996 } 997 } else { 998 if (transitions.length != transitions0.length) { 999 sb.append("\n (NG) Different trans size :" + 1000 transitions.length + ", " + transitions0.length); 1001 } 1002 int length = Math.min(transitions.length, transitions0.length); 1003 for (int i = 0; i < length; i++) { 1004 // sb.append("\n[" + i + "] "); 1005 // offset = transToString(transitions[i], offset, offsets, sb); 1006 long val = transitions[i]; 1007 int dst = (int)((val >>> DST_NSHIFT) & 0xfL); 1008 int save = (dst == 0) ? 0 : offsets[dst] / 1000; 1009 int off = offsets[(int)(val & OFFSET_MASK)]/1000; 1010 long second = (val >> TRANSITION_NSHIFT)/1000; 1011 sb.append("\n "); 1012 offset = transToString(transitions[i], offset, offsets, sb); 1013 if (transitions0 == null || i >= transitions0.length) { 1014 sb.append("\n "); 1015 offset = transToString(transitions[i], offset, offsets, sb); 1016 sb.append("\n (NG) trans0 is null or < trans.length"); 1017 } else { 1018 long val0 = transitions0[i]; 1019 int dst0 = (int)((val0 >>> DST_NSHIFT) & 0xfL); 1020 int save0 = (dst0 == 0) ? 0 : offsets0[dst0] / 1000; 1021 int off0 = offsets0[(int)(val0 & OFFSET_MASK)]/1000; 1022 long second0 = (val0 >> TRANSITION_NSHIFT)/1000; 1023 if (save != save0 || off != off0 || second != second0) { 1024 sb.append("\n (NG)"); 1025 } else { 1026 sb.append("\n (OK)"); 1027 } 1028 offset0 = transToString(transitions0[i], offset0, offsets0, sb); 1029 sb.append("\n -----"); 1030 } 1031 } 1032 } 1033 } 1034 SimpleTimeZone stz = getLastRuleInstance(); 1035 if (stz != null) { 1036 SimpleTimeZone stz0 = other.getLastRule(); 1037 if (!stz.hasSameRules(stz0)) { 1038 sb.append("\n -------------") 1039 .append("\n SimpleTimeZone (NG)") 1040 .append("\n stz=" + stz) 1041 .append("\n stz0=" + stz0); 1042 } 1043 } 1044 sb.append("\n -------------"); 1045 return sb.toString(); 1046 } 1047 }