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