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 * @see #isFixedOffset() 307 */ ZoneRules(ZoneOffset offset)308 private ZoneRules(ZoneOffset offset) { 309 this.standardOffsets = new ZoneOffset[1]; 310 this.standardOffsets[0] = offset; 311 this.standardTransitions = EMPTY_LONG_ARRAY; 312 this.savingsInstantTransitions = EMPTY_LONG_ARRAY; 313 this.savingsLocalTransitions = EMPTY_LDT_ARRAY; 314 this.wallOffsets = standardOffsets; 315 this.lastRules = EMPTY_LASTRULES; 316 } 317 318 /** 319 * Defend against malicious streams. 320 * 321 * @param s the stream to read 322 * @throws InvalidObjectException always 323 */ readObject(ObjectInputStream s)324 private void readObject(ObjectInputStream s) throws InvalidObjectException { 325 throw new InvalidObjectException("Deserialization via serialization delegate"); 326 } 327 328 /** 329 * Writes the object using a 330 * <a href="../../../serialized-form.html#java.time.zone.Ser">dedicated serialized form</a>. 331 * @serialData 332 * <pre style="font-size:1.0em">{@code 333 * 334 * out.writeByte(1); // identifies a ZoneRules 335 * out.writeInt(standardTransitions.length); 336 * for (long trans : standardTransitions) { 337 * Ser.writeEpochSec(trans, out); 338 * } 339 * for (ZoneOffset offset : standardOffsets) { 340 * Ser.writeOffset(offset, out); 341 * } 342 * out.writeInt(savingsInstantTransitions.length); 343 * for (long trans : savingsInstantTransitions) { 344 * Ser.writeEpochSec(trans, out); 345 * } 346 * for (ZoneOffset offset : wallOffsets) { 347 * Ser.writeOffset(offset, out); 348 * } 349 * out.writeByte(lastRules.length); 350 * for (ZoneOffsetTransitionRule rule : lastRules) { 351 * rule.writeExternal(out); 352 * } 353 * } 354 * </pre> 355 * <p> 356 * Epoch second values used for offsets are encoded in a variable 357 * length form to make the common cases put fewer bytes in the stream. 358 * <pre style="font-size:1.0em">{@code 359 * 360 * static void writeEpochSec(long epochSec, DataOutput out) throws IOException { 361 * if (epochSec >= -4575744000L && epochSec < 10413792000L && epochSec % 900 == 0) { // quarter hours between 1825 and 2300 362 * int store = (int) ((epochSec + 4575744000L) / 900); 363 * out.writeByte((store >>> 16) & 255); 364 * out.writeByte((store >>> 8) & 255); 365 * out.writeByte(store & 255); 366 * } else { 367 * out.writeByte(255); 368 * out.writeLong(epochSec); 369 * } 370 * } 371 * } 372 * </pre> 373 * <p> 374 * ZoneOffset values are encoded in a variable length form so the 375 * common cases put fewer bytes in the stream. 376 * <pre style="font-size:1.0em">{@code 377 * 378 * static void writeOffset(ZoneOffset offset, DataOutput out) throws IOException { 379 * final int offsetSecs = offset.getTotalSeconds(); 380 * int offsetByte = offsetSecs % 900 == 0 ? offsetSecs / 900 : 127; // compress to -72 to +72 381 * out.writeByte(offsetByte); 382 * if (offsetByte == 127) { 383 * out.writeInt(offsetSecs); 384 * } 385 * } 386 *} 387 * </pre> 388 * @return the replacing object, not null 389 */ writeReplace()390 private Object writeReplace() { 391 return new Ser(Ser.ZRULES, this); 392 } 393 394 /** 395 * Writes the state to the stream. 396 * 397 * @param out the output stream, not null 398 * @throws IOException if an error occurs 399 */ writeExternal(DataOutput out)400 void writeExternal(DataOutput out) throws IOException { 401 out.writeInt(standardTransitions.length); 402 for (long trans : standardTransitions) { 403 Ser.writeEpochSec(trans, out); 404 } 405 for (ZoneOffset offset : standardOffsets) { 406 Ser.writeOffset(offset, out); 407 } 408 out.writeInt(savingsInstantTransitions.length); 409 for (long trans : savingsInstantTransitions) { 410 Ser.writeEpochSec(trans, out); 411 } 412 for (ZoneOffset offset : wallOffsets) { 413 Ser.writeOffset(offset, out); 414 } 415 out.writeByte(lastRules.length); 416 for (ZoneOffsetTransitionRule rule : lastRules) { 417 rule.writeExternal(out); 418 } 419 } 420 421 /** 422 * Reads the state from the stream. The 1,024 limit to the lengths 423 * of stdTrans and savSize is intended to be the size well enough 424 * to accommodate the max number of transitions in current tzdb data 425 * (203 for Asia/Tehran). 426 * 427 * @param in the input stream, not null 428 * @return the created object, not null 429 * @throws IOException if an error occurs 430 */ readExternal(DataInput in)431 static ZoneRules readExternal(DataInput in) throws IOException, ClassNotFoundException { 432 int stdSize = in.readInt(); 433 if (stdSize > 1024) { 434 throw new InvalidObjectException("Too many transitions"); 435 } 436 long[] stdTrans = (stdSize == 0) ? EMPTY_LONG_ARRAY 437 : new long[stdSize]; 438 for (int i = 0; i < stdSize; i++) { 439 stdTrans[i] = Ser.readEpochSec(in); 440 } 441 ZoneOffset[] stdOffsets = new ZoneOffset[stdSize + 1]; 442 for (int i = 0; i < stdOffsets.length; i++) { 443 stdOffsets[i] = Ser.readOffset(in); 444 } 445 int savSize = in.readInt(); 446 if (savSize > 1024) { 447 throw new InvalidObjectException("Too many saving offsets"); 448 } 449 long[] savTrans = (savSize == 0) ? EMPTY_LONG_ARRAY 450 : new long[savSize]; 451 for (int i = 0; i < savSize; i++) { 452 savTrans[i] = Ser.readEpochSec(in); 453 } 454 ZoneOffset[] savOffsets = new ZoneOffset[savSize + 1]; 455 for (int i = 0; i < savOffsets.length; i++) { 456 savOffsets[i] = Ser.readOffset(in); 457 } 458 int ruleSize = in.readByte(); 459 if (ruleSize > 16) { 460 throw new InvalidObjectException("Too many transition rules"); 461 } 462 ZoneOffsetTransitionRule[] rules = (ruleSize == 0) ? 463 EMPTY_LASTRULES : new ZoneOffsetTransitionRule[ruleSize]; 464 for (int i = 0; i < ruleSize; i++) { 465 rules[i] = ZoneOffsetTransitionRule.readExternal(in); 466 } 467 return new ZoneRules(stdTrans, stdOffsets, savTrans, savOffsets, rules); 468 } 469 470 /** 471 * Checks of the zone rules are fixed, such that the offset never varies. 472 * 473 * @return true if the time-zone is fixed and the offset never changes 474 */ isFixedOffset()475 public boolean isFixedOffset() { 476 return savingsInstantTransitions.length == 0; 477 } 478 479 /** 480 * Gets the offset applicable at the specified instant in these rules. 481 * <p> 482 * The mapping from an instant to an offset is simple, there is only 483 * one valid offset for each instant. 484 * This method returns that offset. 485 * 486 * @param instant the instant to find the offset for, not null, but null 487 * may be ignored if the rules have a single offset for all instants 488 * @return the offset, not null 489 */ getOffset(Instant instant)490 public ZoneOffset getOffset(Instant instant) { 491 if (savingsInstantTransitions.length == 0) { 492 return standardOffsets[0]; 493 } 494 long epochSec = instant.getEpochSecond(); 495 // check if using last rules 496 if (lastRules.length > 0 && 497 epochSec > savingsInstantTransitions[savingsInstantTransitions.length - 1]) { 498 int year = findYear(epochSec, wallOffsets[wallOffsets.length - 1]); 499 ZoneOffsetTransition[] transArray = findTransitionArray(year); 500 ZoneOffsetTransition trans = null; 501 for (int i = 0; i < transArray.length; i++) { 502 trans = transArray[i]; 503 if (epochSec < trans.toEpochSecond()) { 504 return trans.getOffsetBefore(); 505 } 506 } 507 return trans.getOffsetAfter(); 508 } 509 510 // using historic rules 511 int index = Arrays.binarySearch(savingsInstantTransitions, epochSec); 512 if (index < 0) { 513 // switch negative insert position to start of matched range 514 index = -index - 2; 515 } 516 return wallOffsets[index + 1]; 517 } 518 519 /** 520 * Gets a suitable offset for the specified local date-time in these rules. 521 * <p> 522 * The mapping from a local date-time to an offset is not straightforward. 523 * There are three cases: 524 * <ul> 525 * <li>Normal, with one valid offset. For the vast majority of the year, the normal 526 * case applies, where there is a single valid offset for the local date-time.</li> 527 * <li>Gap, with zero valid offsets. This is when clocks jump forward typically 528 * due to the spring daylight savings change from "winter" to "summer". 529 * In a gap there are local date-time values with no valid offset.</li> 530 * <li>Overlap, with two valid offsets. This is when clocks are set back typically 531 * due to the autumn daylight savings change from "summer" to "winter". 532 * In an overlap there are local date-time values with two valid offsets.</li> 533 * </ul> 534 * Thus, for any given local date-time there can be zero, one or two valid offsets. 535 * This method returns the single offset in the Normal case, and in the Gap or Overlap 536 * case it returns the offset before the transition. 537 * <p> 538 * Since, in the case of Gap and Overlap, the offset returned is a "best" value, rather 539 * than the "correct" value, it should be treated with care. Applications that care 540 * about the correct offset should use a combination of this method, 541 * {@link #getValidOffsets(LocalDateTime)} and {@link #getTransition(LocalDateTime)}. 542 * 543 * @param localDateTime the local date-time to query, not null, but null 544 * may be ignored if the rules have a single offset for all instants 545 * @return the best available offset for the local date-time, not null 546 */ getOffset(LocalDateTime localDateTime)547 public ZoneOffset getOffset(LocalDateTime localDateTime) { 548 Object info = getOffsetInfo(localDateTime); 549 if (info instanceof ZoneOffsetTransition) { 550 return ((ZoneOffsetTransition) info).getOffsetBefore(); 551 } 552 return (ZoneOffset) info; 553 } 554 555 /** 556 * Gets the offset applicable at the specified local date-time in these rules. 557 * <p> 558 * The mapping from a local date-time to an offset is not straightforward. 559 * There are three cases: 560 * <ul> 561 * <li>Normal, with one valid offset. For the vast majority of the year, the normal 562 * case applies, where there is a single valid offset for the local date-time.</li> 563 * <li>Gap, with zero valid offsets. This is when clocks jump forward typically 564 * due to the spring daylight savings change from "winter" to "summer". 565 * In a gap there are local date-time values with no valid offset.</li> 566 * <li>Overlap, with two valid offsets. This is when clocks are set back typically 567 * due to the autumn daylight savings change from "summer" to "winter". 568 * In an overlap there are local date-time values with two valid offsets.</li> 569 * </ul> 570 * Thus, for any given local date-time there can be zero, one or two valid offsets. 571 * This method returns that list of valid offsets, which is a list of size 0, 1 or 2. 572 * In the case where there are two offsets, the earlier offset is returned at index 0 573 * and the later offset at index 1. 574 * <p> 575 * There are various ways to handle the conversion from a {@code LocalDateTime}. 576 * One technique, using this method, would be: 577 * <pre> 578 * List<ZoneOffset> validOffsets = rules.getOffset(localDT); 579 * if (validOffsets.size() == 1) { 580 * // Normal case: only one valid offset 581 * zoneOffset = validOffsets.get(0); 582 * } else { 583 * // Gap or Overlap: determine what to do from transition (which will be non-null) 584 * ZoneOffsetTransition trans = rules.getTransition(localDT); 585 * } 586 * </pre> 587 * <p> 588 * In theory, it is possible for there to be more than two valid offsets. 589 * This would happen if clocks to be put back more than once in quick succession. 590 * This has never happened in the history of time-zones and thus has no special handling. 591 * However, if it were to happen, then the list would return more than 2 entries. 592 * 593 * @param localDateTime the local date-time to query for valid offsets, not null, but null 594 * may be ignored if the rules have a single offset for all instants 595 * @return the list of valid offsets, may be immutable, not null 596 */ getValidOffsets(LocalDateTime localDateTime)597 public List<ZoneOffset> getValidOffsets(LocalDateTime localDateTime) { 598 // should probably be optimized 599 Object info = getOffsetInfo(localDateTime); 600 if (info instanceof ZoneOffsetTransition) { 601 return ((ZoneOffsetTransition) info).getValidOffsets(); 602 } 603 return Collections.singletonList((ZoneOffset) info); 604 } 605 606 /** 607 * Gets the offset transition applicable at the specified local date-time in these rules. 608 * <p> 609 * The mapping from a local date-time to an offset is not straightforward. 610 * There are three cases: 611 * <ul> 612 * <li>Normal, with one valid offset. For the vast majority of the year, the normal 613 * case applies, where there is a single valid offset for the local date-time.</li> 614 * <li>Gap, with zero valid offsets. This is when clocks jump forward typically 615 * due to the spring daylight savings change from "winter" to "summer". 616 * In a gap there are local date-time values with no valid offset.</li> 617 * <li>Overlap, with two valid offsets. This is when clocks are set back typically 618 * due to the autumn daylight savings change from "summer" to "winter". 619 * In an overlap there are local date-time values with two valid offsets.</li> 620 * </ul> 621 * A transition is used to model the cases of a Gap or Overlap. 622 * The Normal case will return null. 623 * <p> 624 * There are various ways to handle the conversion from a {@code LocalDateTime}. 625 * One technique, using this method, would be: 626 * <pre> 627 * ZoneOffsetTransition trans = rules.getTransition(localDT); 628 * if (trans != null) { 629 * // Gap or Overlap: determine what to do from transition 630 * } else { 631 * // Normal case: only one valid offset 632 * zoneOffset = rule.getOffset(localDT); 633 * } 634 * </pre> 635 * 636 * @param localDateTime the local date-time to query for offset transition, not null, but null 637 * may be ignored if the rules have a single offset for all instants 638 * @return the offset transition, null if the local date-time is not in transition 639 */ getTransition(LocalDateTime localDateTime)640 public ZoneOffsetTransition getTransition(LocalDateTime localDateTime) { 641 Object info = getOffsetInfo(localDateTime); 642 return (info instanceof ZoneOffsetTransition ? (ZoneOffsetTransition) info : null); 643 } 644 getOffsetInfo(LocalDateTime dt)645 private Object getOffsetInfo(LocalDateTime dt) { 646 if (savingsInstantTransitions.length == 0) { 647 return standardOffsets[0]; 648 } 649 // check if using last rules 650 if (lastRules.length > 0 && 651 dt.isAfter(savingsLocalTransitions[savingsLocalTransitions.length - 1])) { 652 ZoneOffsetTransition[] transArray = findTransitionArray(dt.getYear()); 653 Object info = null; 654 for (ZoneOffsetTransition trans : transArray) { 655 info = findOffsetInfo(dt, trans); 656 if (info instanceof ZoneOffsetTransition || info.equals(trans.getOffsetBefore())) { 657 return info; 658 } 659 } 660 return info; 661 } 662 663 // using historic rules 664 int index = Arrays.binarySearch(savingsLocalTransitions, dt); 665 if (index == -1) { 666 // before first transition 667 return wallOffsets[0]; 668 } 669 if (index < 0) { 670 // switch negative insert position to start of matched range 671 index = -index - 2; 672 } else if (index < savingsLocalTransitions.length - 1 && 673 savingsLocalTransitions[index].equals(savingsLocalTransitions[index + 1])) { 674 // handle overlap immediately following gap 675 index++; 676 } 677 if ((index & 1) == 0) { 678 // gap or overlap 679 LocalDateTime dtBefore = savingsLocalTransitions[index]; 680 LocalDateTime dtAfter = savingsLocalTransitions[index + 1]; 681 ZoneOffset offsetBefore = wallOffsets[index / 2]; 682 ZoneOffset offsetAfter = wallOffsets[index / 2 + 1]; 683 if (offsetAfter.getTotalSeconds() > offsetBefore.getTotalSeconds()) { 684 // gap 685 return new ZoneOffsetTransition(dtBefore, offsetBefore, offsetAfter); 686 } else { 687 // overlap 688 return new ZoneOffsetTransition(dtAfter, offsetBefore, offsetAfter); 689 } 690 } else { 691 // normal (neither gap or overlap) 692 return wallOffsets[index / 2 + 1]; 693 } 694 } 695 696 /** 697 * Finds the offset info for a local date-time and transition. 698 * 699 * @param dt the date-time, not null 700 * @param trans the transition, not null 701 * @return the offset info, not null 702 */ findOffsetInfo(LocalDateTime dt, ZoneOffsetTransition trans)703 private Object findOffsetInfo(LocalDateTime dt, ZoneOffsetTransition trans) { 704 LocalDateTime localTransition = trans.getDateTimeBefore(); 705 if (trans.isGap()) { 706 if (dt.isBefore(localTransition)) { 707 return trans.getOffsetBefore(); 708 } 709 if (dt.isBefore(trans.getDateTimeAfter())) { 710 return trans; 711 } else { 712 return trans.getOffsetAfter(); 713 } 714 } else { 715 if (dt.isBefore(localTransition) == false) { 716 return trans.getOffsetAfter(); 717 } 718 if (dt.isBefore(trans.getDateTimeAfter())) { 719 return trans.getOffsetBefore(); 720 } else { 721 return trans; 722 } 723 } 724 } 725 726 /** 727 * Finds the appropriate transition array for the given year. 728 * 729 * @param year the year, not null 730 * @return the transition array, not null 731 */ findTransitionArray(int year)732 private ZoneOffsetTransition[] findTransitionArray(int year) { 733 Integer yearObj = year; // should use Year class, but this saves a class load 734 ZoneOffsetTransition[] transArray = lastRulesCache.get(yearObj); 735 if (transArray != null) { 736 return transArray; 737 } 738 ZoneOffsetTransitionRule[] ruleArray = lastRules; 739 transArray = new ZoneOffsetTransition[ruleArray.length]; 740 for (int i = 0; i < ruleArray.length; i++) { 741 transArray[i] = ruleArray[i].createTransition(year); 742 } 743 if (year < LAST_CACHED_YEAR) { 744 lastRulesCache.putIfAbsent(yearObj, transArray); 745 } 746 return transArray; 747 } 748 749 /** 750 * Gets the standard offset for the specified instant in this zone. 751 * <p> 752 * This provides access to historic information on how the standard offset 753 * has changed over time. 754 * The standard offset is the offset before any daylight saving time is applied. 755 * This is typically the offset applicable during winter. 756 * 757 * @param instant the instant to find the offset information for, not null, but null 758 * may be ignored if the rules have a single offset for all instants 759 * @return the standard offset, not null 760 */ getStandardOffset(Instant instant)761 public ZoneOffset getStandardOffset(Instant instant) { 762 if (savingsInstantTransitions.length == 0) { 763 return standardOffsets[0]; 764 } 765 long epochSec = instant.getEpochSecond(); 766 int index = Arrays.binarySearch(standardTransitions, epochSec); 767 if (index < 0) { 768 // switch negative insert position to start of matched range 769 index = -index - 2; 770 } 771 return standardOffsets[index + 1]; 772 } 773 774 /** 775 * Gets the amount of daylight savings in use for the specified instant in this zone. 776 * <p> 777 * This provides access to historic information on how the amount of daylight 778 * savings has changed over time. 779 * This is the difference between the standard offset and the actual offset. 780 * Typically the amount is zero during winter and one hour during summer. 781 * Time-zones are second-based, so the nanosecond part of the duration will be zero. 782 * <p> 783 * This default implementation calculates the duration from the 784 * {@link #getOffset(java.time.Instant) actual} and 785 * {@link #getStandardOffset(java.time.Instant) standard} offsets. 786 * 787 * @param instant the instant to find the daylight savings for, not null, but null 788 * may be ignored if the rules have a single offset for all instants 789 * @return the difference between the standard and actual offset, not null 790 */ getDaylightSavings(Instant instant)791 public Duration getDaylightSavings(Instant instant) { 792 if (savingsInstantTransitions.length == 0) { 793 return Duration.ZERO; 794 } 795 ZoneOffset standardOffset = getStandardOffset(instant); 796 ZoneOffset actualOffset = getOffset(instant); 797 return Duration.ofSeconds(actualOffset.getTotalSeconds() - standardOffset.getTotalSeconds()); 798 } 799 800 /** 801 * Checks if the specified instant is in daylight savings. 802 * <p> 803 * This checks if the standard offset and the actual offset are the same 804 * for the specified instant. 805 * If they are not, it is assumed that daylight savings is in operation. 806 * <p> 807 * This default implementation compares the {@link #getOffset(java.time.Instant) actual} 808 * and {@link #getStandardOffset(java.time.Instant) standard} offsets. 809 * 810 * @param instant the instant to find the offset information for, not null, but null 811 * may be ignored if the rules have a single offset for all instants 812 * @return the standard offset, not null 813 */ isDaylightSavings(Instant instant)814 public boolean isDaylightSavings(Instant instant) { 815 return (getStandardOffset(instant).equals(getOffset(instant)) == false); 816 } 817 818 /** 819 * Checks if the offset date-time is valid for these rules. 820 * <p> 821 * To be valid, the local date-time must not be in a gap and the offset 822 * must match one of the valid offsets. 823 * <p> 824 * This default implementation checks if {@link #getValidOffsets(java.time.LocalDateTime)} 825 * contains the specified offset. 826 * 827 * @param localDateTime the date-time to check, not null, but null 828 * may be ignored if the rules have a single offset for all instants 829 * @param offset the offset to check, null returns false 830 * @return true if the offset date-time is valid for these rules 831 */ isValidOffset(LocalDateTime localDateTime, ZoneOffset offset)832 public boolean isValidOffset(LocalDateTime localDateTime, ZoneOffset offset) { 833 return getValidOffsets(localDateTime).contains(offset); 834 } 835 836 /** 837 * Gets the next transition after the specified instant. 838 * <p> 839 * This returns details of the next transition after the specified instant. 840 * For example, if the instant represents a point where "Summer" daylight savings time 841 * applies, then the method will return the transition to the next "Winter" time. 842 * 843 * @param instant the instant to get the next transition after, not null, but null 844 * may be ignored if the rules have a single offset for all instants 845 * @return the next transition after the specified instant, null if this is after the last transition 846 */ nextTransition(Instant instant)847 public ZoneOffsetTransition nextTransition(Instant instant) { 848 if (savingsInstantTransitions.length == 0) { 849 return null; 850 } 851 long epochSec = instant.getEpochSecond(); 852 // check if using last rules 853 if (epochSec >= savingsInstantTransitions[savingsInstantTransitions.length - 1]) { 854 if (lastRules.length == 0) { 855 return null; 856 } 857 // search year the instant is in 858 int year = findYear(epochSec, wallOffsets[wallOffsets.length - 1]); 859 ZoneOffsetTransition[] transArray = findTransitionArray(year); 860 for (ZoneOffsetTransition trans : transArray) { 861 if (epochSec < trans.toEpochSecond()) { 862 return trans; 863 } 864 } 865 // use first from following year 866 if (year < Year.MAX_VALUE) { 867 transArray = findTransitionArray(year + 1); 868 return transArray[0]; 869 } 870 return null; 871 } 872 873 // using historic rules 874 int index = Arrays.binarySearch(savingsInstantTransitions, epochSec); 875 if (index < 0) { 876 index = -index - 1; // switched value is the next transition 877 } else { 878 index += 1; // exact match, so need to add one to get the next 879 } 880 return new ZoneOffsetTransition(savingsInstantTransitions[index], wallOffsets[index], wallOffsets[index + 1]); 881 } 882 883 /** 884 * Gets the previous transition before the specified instant. 885 * <p> 886 * This returns details of the previous transition before the specified instant. 887 * For example, if the instant represents a point where "summer" daylight saving time 888 * applies, then the method will return the transition from the previous "winter" time. 889 * 890 * @param instant the instant to get the previous transition after, not null, but null 891 * may be ignored if the rules have a single offset for all instants 892 * @return the previous transition before the specified instant, null if this is before the first transition 893 */ previousTransition(Instant instant)894 public ZoneOffsetTransition previousTransition(Instant instant) { 895 if (savingsInstantTransitions.length == 0) { 896 return null; 897 } 898 long epochSec = instant.getEpochSecond(); 899 if (instant.getNano() > 0 && epochSec < Long.MAX_VALUE) { 900 epochSec += 1; // allow rest of method to only use seconds 901 } 902 903 // check if using last rules 904 long lastHistoric = savingsInstantTransitions[savingsInstantTransitions.length - 1]; 905 if (lastRules.length > 0 && epochSec > lastHistoric) { 906 // search year the instant is in 907 ZoneOffset lastHistoricOffset = wallOffsets[wallOffsets.length - 1]; 908 int year = findYear(epochSec, lastHistoricOffset); 909 ZoneOffsetTransition[] transArray = findTransitionArray(year); 910 for (int i = transArray.length - 1; i >= 0; i--) { 911 if (epochSec > transArray[i].toEpochSecond()) { 912 return transArray[i]; 913 } 914 } 915 // use last from preceding year 916 int lastHistoricYear = findYear(lastHistoric, lastHistoricOffset); 917 if (--year > lastHistoricYear) { 918 transArray = findTransitionArray(year); 919 return transArray[transArray.length - 1]; 920 } 921 // drop through 922 } 923 924 // using historic rules 925 int index = Arrays.binarySearch(savingsInstantTransitions, epochSec); 926 if (index < 0) { 927 index = -index - 1; 928 } 929 if (index <= 0) { 930 return null; 931 } 932 return new ZoneOffsetTransition(savingsInstantTransitions[index - 1], wallOffsets[index - 1], wallOffsets[index]); 933 } 934 findYear(long epochSecond, ZoneOffset offset)935 private int findYear(long epochSecond, ZoneOffset offset) { 936 // inline for performance 937 long localSecond = epochSecond + offset.getTotalSeconds(); 938 long localEpochDay = Math.floorDiv(localSecond, 86400); 939 return LocalDate.ofEpochDay(localEpochDay).getYear(); 940 } 941 942 /** 943 * Gets the complete list of fully defined transitions. 944 * <p> 945 * The complete set of transitions for this rules instance is defined by this method 946 * and {@link #getTransitionRules()}. This method returns those transitions that have 947 * been fully defined. These are typically historical, but may be in the future. 948 * <p> 949 * The list will be empty for fixed offset rules and for any time-zone where there has 950 * only ever been a single offset. The list will also be empty if the transition rules are unknown. 951 * 952 * @return an immutable list of fully defined transitions, not null 953 */ getTransitions()954 public List<ZoneOffsetTransition> getTransitions() { 955 List<ZoneOffsetTransition> list = new ArrayList<>(); 956 for (int i = 0; i < savingsInstantTransitions.length; i++) { 957 list.add(new ZoneOffsetTransition(savingsInstantTransitions[i], wallOffsets[i], wallOffsets[i + 1])); 958 } 959 return Collections.unmodifiableList(list); 960 } 961 962 /** 963 * Gets the list of transition rules for years beyond those defined in the transition list. 964 * <p> 965 * The complete set of transitions for this rules instance is defined by this method 966 * and {@link #getTransitions()}. This method returns instances of {@link ZoneOffsetTransitionRule} 967 * that define an algorithm for when transitions will occur. 968 * <p> 969 * For any given {@code ZoneRules}, this list contains the transition rules for years 970 * beyond those years that have been fully defined. These rules typically refer to future 971 * daylight saving time rule changes. 972 * <p> 973 * If the zone defines daylight savings into the future, then the list will normally 974 * be of size two and hold information about entering and exiting daylight savings. 975 * If the zone does not have daylight savings, or information about future changes 976 * is uncertain, then the list will be empty. 977 * <p> 978 * The list will be empty for fixed offset rules and for any time-zone where there is no 979 * daylight saving time. The list will also be empty if the transition rules are unknown. 980 * 981 * @return an immutable list of transition rules, not null 982 */ getTransitionRules()983 public List<ZoneOffsetTransitionRule> getTransitionRules() { 984 return List.of(lastRules); 985 } 986 987 /** 988 * Checks if this set of rules equals another. 989 * <p> 990 * Two rule sets are equal if they will always result in the same output 991 * for any given input instant or local date-time. 992 * Rules from two different groups may return false even if they are in fact the same. 993 * <p> 994 * This definition should result in implementations comparing their entire state. 995 * 996 * @param otherRules the other rules, null returns false 997 * @return true if this rules is the same as that specified 998 */ 999 @Override equals(Object otherRules)1000 public boolean equals(Object otherRules) { 1001 if (this == otherRules) { 1002 return true; 1003 } 1004 if (otherRules instanceof ZoneRules) { 1005 ZoneRules other = (ZoneRules) otherRules; 1006 return Arrays.equals(standardTransitions, other.standardTransitions) && 1007 Arrays.equals(standardOffsets, other.standardOffsets) && 1008 Arrays.equals(savingsInstantTransitions, other.savingsInstantTransitions) && 1009 Arrays.equals(wallOffsets, other.wallOffsets) && 1010 Arrays.equals(lastRules, other.lastRules); 1011 } 1012 return false; 1013 } 1014 1015 /** 1016 * Returns a suitable hash code given the definition of {@code #equals}. 1017 * 1018 * @return the hash code 1019 */ 1020 @Override hashCode()1021 public int hashCode() { 1022 return Arrays.hashCode(standardTransitions) ^ 1023 Arrays.hashCode(standardOffsets) ^ 1024 Arrays.hashCode(savingsInstantTransitions) ^ 1025 Arrays.hashCode(wallOffsets) ^ 1026 Arrays.hashCode(lastRules); 1027 } 1028 1029 /** 1030 * Returns a string describing this object. 1031 * 1032 * @return a string for debugging, not null 1033 */ 1034 @Override toString()1035 public String toString() { 1036 return "ZoneRules[currentStandardOffset=" + standardOffsets[standardOffsets.length - 1] + "]"; 1037 } 1038 1039 } 1040