1 /* 2 * Copyright (c) 2007-present, Stephen Colebourne & Michael Nascimento Santos 3 * 4 * All rights reserved. 5 * 6 * Redistribution and use in source and binary forms, with or without 7 * modification, are permitted provided that the following conditions are met: 8 * 9 * * Redistributions of source code must retain the above copyright notice, 10 * this list of conditions and the following disclaimer. 11 * 12 * * Redistributions in binary form must reproduce the above copyright notice, 13 * this list of conditions and the following disclaimer in the documentation 14 * and/or other materials provided with the distribution. 15 * 16 * * Neither the name of JSR-310 nor the names of its contributors 17 * may be used to endorse or promote products derived from this software 18 * without specific prior written permission. 19 * 20 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 21 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 22 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 23 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR 24 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 25 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 26 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 27 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 28 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 29 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 30 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 */ 32 package org.threeten.bp.zone; 33 34 import java.io.DataInput; 35 import java.io.DataOutput; 36 import java.io.IOException; 37 import java.io.Serializable; 38 import java.util.ArrayList; 39 import java.util.Arrays; 40 import java.util.Collections; 41 import java.util.List; 42 import java.util.concurrent.ConcurrentHashMap; 43 import java.util.concurrent.ConcurrentMap; 44 45 import org.threeten.bp.Duration; 46 import org.threeten.bp.Instant; 47 import org.threeten.bp.LocalDate; 48 import org.threeten.bp.LocalDateTime; 49 import org.threeten.bp.Year; 50 import org.threeten.bp.ZoneOffset; 51 import org.threeten.bp.jdk8.Jdk8Methods; 52 53 /** 54 * The rules describing how the zone offset varies through the year and historically. 55 * <p> 56 * This class is used by the TZDB time-zone rules. 57 * 58 * <h3>Specification for implementors</h3> 59 * This class is immutable and thread-safe. 60 */ 61 final class StandardZoneRules extends ZoneRules implements Serializable { 62 63 /** 64 * Serialization version. 65 */ 66 private static final long serialVersionUID = 3044319355680032515L; 67 /** 68 * The last year to have its transitions cached. 69 */ 70 private static final int LAST_CACHED_YEAR = 2100; 71 72 /** 73 * The transitions between standard offsets (epoch seconds), sorted. 74 */ 75 private final long[] standardTransitions; 76 /** 77 * The standard offsets. 78 */ 79 private final ZoneOffset[] standardOffsets; 80 /** 81 * The transitions between instants (epoch seconds), sorted. 82 */ 83 private final long[] savingsInstantTransitions; 84 /** 85 * The transitions between local date-times, sorted. 86 * This is a paired array, where the first entry is the start of the transition 87 * and the second entry is the end of the transition. 88 */ 89 private final LocalDateTime[] savingsLocalTransitions; 90 /** 91 * The wall offsets. 92 */ 93 private final ZoneOffset[] wallOffsets; 94 /** 95 * The last rule. 96 */ 97 private final ZoneOffsetTransitionRule[] lastRules; 98 /** 99 * The map of recent transitions. 100 */ 101 private final ConcurrentMap<Integer, ZoneOffsetTransition[]> lastRulesCache = 102 new ConcurrentHashMap<Integer, ZoneOffsetTransition[]>(); 103 104 /** 105 * Creates an instance. 106 * 107 * @param baseStandardOffset the standard offset to use before legal rules were set, not null 108 * @param baseWallOffset the wall offset to use before legal rules were set, not null 109 * @param standardOffsetTransitionList the list of changes to the standard offset, not null 110 * @param transitionList the list of transitions, not null 111 * @param lastRules the recurring last rules, size 15 or less, not null 112 */ StandardZoneRules( ZoneOffset baseStandardOffset, ZoneOffset baseWallOffset, List<ZoneOffsetTransition> standardOffsetTransitionList, List<ZoneOffsetTransition> transitionList, List<ZoneOffsetTransitionRule> lastRules)113 StandardZoneRules( 114 ZoneOffset baseStandardOffset, 115 ZoneOffset baseWallOffset, 116 List<ZoneOffsetTransition> standardOffsetTransitionList, 117 List<ZoneOffsetTransition> transitionList, 118 List<ZoneOffsetTransitionRule> lastRules) { 119 super(); 120 121 // convert standard transitions 122 this.standardTransitions = new long[standardOffsetTransitionList.size()]; 123 this.standardOffsets = new ZoneOffset[standardOffsetTransitionList.size() + 1]; 124 this.standardOffsets[0] = baseStandardOffset; 125 for (int i = 0; i < standardOffsetTransitionList.size(); i++) { 126 this.standardTransitions[i] = standardOffsetTransitionList.get(i).toEpochSecond(); 127 this.standardOffsets[i + 1] = standardOffsetTransitionList.get(i).getOffsetAfter(); 128 } 129 130 // convert savings transitions to locals 131 List<LocalDateTime> localTransitionList = new ArrayList<LocalDateTime>(); 132 List<ZoneOffset> localTransitionOffsetList = new ArrayList<ZoneOffset>(); 133 localTransitionOffsetList.add(baseWallOffset); 134 for (ZoneOffsetTransition trans : transitionList) { 135 if (trans.isGap()) { 136 localTransitionList.add(trans.getDateTimeBefore()); 137 localTransitionList.add(trans.getDateTimeAfter()); 138 } else { 139 localTransitionList.add(trans.getDateTimeAfter()); 140 localTransitionList.add(trans.getDateTimeBefore()); 141 } 142 localTransitionOffsetList.add(trans.getOffsetAfter()); 143 } 144 this.savingsLocalTransitions = localTransitionList.toArray(new LocalDateTime[localTransitionList.size()]); 145 this.wallOffsets = localTransitionOffsetList.toArray(new ZoneOffset[localTransitionOffsetList.size()]); 146 147 // convert savings transitions to instants 148 this.savingsInstantTransitions = new long[transitionList.size()]; 149 for (int i = 0; i < transitionList.size(); i++) { 150 this.savingsInstantTransitions[i] = transitionList.get(i).getInstant().getEpochSecond(); 151 } 152 153 // last rules 154 if (lastRules.size() > 15) { 155 throw new IllegalArgumentException("Too many transition rules"); 156 } 157 this.lastRules = lastRules.toArray(new ZoneOffsetTransitionRule[lastRules.size()]); 158 } 159 160 /** 161 * Constructor. 162 * 163 * @param standardTransitions the standard transitions, not null 164 * @param standardOffsets the standard offsets, not null 165 * @param savingsInstantTransitions the standard transitions, not null 166 * @param wallOffsets the wall offsets, not null 167 * @param lastRules the recurring last rules, size 15 or less, not null 168 */ StandardZoneRules( long[] standardTransitions, ZoneOffset[] standardOffsets, long[] savingsInstantTransitions, ZoneOffset[] wallOffsets, ZoneOffsetTransitionRule[] lastRules)169 private StandardZoneRules( 170 long[] standardTransitions, 171 ZoneOffset[] standardOffsets, 172 long[] savingsInstantTransitions, 173 ZoneOffset[] wallOffsets, 174 ZoneOffsetTransitionRule[] lastRules) { 175 super(); 176 177 this.standardTransitions = standardTransitions; 178 this.standardOffsets = standardOffsets; 179 this.savingsInstantTransitions = savingsInstantTransitions; 180 this.wallOffsets = wallOffsets; 181 this.lastRules = lastRules; 182 183 // convert savings transitions to locals 184 List<LocalDateTime> localTransitionList = new ArrayList<LocalDateTime>(); 185 for (int i = 0; i < savingsInstantTransitions.length; i++) { 186 ZoneOffset before = wallOffsets[i]; 187 ZoneOffset after = wallOffsets[i + 1]; 188 ZoneOffsetTransition trans = new ZoneOffsetTransition(savingsInstantTransitions[i], before, after); 189 if (trans.isGap()) { 190 localTransitionList.add(trans.getDateTimeBefore()); 191 localTransitionList.add(trans.getDateTimeAfter()); 192 } else { 193 localTransitionList.add(trans.getDateTimeAfter()); 194 localTransitionList.add(trans.getDateTimeBefore()); 195 } 196 } 197 this.savingsLocalTransitions = localTransitionList.toArray(new LocalDateTime[localTransitionList.size()]); 198 } 199 200 //----------------------------------------------------------------------- 201 /** 202 * Uses a serialization delegate. 203 * 204 * @return the replacing object, not null 205 */ writeReplace()206 private Object writeReplace() { 207 return new Ser(Ser.SZR, this); 208 } 209 210 /** 211 * Writes the state to the stream. 212 * 213 * @param out the output stream, not null 214 * @throws IOException if an error occurs 215 */ writeExternal(DataOutput out)216 void writeExternal(DataOutput out) throws IOException { 217 out.writeInt(standardTransitions.length); 218 for (long trans : standardTransitions) { 219 Ser.writeEpochSec(trans, out); 220 } 221 for (ZoneOffset offset : standardOffsets) { 222 Ser.writeOffset(offset, out); 223 } 224 out.writeInt(savingsInstantTransitions.length); 225 for (long trans : savingsInstantTransitions) { 226 Ser.writeEpochSec(trans, out); 227 } 228 for (ZoneOffset offset : wallOffsets) { 229 Ser.writeOffset(offset, out); 230 } 231 out.writeByte(lastRules.length); 232 for (ZoneOffsetTransitionRule rule : lastRules) { 233 rule.writeExternal(out); 234 } 235 } 236 237 /** 238 * Reads the state from the stream. 239 * 240 * @param in the input stream, not null 241 * @return the created object, not null 242 * @throws IOException if an error occurs 243 */ readExternal(DataInput in)244 static StandardZoneRules readExternal(DataInput in) throws IOException, ClassNotFoundException { 245 int stdSize = in.readInt(); 246 long[] stdTrans = new long[stdSize]; 247 for (int i = 0; i < stdSize; i++) { 248 stdTrans[i] = Ser.readEpochSec(in); 249 } 250 ZoneOffset[] stdOffsets = new ZoneOffset[stdSize + 1]; 251 for (int i = 0; i < stdOffsets.length; i++) { 252 stdOffsets[i] = Ser.readOffset(in); 253 } 254 int savSize = in.readInt(); 255 long[] savTrans = new long[savSize]; 256 for (int i = 0; i < savSize; i++) { 257 savTrans[i] = Ser.readEpochSec(in); 258 } 259 ZoneOffset[] savOffsets = new ZoneOffset[savSize + 1]; 260 for (int i = 0; i < savOffsets.length; i++) { 261 savOffsets[i] = Ser.readOffset(in); 262 } 263 int ruleSize = in.readByte(); 264 ZoneOffsetTransitionRule[] rules = new ZoneOffsetTransitionRule[ruleSize]; 265 for (int i = 0; i < ruleSize; i++) { 266 rules[i] = ZoneOffsetTransitionRule.readExternal(in); 267 } 268 return new StandardZoneRules(stdTrans, stdOffsets, savTrans, savOffsets, rules); 269 } 270 271 //----------------------------------------------------------------------- 272 @Override isFixedOffset()273 public boolean isFixedOffset() { 274 return savingsInstantTransitions.length == 0 && lastRules.length == 0 && wallOffsets[0].equals(standardOffsets[0]); 275 } 276 277 //----------------------------------------------------------------------- 278 @Override getOffset(Instant instant)279 public ZoneOffset getOffset(Instant instant) { 280 long epochSec = instant.getEpochSecond(); 281 282 // check if using last rules 283 if (lastRules.length > 0 && (savingsInstantTransitions.length == 0 || 284 epochSec > savingsInstantTransitions[savingsInstantTransitions.length - 1])) { 285 int year = findYear(epochSec, wallOffsets[wallOffsets.length - 1]); 286 ZoneOffsetTransition[] transArray = findTransitionArray(year); 287 ZoneOffsetTransition trans = null; 288 for (int i = 0; i < transArray.length; i++) { 289 trans = transArray[i]; 290 if (epochSec < trans.toEpochSecond()) { 291 return trans.getOffsetBefore(); 292 } 293 } 294 return trans.getOffsetAfter(); 295 } 296 297 // using historic rules 298 int index = Arrays.binarySearch(savingsInstantTransitions, epochSec); 299 if (index < 0) { 300 // switch negative insert position to start of matched range 301 index = -index - 2; 302 } 303 return wallOffsets[index + 1]; 304 } 305 306 //----------------------------------------------------------------------- 307 @Override getOffset(LocalDateTime localDateTime)308 public ZoneOffset getOffset(LocalDateTime localDateTime) { 309 Object info = getOffsetInfo(localDateTime); 310 if (info instanceof ZoneOffsetTransition) { 311 return ((ZoneOffsetTransition) info).getOffsetBefore(); 312 } 313 return (ZoneOffset) info; 314 } 315 316 @Override getValidOffsets(LocalDateTime localDateTime)317 public List<ZoneOffset> getValidOffsets(LocalDateTime localDateTime) { 318 // should probably be optimized 319 Object info = getOffsetInfo(localDateTime); 320 if (info instanceof ZoneOffsetTransition) { 321 return ((ZoneOffsetTransition) info).getValidOffsets(); 322 } 323 return Collections.singletonList((ZoneOffset) info); 324 } 325 326 @Override getTransition(LocalDateTime localDateTime)327 public ZoneOffsetTransition getTransition(LocalDateTime localDateTime) { 328 Object info = getOffsetInfo(localDateTime); 329 return (info instanceof ZoneOffsetTransition ? (ZoneOffsetTransition) info : null); 330 } 331 getOffsetInfo(LocalDateTime dt)332 private Object getOffsetInfo(LocalDateTime dt) { 333 // check if using last rules 334 if (lastRules.length > 0 && (savingsLocalTransitions.length == 0 || 335 dt.isAfter(savingsLocalTransitions[savingsLocalTransitions.length - 1]))) { 336 ZoneOffsetTransition[] transArray = findTransitionArray(dt.getYear()); 337 Object info = null; 338 for (ZoneOffsetTransition trans : transArray) { 339 info = findOffsetInfo(dt, trans); 340 if (info instanceof ZoneOffsetTransition || info.equals(trans.getOffsetBefore())) { 341 return info; 342 } 343 } 344 return info; 345 } 346 347 // using historic rules 348 int index = Arrays.binarySearch(savingsLocalTransitions, dt); 349 if (index == -1) { 350 // before first transition 351 return wallOffsets[0]; 352 } 353 if (index < 0) { 354 // switch negative insert position to start of matched range 355 index = -index - 2; 356 } else if (index < savingsLocalTransitions.length - 1 && 357 savingsLocalTransitions[index].equals(savingsLocalTransitions[index + 1])) { 358 // handle overlap immediately following gap 359 index++; 360 } 361 if ((index & 1) == 0) { 362 // gap or overlap 363 LocalDateTime dtBefore = savingsLocalTransitions[index]; 364 LocalDateTime dtAfter = savingsLocalTransitions[index + 1]; 365 ZoneOffset offsetBefore = wallOffsets[index / 2]; 366 ZoneOffset offsetAfter = wallOffsets[index / 2 + 1]; 367 if (offsetAfter.getTotalSeconds() > offsetBefore.getTotalSeconds()) { 368 // gap 369 return new ZoneOffsetTransition(dtBefore, offsetBefore, offsetAfter); 370 } else { 371 // overlap 372 return new ZoneOffsetTransition(dtAfter, offsetBefore, offsetAfter); 373 } 374 } else { 375 // normal (neither gap or overlap) 376 return wallOffsets[index / 2 + 1]; 377 } 378 } 379 380 /** 381 * Finds the offset info for a local date-time and transition. 382 * 383 * @param dt the date-time, not null 384 * @param trans the transition, not null 385 * @return the offset info, not null 386 */ findOffsetInfo(LocalDateTime dt, ZoneOffsetTransition trans)387 private Object findOffsetInfo(LocalDateTime dt, ZoneOffsetTransition trans) { 388 LocalDateTime localTransition = trans.getDateTimeBefore(); 389 if (trans.isGap()) { 390 if (dt.isBefore(localTransition)) { 391 return trans.getOffsetBefore(); 392 } 393 if (dt.isBefore(trans.getDateTimeAfter())) { 394 return trans; 395 } else { 396 return trans.getOffsetAfter(); 397 } 398 } else { 399 if (dt.isBefore(localTransition) == false) { 400 return trans.getOffsetAfter(); 401 } 402 if (dt.isBefore(trans.getDateTimeAfter())) { 403 return trans.getOffsetBefore(); 404 } else { 405 return trans; 406 } 407 } 408 } 409 410 @Override isValidOffset(LocalDateTime localDateTime, ZoneOffset offset)411 public boolean isValidOffset(LocalDateTime localDateTime, ZoneOffset offset) { 412 return getValidOffsets(localDateTime).contains(offset); 413 } 414 415 //----------------------------------------------------------------------- 416 /** 417 * Finds the appropriate transition array for the given year. 418 * 419 * @param year the year, not null 420 * @return the transition array, not null 421 */ findTransitionArray(int year)422 private ZoneOffsetTransition[] findTransitionArray(int year) { 423 Integer yearObj = year; // should use Year class, but this saves a class load 424 ZoneOffsetTransition[] transArray = lastRulesCache.get(yearObj); 425 if (transArray != null) { 426 return transArray; 427 } 428 ZoneOffsetTransitionRule[] ruleArray = lastRules; 429 transArray = new ZoneOffsetTransition[ruleArray.length]; 430 for (int i = 0; i < ruleArray.length; i++) { 431 transArray[i] = ruleArray[i].createTransition(year); 432 } 433 if (year < LAST_CACHED_YEAR) { 434 lastRulesCache.putIfAbsent(yearObj, transArray); 435 } 436 return transArray; 437 } 438 439 //----------------------------------------------------------------------- 440 @Override getStandardOffset(Instant instant)441 public ZoneOffset getStandardOffset(Instant instant) { 442 long epochSec = instant.getEpochSecond(); 443 int index = Arrays.binarySearch(standardTransitions, epochSec); 444 if (index < 0) { 445 // switch negative insert position to start of matched range 446 index = -index - 2; 447 } 448 return standardOffsets[index + 1]; 449 } 450 451 @Override getDaylightSavings(Instant instant)452 public Duration getDaylightSavings(Instant instant) { 453 ZoneOffset standardOffset = getStandardOffset(instant); 454 ZoneOffset actualOffset = getOffset(instant); 455 return Duration.ofSeconds(actualOffset.getTotalSeconds() - standardOffset.getTotalSeconds()); 456 } 457 458 @Override isDaylightSavings(Instant instant)459 public boolean isDaylightSavings(Instant instant) { 460 return (getStandardOffset(instant).equals(getOffset(instant)) == false); 461 } 462 463 //----------------------------------------------------------------------- 464 @Override nextTransition(Instant instant)465 public ZoneOffsetTransition nextTransition(Instant instant) { 466 if (savingsInstantTransitions.length == 0) { 467 return null; 468 } 469 470 long epochSec = instant.getEpochSecond(); 471 472 // check if using last rules 473 if (epochSec >= savingsInstantTransitions[savingsInstantTransitions.length - 1]) { 474 if (lastRules.length == 0) { 475 return null; 476 } 477 // search year the instant is in 478 int year = findYear(epochSec, wallOffsets[wallOffsets.length - 1]); 479 ZoneOffsetTransition[] transArray = findTransitionArray(year); 480 for (ZoneOffsetTransition trans : transArray) { 481 if (epochSec < trans.toEpochSecond()) { 482 return trans; 483 } 484 } 485 // use first from following year 486 if (year < Year.MAX_VALUE) { 487 transArray = findTransitionArray(year + 1); 488 return transArray[0]; 489 } 490 return null; 491 } 492 493 // using historic rules 494 int index = Arrays.binarySearch(savingsInstantTransitions, epochSec); 495 if (index < 0) { 496 index = -index - 1; // switched value is the next transition 497 } else { 498 index += 1; // exact match, so need to add one to get the next 499 } 500 return new ZoneOffsetTransition(savingsInstantTransitions[index], wallOffsets[index], wallOffsets[index + 1]); 501 } 502 503 @Override previousTransition(Instant instant)504 public ZoneOffsetTransition previousTransition(Instant instant) { 505 if (savingsInstantTransitions.length == 0) { 506 return null; 507 } 508 509 long epochSec = instant.getEpochSecond(); 510 if (instant.getNano() > 0 && epochSec < Long.MAX_VALUE) { 511 epochSec += 1; // allow rest of method to only use seconds 512 } 513 514 // check if using last rules 515 long lastHistoric = savingsInstantTransitions[savingsInstantTransitions.length - 1]; 516 if (lastRules.length > 0 && epochSec > lastHistoric) { 517 // search year the instant is in 518 ZoneOffset lastHistoricOffset = wallOffsets[wallOffsets.length - 1]; 519 int year = findYear(epochSec, lastHistoricOffset); 520 ZoneOffsetTransition[] transArray = findTransitionArray(year); 521 for (int i = transArray.length - 1; i >= 0; i--) { 522 if (epochSec > transArray[i].toEpochSecond()) { 523 return transArray[i]; 524 } 525 } 526 // use last from preceeding year 527 int lastHistoricYear = findYear(lastHistoric, lastHistoricOffset); 528 if (--year > lastHistoricYear) { 529 transArray = findTransitionArray(year); 530 return transArray[transArray.length - 1]; 531 } 532 // drop through 533 } 534 535 // using historic rules 536 int index = Arrays.binarySearch(savingsInstantTransitions, epochSec); 537 if (index < 0) { 538 index = -index - 1; 539 } 540 if (index <= 0) { 541 return null; 542 } 543 return new ZoneOffsetTransition(savingsInstantTransitions[index - 1], wallOffsets[index - 1], wallOffsets[index]); 544 } 545 findYear(long epochSecond, ZoneOffset offset)546 private int findYear(long epochSecond, ZoneOffset offset) { 547 // inline for performance 548 long localSecond = epochSecond + offset.getTotalSeconds(); 549 long localEpochDay = Jdk8Methods.floorDiv(localSecond, 86400); 550 return LocalDate.ofEpochDay(localEpochDay).getYear(); 551 } 552 553 //------------------------------------------------------------------------- 554 @Override getTransitions()555 public List<ZoneOffsetTransition> getTransitions() { 556 List<ZoneOffsetTransition> list = new ArrayList<ZoneOffsetTransition>(); 557 for (int i = 0; i < savingsInstantTransitions.length; i++) { 558 list.add(new ZoneOffsetTransition(savingsInstantTransitions[i], wallOffsets[i], wallOffsets[i + 1])); 559 } 560 return Collections.unmodifiableList(list); 561 } 562 563 @Override getTransitionRules()564 public List<ZoneOffsetTransitionRule> getTransitionRules() { 565 return Collections.unmodifiableList(Arrays.asList(lastRules)); 566 } 567 568 //----------------------------------------------------------------------- 569 @Override equals(Object obj)570 public boolean equals(Object obj) { 571 if (this == obj) { 572 return true; 573 } 574 if (obj instanceof StandardZoneRules) { 575 StandardZoneRules other = (StandardZoneRules) obj; 576 return Arrays.equals(standardTransitions, other.standardTransitions) && 577 Arrays.equals(standardOffsets, other.standardOffsets) && 578 Arrays.equals(savingsInstantTransitions, other.savingsInstantTransitions) && 579 Arrays.equals(wallOffsets, other.wallOffsets) && 580 Arrays.equals(lastRules, other.lastRules); 581 } 582 if (obj instanceof Fixed) { 583 return isFixedOffset() && getOffset(Instant.EPOCH).equals(((Fixed) obj).getOffset(Instant.EPOCH)); 584 } 585 return false; 586 } 587 588 @Override hashCode()589 public int hashCode() { 590 return Arrays.hashCode(standardTransitions) ^ 591 Arrays.hashCode(standardOffsets) ^ 592 Arrays.hashCode(savingsInstantTransitions) ^ 593 Arrays.hashCode(wallOffsets) ^ 594 Arrays.hashCode(lastRules); 595 } 596 597 //----------------------------------------------------------------------- 598 /** 599 * Returns a string describing this object. 600 * 601 * @return a string for debugging, not null 602 */ 603 @Override toString()604 public String toString() { 605 return "StandardZoneRules[currentStandardOffset=" + standardOffsets[standardOffsets.length - 1] + "]"; 606 } 607 608 } 609