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