1 /* 2 * Copyright (c) 2012, 2013, 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 /* 27 * This file is available under and governed by the GNU General Public 28 * License version 2 only, as published by the Free Software Foundation. 29 * However, the following notice accompanied the original version of this 30 * file: 31 * 32 * Copyright (c) 2009-2012, Stephen Colebourne & Michael Nascimento Santos 33 * 34 * All rights reserved. 35 * 36 * Redistribution and use in source and binary forms, with or without 37 * modification, are permitted provided that the following conditions are met: 38 * 39 * * Redistributions of source code must retain the above copyright notice, 40 * this list of conditions and the following disclaimer. 41 * 42 * * Redistributions in binary form must reproduce the above copyright notice, 43 * this list of conditions and the following disclaimer in the documentation 44 * and/or other materials provided with the distribution. 45 * 46 * * Neither the name of JSR-310 nor the names of its contributors 47 * may be used to endorse or promote products derived from this software 48 * without specific prior written permission. 49 * 50 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 51 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 52 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 53 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR 54 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 55 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 56 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 57 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 58 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 59 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 60 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 61 */ 62 package java.time.zone; 63 64 import java.io.DataInput; 65 import java.io.DataOutput; 66 import java.io.IOException; 67 import java.io.Serializable; 68 import java.time.Duration; 69 import java.time.Instant; 70 import java.time.LocalDate; 71 import java.time.LocalDateTime; 72 import java.time.ZoneId; 73 import java.time.ZoneOffset; 74 import java.time.temporal.Year; 75 import java.util.ArrayList; 76 import java.util.Arrays; 77 import java.util.Collections; 78 import java.util.List; 79 import java.util.Objects; 80 import java.util.concurrent.ConcurrentHashMap; 81 import java.util.concurrent.ConcurrentMap; 82 83 /** 84 * The rules defining how the zone offset varies for a single time-zone. 85 * <p> 86 * The rules model all the historic and future transitions for a time-zone. 87 * {@link ZoneOffsetTransition} is used for known transitions, typically historic. 88 * {@link ZoneOffsetTransitionRule} is used for future transitions that are based 89 * on the result of an algorithm. 90 * <p> 91 * The rules are loaded via {@link ZoneRulesProvider} using a {@link ZoneId}. 92 * The same rules may be shared internally between multiple zone IDs. 93 * <p> 94 * Serializing an instance of {@code ZoneRules} will store the entire set of rules. 95 * It does not store the zone ID as it is not part of the state of this object. 96 * <p> 97 * A rule implementation may or may not store full information about historic 98 * and future transitions, and the information stored is only as accurate as 99 * that supplied to the implementation by the rules provider. 100 * Applications should treat the data provided as representing the best information 101 * available to the implementation of this rule. 102 * 103 * <h3>Specification for implementors</h3> 104 * This class is immutable and thread-safe. 105 * 106 * @since 1.8 107 */ 108 public final class ZoneRules implements Serializable { 109 110 /** 111 * Serialization version. 112 */ 113 private static final long serialVersionUID = 3044319355680032515L; 114 /** 115 * The last year to have its transitions cached. 116 */ 117 private static final int LAST_CACHED_YEAR = 2100; 118 119 /** 120 * The transitions between standard offsets (epoch seconds), sorted. 121 */ 122 private final long[] standardTransitions; 123 /** 124 * The standard offsets. 125 */ 126 private final ZoneOffset[] standardOffsets; 127 /** 128 * The transitions between instants (epoch seconds), sorted. 129 */ 130 private final long[] savingsInstantTransitions; 131 /** 132 * The transitions between local date-times, sorted. 133 * This is a paired array, where the first entry is the start of the transition 134 * and the second entry is the end of the transition. 135 */ 136 private final LocalDateTime[] savingsLocalTransitions; 137 /** 138 * The wall offsets. 139 */ 140 private final ZoneOffset[] wallOffsets; 141 /** 142 * The last rule. 143 */ 144 private final ZoneOffsetTransitionRule[] lastRules; 145 /** 146 * The map of recent transitions. 147 */ 148 private final ConcurrentMap<Integer, ZoneOffsetTransition[]> lastRulesCache = 149 new ConcurrentHashMap<Integer, ZoneOffsetTransition[]>(); 150 /** 151 * The zero-length long array. 152 */ 153 private static final long[] EMPTY_LONG_ARRAY = new long[0]; 154 /** 155 * The zero-length lastrules array. 156 */ 157 private static final ZoneOffsetTransitionRule[] EMPTY_LASTRULES = 158 new ZoneOffsetTransitionRule[0]; 159 /** 160 * The zero-length ldt array. 161 */ 162 private static final LocalDateTime[] EMPTY_LDT_ARRAY = new LocalDateTime[0]; 163 164 /** 165 * Obtains an instance of a ZoneRules. 166 * 167 * @param baseStandardOffset the standard offset to use before legal rules were set, not null 168 * @param baseWallOffset the wall offset to use before legal rules were set, not null 169 * @param standardOffsetTransitionList the list of changes to the standard offset, not null 170 * @param transitionList the list of transitions, not null 171 * @param lastRules the recurring last rules, size 16 or less, not null 172 * @return the zone rules, not null 173 */ 174 public static ZoneRules of(ZoneOffset baseStandardOffset, 175 ZoneOffset baseWallOffset, 176 List<ZoneOffsetTransition> standardOffsetTransitionList, 177 List<ZoneOffsetTransition> transitionList, 178 List<ZoneOffsetTransitionRule> lastRules) { 179 Objects.requireNonNull(baseStandardOffset, "baseStandardOffset"); 180 Objects.requireNonNull(baseWallOffset, "baseWallOffset"); 181 Objects.requireNonNull(standardOffsetTransitionList, "standardOffsetTransitionList"); 182 Objects.requireNonNull(transitionList, "transitionList"); 183 Objects.requireNonNull(lastRules, "lastRules"); 184 return new ZoneRules(baseStandardOffset, baseWallOffset, 185 standardOffsetTransitionList, transitionList, lastRules); 186 } 187 188 /** 189 * Obtains an instance of ZoneRules that has fixed zone rules. 190 * 191 * @param offset the offset this fixed zone rules is based on, not null 192 * @return the zone rules, not null 193 * @see #isFixedOffset() 194 */ 195 public static ZoneRules of(ZoneOffset offset) { 196 Objects.requireNonNull(offset, "offset"); 197 return new ZoneRules(offset); 198 } 199 200 /** 201 * Creates an instance. 202 * 203 * @param baseStandardOffset the standard offset to use before legal rules were set, not null 204 * @param baseWallOffset the wall offset to use before legal rules were set, not null 205 * @param standardOffsetTransitionList the list of changes to the standard offset, not null 206 * @param transitionList the list of transitions, not null 207 * @param lastRules the recurring last rules, size 16 or less, not null 208 */ 209 ZoneRules(ZoneOffset baseStandardOffset, 210 ZoneOffset baseWallOffset, 211 List<ZoneOffsetTransition> standardOffsetTransitionList, 212 List<ZoneOffsetTransition> transitionList, 213 List<ZoneOffsetTransitionRule> lastRules) { 214 super(); 215 216 // convert standard transitions 217 218 this.standardTransitions = new long[standardOffsetTransitionList.size()]; 219 220 this.standardOffsets = new ZoneOffset[standardOffsetTransitionList.size() + 1]; 221 this.standardOffsets[0] = baseStandardOffset; 222 for (int i = 0; i < standardOffsetTransitionList.size(); i++) { 223 this.standardTransitions[i] = standardOffsetTransitionList.get(i).toEpochSecond(); 224 this.standardOffsets[i + 1] = standardOffsetTransitionList.get(i).getOffsetAfter(); 225 } 226 227 // convert savings transitions to locals 228 List<LocalDateTime> localTransitionList = new ArrayList<>(); 229 List<ZoneOffset> localTransitionOffsetList = new ArrayList<>(); 230 localTransitionOffsetList.add(baseWallOffset); 231 for (ZoneOffsetTransition trans : transitionList) { 232 if (trans.isGap()) { 233 localTransitionList.add(trans.getDateTimeBefore()); 234 localTransitionList.add(trans.getDateTimeAfter()); 235 } else { 236 localTransitionList.add(trans.getDateTimeAfter()); 237 localTransitionList.add(trans.getDateTimeBefore()); 238 } 239 localTransitionOffsetList.add(trans.getOffsetAfter()); 240 } 241 this.savingsLocalTransitions = localTransitionList.toArray(new LocalDateTime[localTransitionList.size()]); 242 this.wallOffsets = localTransitionOffsetList.toArray(new ZoneOffset[localTransitionOffsetList.size()]); 243 244 // convert savings transitions to instants 245 this.savingsInstantTransitions = new long[transitionList.size()]; 246 for (int i = 0; i < transitionList.size(); i++) { 247 this.savingsInstantTransitions[i] = transitionList.get(i).toEpochSecond(); 248 } 249 250 // last rules 251 if (lastRules.size() > 16) { 252 throw new IllegalArgumentException("Too many transition rules"); 253 } 254 this.lastRules = lastRules.toArray(new ZoneOffsetTransitionRule[lastRules.size()]); 255 } 256 257 /** 258 * Constructor. 259 * 260 * @param standardTransitions the standard transitions, not null 261 * @param standardOffsets the standard offsets, not null 262 * @param savingsInstantTransitions the standard transitions, not null 263 * @param wallOffsets the wall offsets, not null 264 * @param lastRules the recurring last rules, size 15 or less, not null 265 */ 266 private ZoneRules(long[] standardTransitions, 267 ZoneOffset[] standardOffsets, 268 long[] savingsInstantTransitions, 269 ZoneOffset[] wallOffsets, 270 ZoneOffsetTransitionRule[] lastRules) { 271 super(); 272 273 this.standardTransitions = standardTransitions; 274 this.standardOffsets = standardOffsets; 275 this.savingsInstantTransitions = savingsInstantTransitions; 276 this.wallOffsets = wallOffsets; 277 this.lastRules = lastRules; 278 279 if (savingsInstantTransitions.length == 0) { 280 this.savingsLocalTransitions = EMPTY_LDT_ARRAY; 281 } else { 282 // convert savings transitions to locals 283 List<LocalDateTime> localTransitionList = new ArrayList<>(); 284 for (int i = 0; i < savingsInstantTransitions.length; i++) { 285 ZoneOffset before = wallOffsets[i]; 286 ZoneOffset after = wallOffsets[i + 1]; 287 ZoneOffsetTransition trans = new ZoneOffsetTransition(savingsInstantTransitions[i], before, after); 288 if (trans.isGap()) { 289 localTransitionList.add(trans.getDateTimeBefore()); 290 localTransitionList.add(trans.getDateTimeAfter()); 291 } else { 292 localTransitionList.add(trans.getDateTimeAfter()); 293 localTransitionList.add(trans.getDateTimeBefore()); 294 } 295 } 296 this.savingsLocalTransitions = localTransitionList.toArray(new LocalDateTime[localTransitionList.size()]); 297 } 298 } 299 300 /** 301 * Creates an instance of ZoneRules that has fixed zone rules. 302 * 303 * @param offset the offset this fixed zone rules is based on, not null 304 * @return the zone rules, not null 305 * @see #isFixedOffset() 306 */ 307 private ZoneRules(ZoneOffset offset) { 308 this.standardOffsets = new ZoneOffset[1]; 309 this.standardOffsets[0] = offset; 310 this.standardTransitions = EMPTY_LONG_ARRAY; 311 this.savingsInstantTransitions = EMPTY_LONG_ARRAY; 312 this.savingsLocalTransitions = EMPTY_LDT_ARRAY; 313 this.wallOffsets = standardOffsets; 314 this.lastRules = EMPTY_LASTRULES; 315 } 316 317 /** 318 * Uses a serialization delegate. 319 * 320 * @return the replacing object, not null 321 */ 322 private Object writeReplace() { 323 return new Ser(Ser.ZRULES, this); 324 } 325 326 /** 327 * Writes the state to the stream. 328 * 329 * @param out the output stream, not null 330 * @throws IOException if an error occurs 331 */ 332 void writeExternal(DataOutput out) throws IOException { 333 out.writeInt(standardTransitions.length); 334 for (long trans : standardTransitions) { 335 Ser.writeEpochSec(trans, out); 336 } 337 for (ZoneOffset offset : standardOffsets) { 338 Ser.writeOffset(offset, out); 339 } 340 out.writeInt(savingsInstantTransitions.length); 341 for (long trans : savingsInstantTransitions) { 342 Ser.writeEpochSec(trans, out); 343 } 344 for (ZoneOffset offset : wallOffsets) { 345 Ser.writeOffset(offset, out); 346 } 347 out.writeByte(lastRules.length); 348 for (ZoneOffsetTransitionRule rule : lastRules) { 349 rule.writeExternal(out); 350 } 351 } 352 353 /** 354 * Reads the state from the stream. 355 * 356 * @param in the input stream, not null 357 * @return the created object, not null 358 * @throws IOException if an error occurs 359 */ 360 static ZoneRules readExternal(DataInput in) throws IOException, ClassNotFoundException { 361 int stdSize = in.readInt(); 362 long[] stdTrans = (stdSize == 0) ? EMPTY_LONG_ARRAY 363 : new long[stdSize]; 364 for (int i = 0; i < stdSize; i++) { 365 stdTrans[i] = Ser.readEpochSec(in); 366 } 367 ZoneOffset[] stdOffsets = new ZoneOffset[stdSize + 1]; 368 for (int i = 0; i < stdOffsets.length; i++) { 369 stdOffsets[i] = Ser.readOffset(in); 370 } 371 int savSize = in.readInt(); 372 long[] savTrans = (savSize == 0) ? EMPTY_LONG_ARRAY 373 : new long[savSize]; 374 for (int i = 0; i < savSize; i++) { 375 savTrans[i] = Ser.readEpochSec(in); 376 } 377 ZoneOffset[] savOffsets = new ZoneOffset[savSize + 1]; 378 for (int i = 0; i < savOffsets.length; i++) { 379 savOffsets[i] = Ser.readOffset(in); 380 } 381 int ruleSize = in.readByte(); 382 ZoneOffsetTransitionRule[] rules = (ruleSize == 0) ? 383 EMPTY_LASTRULES : new ZoneOffsetTransitionRule[ruleSize]; 384 for (int i = 0; i < ruleSize; i++) { 385 rules[i] = ZoneOffsetTransitionRule.readExternal(in); 386 } 387 return new ZoneRules(stdTrans, stdOffsets, savTrans, savOffsets, rules); 388 } 389 390 /** 391 * Checks of the zone rules are fixed, such that the offset never varies. 392 * 393 * @return true if the time-zone is fixed and the offset never changes 394 */ 395 public boolean isFixedOffset() { 396 return savingsInstantTransitions.length == 0; 397 } 398 399 /** 400 * Gets the offset applicable at the specified instant in these rules. 401 * <p> 402 * The mapping from an instant to an offset is simple, there is only 403 * one valid offset for each instant. 404 * This method returns that offset. 405 * 406 * @param instant the instant to find the offset for, not null, but null 407 * may be ignored if the rules have a single offset for all instants 408 * @return the offset, not null 409 */ 410 public ZoneOffset getOffset(Instant instant) { 411 if (savingsInstantTransitions.length == 0) { 412 return standardOffsets[0]; 413 } 414 long epochSec = instant.getEpochSecond(); 415 // check if using last rules 416 if (lastRules.length > 0 && 417 epochSec > savingsInstantTransitions[savingsInstantTransitions.length - 1]) { 418 int year = findYear(epochSec, wallOffsets[wallOffsets.length - 1]); 419 ZoneOffsetTransition[] transArray = findTransitionArray(year); 420 ZoneOffsetTransition trans = null; 421 for (int i = 0; i < transArray.length; i++) { 422 trans = transArray[i]; 423 if (epochSec < trans.toEpochSecond()) { 424 return trans.getOffsetBefore(); 425 } 426 } 427 return trans.getOffsetAfter(); 428 } 429 430 // using historic rules 431 int index = Arrays.binarySearch(savingsInstantTransitions, epochSec); 432 if (index < 0) { 433 // switch negative insert position to start of matched range 434 index = -index - 2; 435 } 436 return wallOffsets[index + 1]; 437 } 438 439 /** 440 * Gets a suitable offset for the specified local date-time in these rules. 441 * <p> 442 * The mapping from a local date-time to an offset is not straightforward. 443 * There are three cases: 444 * <p><ul> 445 * <li>Normal, with one valid offset. For the vast majority of the year, the normal 446 * case applies, where there is a single valid offset for the local date-time.</li> 447 * <li>Gap, with zero valid offsets. This is when clocks jump forward typically 448 * due to the spring daylight savings change from "winter" to "summer". 449 * In a gap there are local date-time values with no valid offset.</li> 450 * <li>Overlap, with two valid offsets. This is when clocks are set back typically 451 * due to the autumn daylight savings change from "summer" to "winter". 452 * In an overlap there are local date-time values with two valid offsets.</li> 453 * </ul><p> 454 * Thus, for any given local date-time there can be zero, one or two valid offsets. 455 * This method returns the single offset in the Normal case, and in the Gap or Overlap 456 * case it returns the offset before the transition. 457 * <p> 458 * Since, in the case of Gap and Overlap, the offset returned is a "best" value, rather 459 * than the "correct" value, it should be treated with care. Applications that care 460 * about the correct offset should use a combination of this method, 461 * {@link #getValidOffsets(LocalDateTime)} and {@link #getTransition(LocalDateTime)}. 462 * 463 * @param localDateTime the local date-time to query, not null, but null 464 * may be ignored if the rules have a single offset for all instants 465 * @return the best available offset for the local date-time, not null 466 */ 467 public ZoneOffset getOffset(LocalDateTime localDateTime) { 468 Object info = getOffsetInfo(localDateTime); 469 if (info instanceof ZoneOffsetTransition) { 470 return ((ZoneOffsetTransition) info).getOffsetBefore(); 471 } 472 return (ZoneOffset) info; 473 } 474 475 /** 476 * Gets the offset applicable at the specified local date-time in these rules. 477 * <p> 478 * The mapping from a local date-time to an offset is not straightforward. 479 * There are three cases: 480 * <p><ul> 481 * <li>Normal, with one valid offset. For the vast majority of the year, the normal 482 * case applies, where there is a single valid offset for the local date-time.</li> 483 * <li>Gap, with zero valid offsets. This is when clocks jump forward typically 484 * due to the spring daylight savings change from "winter" to "summer". 485 * In a gap there are local date-time values with no valid offset.</li> 486 * <li>Overlap, with two valid offsets. This is when clocks are set back typically 487 * due to the autumn daylight savings change from "summer" to "winter". 488 * In an overlap there are local date-time values with two valid offsets.</li> 489 * </ul><p> 490 * Thus, for any given local date-time there can be zero, one or two valid offsets. 491 * This method returns that list of valid offsets, which is a list of size 0, 1 or 2. 492 * In the case where there are two offsets, the earlier offset is returned at index 0 493 * and the later offset at index 1. 494 * <p> 495 * There are various ways to handle the conversion from a {@code LocalDateTime}. 496 * One technique, using this method, would be: 497 * <pre> 498 * List<ZoneOffset> validOffsets = rules.getOffset(localDT); 499 * if (validOffsets.size() == 1) { 500 * // Normal case: only one valid offset 501 * zoneOffset = validOffsets.get(0); 502 * } else { 503 * // Gap or Overlap: determine what to do from transition (which will be non-null) 504 * ZoneOffsetTransition trans = rules.getTransition(localDT); 505 * } 506 * </pre> 507 * <p> 508 * In theory, it is possible for there to be more than two valid offsets. 509 * This would happen if clocks to be put back more than once in quick succession. 510 * This has never happened in the history of time-zones and thus has no special handling. 511 * However, if it were to happen, then the list would return more than 2 entries. 512 * 513 * @param localDateTime the local date-time to query for valid offsets, not null, but null 514 * may be ignored if the rules have a single offset for all instants 515 * @return the list of valid offsets, may be immutable, not null 516 */ 517 public List<ZoneOffset> getValidOffsets(LocalDateTime localDateTime) { 518 // should probably be optimized 519 Object info = getOffsetInfo(localDateTime); 520 if (info instanceof ZoneOffsetTransition) { 521 return ((ZoneOffsetTransition) info).getValidOffsets(); 522 } 523 return Collections.singletonList((ZoneOffset) info); 524 } 525 526 /** 527 * Gets the offset transition applicable at the specified local date-time in these rules. 528 * <p> 529 * The mapping from a local date-time to an offset is not straightforward. 530 * There are three cases: 531 * <p><ul> 532 * <li>Normal, with one valid offset. For the vast majority of the year, the normal 533 * case applies, where there is a single valid offset for the local date-time.</li> 534 * <li>Gap, with zero valid offsets. This is when clocks jump forward typically 535 * due to the spring daylight savings change from "winter" to "summer". 536 * In a gap there are local date-time values with no valid offset.</li> 537 * <li>Overlap, with two valid offsets. This is when clocks are set back typically 538 * due to the autumn daylight savings change from "summer" to "winter". 539 * In an overlap there are local date-time values with two valid offsets.</li> 540 * </ul><p> 541 * A transition is used to model the cases of a Gap or Overlap. 542 * The Normal case will return null. 543 * <p> 544 * There are various ways to handle the conversion from a {@code LocalDateTime}. 545 * One technique, using this method, would be: 546 * <pre> 547 * ZoneOffsetTransition trans = rules.getTransition(localDT); 548 * if (trans == null) { 549 * // Gap or Overlap: determine what to do from transition 550 * } else { 551 * // Normal case: only one valid offset 552 * zoneOffset = rule.getOffset(localDT); 553 * } 554 * </pre> 555 * 556 * @param localDateTime the local date-time to query for offset transition, not null, but null 557 * may be ignored if the rules have a single offset for all instants 558 * @return the offset transition, null if the local date-time is not in transition 559 */ 560 public ZoneOffsetTransition getTransition(LocalDateTime localDateTime) { 561 Object info = getOffsetInfo(localDateTime); 562 return (info instanceof ZoneOffsetTransition ? (ZoneOffsetTransition) info : null); 563 } 564 565 private Object getOffsetInfo(LocalDateTime dt) { 566 if (savingsInstantTransitions.length == 0) { 567 return standardOffsets[0]; 568 } 569 // check if using last rules 570 if (lastRules.length > 0 && 571 dt.isAfter(savingsLocalTransitions[savingsLocalTransitions.length - 1])) { 572 ZoneOffsetTransition[] transArray = findTransitionArray(dt.getYear()); 573 Object info = null; 574 for (ZoneOffsetTransition trans : transArray) { 575 info = findOffsetInfo(dt, trans); 576 if (info instanceof ZoneOffsetTransition || info.equals(trans.getOffsetBefore())) { 577 return info; 578 } 579 } 580 return info; 581 } 582 583 // using historic rules 584 int index = Arrays.binarySearch(savingsLocalTransitions, dt); 585 if (index == -1) { 586 // before first transition 587 return wallOffsets[0]; 588 } 589 if (index < 0) { 590 // switch negative insert position to start of matched range 591 index = -index - 2; 592 } else if (index < savingsLocalTransitions.length - 1 && 593 savingsLocalTransitions[index].equals(savingsLocalTransitions[index + 1])) { 594 // handle overlap immediately following gap 595 index++; 596 } 597 if ((index & 1) == 0) { 598 // gap or overlap 599 LocalDateTime dtBefore = savingsLocalTransitions[index]; 600 LocalDateTime dtAfter = savingsLocalTransitions[index + 1]; 601 ZoneOffset offsetBefore = wallOffsets[index / 2]; 602 ZoneOffset offsetAfter = wallOffsets[index / 2 + 1]; 603 if (offsetAfter.getTotalSeconds() > offsetBefore.getTotalSeconds()) { 604 // gap 605 return new ZoneOffsetTransition(dtBefore, offsetBefore, offsetAfter); 606 } else { 607 // overlap 608 return new ZoneOffsetTransition(dtAfter, offsetBefore, offsetAfter); 609 } 610 } else { 611 // normal (neither gap or overlap) 612 return wallOffsets[index / 2 + 1]; 613 } 614 } 615 616 /** 617 * Finds the offset info for a local date-time and transition. 618 * 619 * @param dt the date-time, not null 620 * @param trans the transition, not null 621 * @return the offset info, not null 622 */ 623 private Object findOffsetInfo(LocalDateTime dt, ZoneOffsetTransition trans) { 624 LocalDateTime localTransition = trans.getDateTimeBefore(); 625 if (trans.isGap()) { 626 if (dt.isBefore(localTransition)) { 627 return trans.getOffsetBefore(); 628 } 629 if (dt.isBefore(trans.getDateTimeAfter())) { 630 return trans; 631 } else { 632 return trans.getOffsetAfter(); 633 } 634 } else { 635 if (dt.isBefore(localTransition) == false) { 636 return trans.getOffsetAfter(); 637 } 638 if (dt.isBefore(trans.getDateTimeAfter())) { 639 return trans.getOffsetBefore(); 640 } else { 641 return trans; 642 } 643 } 644 } 645 646 /** 647 * Finds the appropriate transition array for the given year. 648 * 649 * @param year the year, not null 650 * @return the transition array, not null 651 */ 652 private ZoneOffsetTransition[] findTransitionArray(int year) { 653 Integer yearObj = year; // should use Year class, but this saves a class load 654 ZoneOffsetTransition[] transArray = lastRulesCache.get(yearObj); 655 if (transArray != null) { 656 return transArray; 657 } 658 ZoneOffsetTransitionRule[] ruleArray = lastRules; 659 transArray = new ZoneOffsetTransition[ruleArray.length]; 660 for (int i = 0; i < ruleArray.length; i++) { 661 transArray[i] = ruleArray[i].createTransition(year); 662 } 663 if (year < LAST_CACHED_YEAR) { 664 lastRulesCache.putIfAbsent(yearObj, transArray); 665 } 666 return transArray; 667 } 668 669 /** 670 * Gets the standard offset for the specified instant in this zone. 671 * <p> 672 * This provides access to historic information on how the standard offset 673 * has changed over time. 674 * The standard offset is the offset before any daylight saving time is applied. 675 * This is typically the offset applicable during winter. 676 * 677 * @param instant the instant to find the offset information for, not null, but null 678 * may be ignored if the rules have a single offset for all instants 679 * @return the standard offset, not null 680 */ 681 public ZoneOffset getStandardOffset(Instant instant) { 682 if (savingsInstantTransitions.length == 0) { 683 return standardOffsets[0]; 684 } 685 long epochSec = instant.getEpochSecond(); 686 int index = Arrays.binarySearch(standardTransitions, epochSec); 687 if (index < 0) { 688 // switch negative insert position to start of matched range 689 index = -index - 2; 690 } 691 return standardOffsets[index + 1]; 692 } 693 694 /** 695 * Gets the amount of daylight savings in use for the specified instant in this zone. 696 * <p> 697 * This provides access to historic information on how the amount of daylight 698 * savings has changed over time. 699 * This is the difference between the standard offset and the actual offset. 700 * Typically the amount is zero during winter and one hour during summer. 701 * Time-zones are second-based, so the nanosecond part of the duration will be zero. 702 * <p> 703 * This default implementation calculates the duration from the 704 * {@link #getOffset(java.time.Instant) actual} and 705 * {@link #getStandardOffset(java.time.Instant) standard} offsets. 706 * 707 * @param instant the instant to find the daylight savings for, not null, but null 708 * may be ignored if the rules have a single offset for all instants 709 * @return the difference between the standard and actual offset, not null 710 */ 711 public Duration getDaylightSavings(Instant instant) { 712 if (savingsInstantTransitions.length == 0) { 713 return Duration.ZERO; 714 } 715 ZoneOffset standardOffset = getStandardOffset(instant); 716 ZoneOffset actualOffset = getOffset(instant); 717 return Duration.ofSeconds(actualOffset.getTotalSeconds() - standardOffset.getTotalSeconds()); 718 } 719 720 /** 721 * Checks if the specified instant is in daylight savings. 722 * <p> 723 * This checks if the standard offset and the actual offset are the same 724 * for the specified instant. 725 * If they are not, it is assumed that daylight savings is in operation. 726 * <p> 727 * This default implementation compares the {@link #getOffset(java.time.Instant) actual} 728 * and {@link #getStandardOffset(java.time.Instant) standard} offsets. 729 * 730 * @param instant the instant to find the offset information for, not null, but null 731 * may be ignored if the rules have a single offset for all instants 732 * @return the standard offset, not null 733 */ 734 public boolean isDaylightSavings(Instant instant) { 735 return (getStandardOffset(instant).equals(getOffset(instant)) == false); 736 } 737 738 /** 739 * Checks if the offset date-time is valid for these rules. 740 * <p> 741 * To be valid, the local date-time must not be in a gap and the offset 742 * must match one of the valid offsets. 743 * <p> 744 * This default implementation checks if {@link #getValidOffsets(java.time.LocalDateTime)} 745 * contains the specified offset. 746 * 747 * @param localDateTime the date-time to check, not null, but null 748 * may be ignored if the rules have a single offset for all instants 749 * @param offset the offset to check, null returns false 750 * @return true if the offset date-time is valid for these rules 751 */ 752 public boolean isValidOffset(LocalDateTime localDateTime, ZoneOffset offset) { 753 return getValidOffsets(localDateTime).contains(offset); 754 } 755 756 /** 757 * Gets the next transition after the specified instant. 758 * <p> 759 * This returns details of the next transition after the specified instant. 760 * For example, if the instant represents a point where "Summer" daylight savings time 761 * applies, then the method will return the transition to the next "Winter" time. 762 * 763 * @param instant the instant to get the next transition after, not null, but null 764 * may be ignored if the rules have a single offset for all instants 765 * @return the next transition after the specified instant, null if this is after the last transition 766 */ 767 public ZoneOffsetTransition nextTransition(Instant instant) { 768 if (savingsInstantTransitions.length == 0) { 769 return null; 770 } 771 long epochSec = instant.getEpochSecond(); 772 // check if using last rules 773 if (epochSec >= savingsInstantTransitions[savingsInstantTransitions.length - 1]) { 774 if (lastRules.length == 0) { 775 return null; 776 } 777 // search year the instant is in 778 int year = findYear(epochSec, wallOffsets[wallOffsets.length - 1]); 779 ZoneOffsetTransition[] transArray = findTransitionArray(year); 780 for (ZoneOffsetTransition trans : transArray) { 781 if (epochSec < trans.toEpochSecond()) { 782 return trans; 783 } 784 } 785 // use first from following year 786 if (year < Year.MAX_VALUE) { 787 transArray = findTransitionArray(year + 1); 788 return transArray[0]; 789 } 790 return null; 791 } 792 793 // using historic rules 794 int index = Arrays.binarySearch(savingsInstantTransitions, epochSec); 795 if (index < 0) { 796 index = -index - 1; // switched value is the next transition 797 } else { 798 index += 1; // exact match, so need to add one to get the next 799 } 800 return new ZoneOffsetTransition(savingsInstantTransitions[index], wallOffsets[index], wallOffsets[index + 1]); 801 } 802 803 /** 804 * Gets the previous transition before the specified instant. 805 * <p> 806 * This returns details of the previous transition after the specified instant. 807 * For example, if the instant represents a point where "summer" daylight saving time 808 * applies, then the method will return the transition from the previous "winter" time. 809 * 810 * @param instant the instant to get the previous transition after, not null, but null 811 * may be ignored if the rules have a single offset for all instants 812 * @return the previous transition after the specified instant, null if this is before the first transition 813 */ 814 public ZoneOffsetTransition previousTransition(Instant instant) { 815 if (savingsInstantTransitions.length == 0) { 816 return null; 817 } 818 long epochSec = instant.getEpochSecond(); 819 if (instant.getNano() > 0 && epochSec < Long.MAX_VALUE) { 820 epochSec += 1; // allow rest of method to only use seconds 821 } 822 823 // check if using last rules 824 long lastHistoric = savingsInstantTransitions[savingsInstantTransitions.length - 1]; 825 if (lastRules.length > 0 && epochSec > lastHistoric) { 826 // search year the instant is in 827 ZoneOffset lastHistoricOffset = wallOffsets[wallOffsets.length - 1]; 828 int year = findYear(epochSec, lastHistoricOffset); 829 ZoneOffsetTransition[] transArray = findTransitionArray(year); 830 for (int i = transArray.length - 1; i >= 0; i--) { 831 if (epochSec > transArray[i].toEpochSecond()) { 832 return transArray[i]; 833 } 834 } 835 // use last from preceeding year 836 int lastHistoricYear = findYear(lastHistoric, lastHistoricOffset); 837 if (--year > lastHistoricYear) { 838 transArray = findTransitionArray(year); 839 return transArray[transArray.length - 1]; 840 } 841 // drop through 842 } 843 844 // using historic rules 845 int index = Arrays.binarySearch(savingsInstantTransitions, epochSec); 846 if (index < 0) { 847 index = -index - 1; 848 } 849 if (index <= 0) { 850 return null; 851 } 852 return new ZoneOffsetTransition(savingsInstantTransitions[index - 1], wallOffsets[index - 1], wallOffsets[index]); 853 } 854 855 private int findYear(long epochSecond, ZoneOffset offset) { 856 // inline for performance 857 long localSecond = epochSecond + offset.getTotalSeconds(); 858 long localEpochDay = Math.floorDiv(localSecond, 86400); 859 return LocalDate.ofEpochDay(localEpochDay).getYear(); 860 } 861 862 /** 863 * Gets the complete list of fully defined transitions. 864 * <p> 865 * The complete set of transitions for this rules instance is defined by this method 866 * and {@link #getTransitionRules()}. This method returns those transitions that have 867 * been fully defined. These are typically historical, but may be in the future. 868 * <p> 869 * The list will be empty for fixed offset rules and for any time-zone where there has 870 * only ever been a single offset. The list will also be empty if the transition rules are unknown. 871 * 872 * @return an immutable list of fully defined transitions, not null 873 */ 874 public List<ZoneOffsetTransition> getTransitions() { 875 List<ZoneOffsetTransition> list = new ArrayList<>(); 876 for (int i = 0; i < savingsInstantTransitions.length; i++) { 877 list.add(new ZoneOffsetTransition(savingsInstantTransitions[i], wallOffsets[i], wallOffsets[i + 1])); 878 } 879 return Collections.unmodifiableList(list); 880 } 881 882 /** 883 * Gets the list of transition rules for years beyond those defined in the transition list. 884 * <p> 885 * The complete set of transitions for this rules instance is defined by this method 886 * and {@link #getTransitions()}. This method returns instances of {@link ZoneOffsetTransitionRule} 887 * that define an algorithm for when transitions will occur. 888 * <p> 889 * For any given {@code ZoneRules}, this list contains the transition rules for years 890 * beyond those years that have been fully defined. These rules typically refer to future 891 * daylight saving time rule changes. 892 * <p> 893 * If the zone defines daylight savings into the future, then the list will normally 894 * be of size two and hold information about entering and exiting daylight savings. 895 * If the zone does not have daylight savings, or information about future changes 896 * is uncertain, then the list will be empty. 897 * <p> 898 * The list will be empty for fixed offset rules and for any time-zone where there is no 899 * daylight saving time. The list will also be empty if the transition rules are unknown. 900 * 901 * @return an immutable list of transition rules, not null 902 */ 903 public List<ZoneOffsetTransitionRule> getTransitionRules() { 904 return Collections.unmodifiableList(Arrays.asList(lastRules)); 905 } 906 907 /** 908 * Checks if this set of rules equals another. 909 * <p> 910 * Two rule sets are equal if they will always result in the same output 911 * for any given input instant or local date-time. 912 * Rules from two different groups may return false even if they are in fact the same. 913 * <p> 914 * This definition should result in implementations comparing their entire state. 915 * 916 * @param otherRules the other rules, null returns false 917 * @return true if this rules is the same as that specified 918 */ 919 @Override 920 public boolean equals(Object otherRules) { 921 if (this == otherRules) { 922 return true; 923 } 924 if (otherRules instanceof ZoneRules) { 925 ZoneRules other = (ZoneRules) otherRules; 926 return Arrays.equals(standardTransitions, other.standardTransitions) && 927 Arrays.equals(standardOffsets, other.standardOffsets) && 928 Arrays.equals(savingsInstantTransitions, other.savingsInstantTransitions) && 929 Arrays.equals(wallOffsets, other.wallOffsets) && 930 Arrays.equals(lastRules, other.lastRules); 931 } 932 return false; 933 } 934 935 /** 936 * Returns a suitable hash code given the definition of {@code #equals}. 937 * 938 * @return the hash code 939 */ 940 @Override 941 public int hashCode() { 942 return Arrays.hashCode(standardTransitions) ^ 943 Arrays.hashCode(standardOffsets) ^ 944 Arrays.hashCode(savingsInstantTransitions) ^ 945 Arrays.hashCode(wallOffsets) ^ 946 Arrays.hashCode(lastRules); 947 } 948 949 /** 950 * Returns a string describing this object. 951 * 952 * @return a string for debugging, not null 953 */ 954 @Override 955 public String toString() { 956 return "ZoneRules[currentStandardOffset=" + standardOffsets[standardOffsets.length - 1] + "]"; 957 } 958 959 }