1 /* 2 * Copyright (c) 2012, 2013, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 /* 27 * This file is available under and governed by the GNU General Public 28 * License version 2 only, as published by the Free Software Foundation. 29 * However, the following notice accompanied the original version of this 30 * file: 31 * 32 * Copyright (c) 2009-2012, Stephen Colebourne & Michael Nascimento Santos 33 * 34 * All rights reserved. 35 * 36 * Redistribution and use in source and binary forms, with or without 37 * modification, are permitted provided that the following conditions are met: 38 * 39 * * Redistributions of source code must retain the above copyright notice, 40 * this list of conditions and the following disclaimer. 41 * 42 * * Redistributions in binary form must reproduce the above copyright notice, 43 * this list of conditions and the following disclaimer in the documentation 44 * and/or other materials provided with the distribution. 45 * 46 * * Neither the name of JSR-310 nor the names of its contributors 47 * may be used to endorse or promote products derived from this software 48 * without specific prior written permission. 49 * 50 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 51 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 52 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 53 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR 54 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 55 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 56 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 57 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 58 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 59 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 60 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 61 */ 62 package build.tools.tzdb; 63 64 import java.util.Arrays; 65 import java.util.Collections; 66 import java.util.List; 67 import java.util.Objects; 68 69 /** 70 * A transition between two offsets caused by a discontinuity in the local time-line. 71 * 72 * @since 1.8 73 */ 74 final class ZoneOffsetTransition implements Comparable<ZoneOffsetTransition> { 75 76 /** 77 * The local transition date-time at the transition. 78 */ 79 private final LocalDateTime transition; 80 /** 81 * The offset before transition. 82 */ 83 private final ZoneOffset offsetBefore; 84 /** 85 * The offset after transition. 86 */ 87 private final ZoneOffset offsetAfter; 88 89 /** 90 * Creates an instance defining a transition between two offsets. 91 * 92 * @param transition the transition date-time with the offset before the transition, not null 93 * @param offsetBefore the offset before the transition, not null 94 * @param offsetAfter the offset at and after the transition, not null 95 */ 96 ZoneOffsetTransition(LocalDateTime transition, ZoneOffset offsetBefore, ZoneOffset offsetAfter) { 97 Objects.requireNonNull(transition, "transition"); 98 Objects.requireNonNull(offsetBefore, "offsetBefore"); 99 Objects.requireNonNull(offsetAfter, "offsetAfter"); 100 if (offsetBefore.equals(offsetAfter)) { 101 throw new IllegalArgumentException("Offsets must not be equal"); 102 } 103 this.transition = transition; 104 this.offsetBefore = offsetBefore; 105 this.offsetAfter = offsetAfter; 106 } 107 108 /** 109 * Creates an instance from epoch-second and offsets. 110 * 111 * @param epochSecond the transition epoch-second 112 * @param offsetBefore the offset before the transition, not null 113 * @param offsetAfter the offset at and after the transition, not null 114 */ 115 ZoneOffsetTransition(long epochSecond, ZoneOffset offsetBefore, ZoneOffset offsetAfter) { 116 this.transition = LocalDateTime.ofEpochSecond(epochSecond, 0, offsetBefore); 117 this.offsetBefore = offsetBefore; 118 this.offsetAfter = offsetAfter; 119 } 120 121 /** 122 * Gets the transition instant as an epoch second. 123 * 124 * @return the transition epoch second 125 */ 126 public long toEpochSecond() { 127 return transition.toEpochSecond(offsetBefore); 128 } 129 130 /** 131 * Gets the local transition date-time, as would be expressed with the 'before' offset. 132 * <p> 133 * This is the date-time where the discontinuity begins expressed with the 'before' offset. 134 * At this instant, the 'after' offset is actually used, therefore the combination of this 135 * date-time and the 'before' offset will never occur. 136 * <p> 137 * The combination of the 'before' date-time and offset represents the same instant 138 * as the 'after' date-time and offset. 139 * 140 * @return the transition date-time expressed with the before offset, not null 141 */ 142 public LocalDateTime getDateTimeBefore() { 143 return transition; 144 } 145 146 /** 147 * Gets the local transition date-time, as would be expressed with the 'after' offset. 148 * <p> 149 * This is the first date-time after the discontinuity, when the new offset applies. 150 * <p> 151 * The combination of the 'before' date-time and offset represents the same instant 152 * as the 'after' date-time and offset. 153 * 154 * @return the transition date-time expressed with the after offset, not null 155 */ 156 public LocalDateTime getDateTimeAfter() { 157 return transition.plusSeconds(getDurationSeconds()); 158 } 159 160 /** 161 * Gets the offset before the transition. 162 * <p> 163 * This is the offset in use before the instant of the transition. 164 * 165 * @return the offset before the transition, not null 166 */ 167 public ZoneOffset getOffsetBefore() { 168 return offsetBefore; 169 } 170 171 /** 172 * Gets the offset after the transition. 173 * <p> 174 * This is the offset in use on and after the instant of the transition. 175 * 176 * @return the offset after the transition, not null 177 */ 178 public ZoneOffset getOffsetAfter() { 179 return offsetAfter; 180 } 181 182 /** 183 * Gets the duration of the transition in seconds. 184 * 185 * @return the duration in seconds 186 */ 187 private int getDurationSeconds() { 188 return getOffsetAfter().getTotalSeconds() - getOffsetBefore().getTotalSeconds(); 189 } 190 191 /** 192 * Does this transition represent a gap in the local time-line. 193 * <p> 194 * Gaps occur where there are local date-times that simply do not not exist. 195 * An example would be when the offset changes from {@code +01:00} to {@code +02:00}. 196 * This might be described as 'the clocks will move forward one hour tonight at 1am'. 197 * 198 * @return true if this transition is a gap, false if it is an overlap 199 */ 200 public boolean isGap() { 201 return getOffsetAfter().getTotalSeconds() > getOffsetBefore().getTotalSeconds(); 202 } 203 204 /** 205 * Does this transition represent a gap in the local time-line. 206 * <p> 207 * Overlaps occur where there are local date-times that exist twice. 208 * An example would be when the offset changes from {@code +02:00} to {@code +01:00}. 209 * This might be described as 'the clocks will move back one hour tonight at 2am'. 210 * 211 * @return true if this transition is an overlap, false if it is a gap 212 */ 213 public boolean isOverlap() { 214 return getOffsetAfter().getTotalSeconds() < getOffsetBefore().getTotalSeconds(); 215 } 216 217 /** 218 * Checks if the specified offset is valid during this transition. 219 * <p> 220 * This checks to see if the given offset will be valid at some point in the transition. 221 * A gap will always return false. 222 * An overlap will return true if the offset is either the before or after offset. 223 * 224 * @param offset the offset to check, null returns false 225 * @return true if the offset is valid during the transition 226 */ 227 public boolean isValidOffset(ZoneOffset offset) { 228 return isGap() ? false : (getOffsetBefore().equals(offset) || getOffsetAfter().equals(offset)); 229 } 230 231 /** 232 * Gets the valid offsets during this transition. 233 * <p> 234 * A gap will return an empty list, while an overlap will return both offsets. 235 * 236 * @return the list of valid offsets 237 */ 238 List<ZoneOffset> getValidOffsets() { 239 if (isGap()) { 240 return Collections.emptyList(); 241 } 242 return Arrays.asList(getOffsetBefore(), getOffsetAfter()); 243 } 244 245 /** 246 * Compares this transition to another based on the transition instant. 247 * <p> 248 * This compares the instants of each transition. 249 * The offsets are ignored, making this order inconsistent with equals. 250 * 251 * @param transition the transition to compare to, not null 252 * @return the comparator value, negative if less, positive if greater 253 */ 254 @Override 255 public int compareTo(ZoneOffsetTransition transition) { 256 return Long.compare(this.toEpochSecond(), transition.toEpochSecond()); 257 } 258 259 /** 260 * Checks if this object equals another. 261 * <p> 262 * The entire state of the object is compared. 263 * 264 * @param other the other object to compare to, null returns false 265 * @return true if equal 266 */ 267 @Override 268 public boolean equals(Object other) { 269 if (other == this) { 270 return true; 271 } 272 if (other instanceof ZoneOffsetTransition) { 273 ZoneOffsetTransition d = (ZoneOffsetTransition) other; 274 return transition.equals(d.transition) && 275 offsetBefore.equals(d.offsetBefore) && offsetAfter.equals(d.offsetAfter); 276 } 277 return false; 278 } 279 280 /** 281 * Returns a suitable hash code. 282 * 283 * @return the hash code 284 */ 285 @Override 286 public int hashCode() { 287 return transition.hashCode() ^ offsetBefore.hashCode() ^ Integer.rotateLeft(offsetAfter.hashCode(), 16); 288 } 289 290 }