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