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