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