ZoneRules.java revision 8845:4be14673b9bf
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) 2011-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 */ 62package build.tools.tzdb; 63 64import java.io.DataOutput; 65import java.io.IOException; 66import java.io.ObjectOutput; 67import java.util.Arrays; 68import java.util.ArrayList; 69import java.util.List; 70 71/** 72 * Duplicated code of javax.time.zone.ZoneRules, ZoneOffsetTransitionRule 73 * and Ser to generate the serialization form output of ZoneRules for 74 * tzdb.jar. 75 * 76 * Implementation here is the copy/paste of ZoneRules, ZoneOffsetTransitionRule 77 * and Ser in javax.time.zone package. Make sure the code here is synchrionozed 78 * with the serialization implementation there. 79 * 80 * @since 1.8 81 */ 82 83final class ZoneRules { 84 85 /** 86 * The transitions between standard offsets (epoch seconds), sorted. 87 */ 88 private final long[] standardTransitions; 89 /** 90 * The standard offsets. 91 */ 92 private final ZoneOffset[] standardOffsets; 93 /** 94 * The transitions between instants (epoch seconds), sorted. 95 */ 96 private final long[] savingsInstantTransitions; 97 98 /** 99 * The wall offsets. 100 */ 101 private final ZoneOffset[] wallOffsets; 102 /** 103 * The last rule. 104 */ 105 private final ZoneOffsetTransitionRule[] lastRules; 106 107 /** 108 * Creates an instance. 109 * 110 * @param baseStandardOffset the standard offset to use before legal rules were set, not null 111 * @param baseWallOffset the wall offset to use before legal rules were set, not null 112 * @param standardOffsetTransitionList the list of changes to the standard offset, not null 113 * @param transitionList the list of transitions, not null 114 * @param lastRules the recurring last rules, size 16 or less, not null 115 */ 116 ZoneRules(ZoneOffset baseStandardOffset, 117 ZoneOffset baseWallOffset, 118 List<ZoneOffsetTransition> standardOffsetTransitionList, 119 List<ZoneOffsetTransition> transitionList, 120 List<ZoneOffsetTransitionRule> lastRules) { 121 122 this.standardTransitions = new long[standardOffsetTransitionList.size()]; 123 124 this.standardOffsets = new ZoneOffset[standardOffsetTransitionList.size() + 1]; 125 this.standardOffsets[0] = baseStandardOffset; 126 for (int i = 0; i < standardOffsetTransitionList.size(); i++) { 127 this.standardTransitions[i] = standardOffsetTransitionList.get(i).toEpochSecond(); 128 this.standardOffsets[i + 1] = standardOffsetTransitionList.get(i).getOffsetAfter(); 129 } 130 131 // convert savings transitions to locals 132 List<ZoneOffset> localTransitionOffsetList = new ArrayList<>(); 133 localTransitionOffsetList.add(baseWallOffset); 134 for (ZoneOffsetTransition trans : transitionList) { 135 localTransitionOffsetList.add(trans.getOffsetAfter()); 136 } 137 138 this.wallOffsets = localTransitionOffsetList.toArray(new ZoneOffset[localTransitionOffsetList.size()]); 139 140 // convert savings transitions to instants 141 this.savingsInstantTransitions = new long[transitionList.size()]; 142 for (int i = 0; i < transitionList.size(); i++) { 143 this.savingsInstantTransitions[i] = transitionList.get(i).toEpochSecond(); 144 } 145 146 // last rules 147 if (lastRules.size() > 16) { 148 throw new IllegalArgumentException("Too many transition rules"); 149 } 150 this.lastRules = lastRules.toArray(new ZoneOffsetTransitionRule[lastRules.size()]); 151 } 152 153 /** Type for ZoneRules. */ 154 static final byte ZRULES = 1; 155 156 /** 157 * Writes the state to the stream. 158 * 159 * @param out the output stream, not null 160 * @throws IOException if an error occurs 161 */ 162 void writeExternal(DataOutput out) throws IOException { 163 out.writeByte(ZRULES); 164 out.writeInt(standardTransitions.length); 165 for (long trans : standardTransitions) { 166 writeEpochSec(trans, out); 167 } 168 for (ZoneOffset offset : standardOffsets) { 169 writeOffset(offset, out); 170 } 171 out.writeInt(savingsInstantTransitions.length); 172 for (long trans : savingsInstantTransitions) { 173 writeEpochSec(trans, out); 174 } 175 for (ZoneOffset offset : wallOffsets) { 176 writeOffset(offset, out); 177 } 178 out.writeByte(lastRules.length); 179 for (ZoneOffsetTransitionRule rule : lastRules) { 180 writeRule(rule, out); 181 } 182 } 183 184 /** 185 * Writes the state the ZoneOffset to the stream. 186 * 187 * @param offset the offset, not null 188 * @param out the output stream, not null 189 * @throws IOException if an error occurs 190 */ 191 static void writeOffset(ZoneOffset offset, DataOutput out) throws IOException { 192 final int offsetSecs = offset.getTotalSeconds(); 193 int offsetByte = offsetSecs % 900 == 0 ? offsetSecs / 900 : 127; // compress to -72 to +72 194 out.writeByte(offsetByte); 195 if (offsetByte == 127) { 196 out.writeInt(offsetSecs); 197 } 198 } 199 200 /** 201 * Writes the epoch seconds to the stream. 202 * 203 * @param epochSec the epoch seconds, not null 204 * @param out the output stream, not null 205 * @throws IOException if an error occurs 206 */ 207 static void writeEpochSec(long epochSec, DataOutput out) throws IOException { 208 if (epochSec >= -4575744000L && epochSec < 10413792000L && epochSec % 900 == 0) { // quarter hours between 1825 and 2300 209 int store = (int) ((epochSec + 4575744000L) / 900); 210 out.writeByte((store >>> 16) & 255); 211 out.writeByte((store >>> 8) & 255); 212 out.writeByte(store & 255); 213 } else { 214 out.writeByte(255); 215 out.writeLong(epochSec); 216 } 217 } 218 219 /** 220 * Writes the state of the transition rule to the stream. 221 * 222 * @param rule the transition rule, not null 223 * @param out the output stream, not null 224 * @throws IOException if an error occurs 225 */ 226 static void writeRule(ZoneOffsetTransitionRule rule, DataOutput out) throws IOException { 227 int month = rule.month; 228 byte dom = rule.dom; 229 int dow = rule.dow; 230 LocalTime time = rule.time; 231 boolean timeEndOfDay = rule.timeEndOfDay; 232 TimeDefinition timeDefinition = rule.timeDefinition; 233 ZoneOffset standardOffset = rule.standardOffset; 234 ZoneOffset offsetBefore = rule.offsetBefore; 235 ZoneOffset offsetAfter = rule.offsetAfter; 236 237 int timeSecs = (timeEndOfDay ? 86400 : time.toSecondOfDay()); 238 int stdOffset = standardOffset.getTotalSeconds(); 239 int beforeDiff = offsetBefore.getTotalSeconds() - stdOffset; 240 int afterDiff = offsetAfter.getTotalSeconds() - stdOffset; 241 int timeByte = (timeSecs % 3600 == 0 ? (timeEndOfDay ? 24 : time.getHour()) : 31); 242 int stdOffsetByte = (stdOffset % 900 == 0 ? stdOffset / 900 + 128 : 255); 243 int beforeByte = (beforeDiff == 0 || beforeDiff == 1800 || beforeDiff == 3600 ? beforeDiff / 1800 : 3); 244 int afterByte = (afterDiff == 0 || afterDiff == 1800 || afterDiff == 3600 ? afterDiff / 1800 : 3); 245 int dowByte = (dow == -1 ? 0 : dow); 246 int b = (month << 28) + // 4 bytes 247 ((dom + 32) << 22) + // 6 bytes 248 (dowByte << 19) + // 3 bytes 249 (timeByte << 14) + // 5 bytes 250 (timeDefinition.ordinal() << 12) + // 2 bytes 251 (stdOffsetByte << 4) + // 8 bytes 252 (beforeByte << 2) + // 2 bytes 253 afterByte; // 2 bytes 254 out.writeInt(b); 255 if (timeByte == 31) { 256 out.writeInt(timeSecs); 257 } 258 if (stdOffsetByte == 255) { 259 out.writeInt(stdOffset); 260 } 261 if (beforeByte == 3) { 262 out.writeInt(offsetBefore.getTotalSeconds()); 263 } 264 if (afterByte == 3) { 265 out.writeInt(offsetAfter.getTotalSeconds()); 266 } 267 } 268 269 /** 270 * Checks if this set of rules equals another. 271 * <p> 272 * Two rule sets are equal if they will always result in the same output 273 * for any given input instant or local date-time. 274 * Rules from two different groups may return false even if they are in fact the same. 275 * <p> 276 * This definition should result in implementations comparing their entire state. 277 * 278 * @param otherRules the other rules, null returns false 279 * @return true if this rules is the same as that specified 280 */ 281 @Override 282 public boolean equals(Object otherRules) { 283 if (this == otherRules) { 284 return true; 285 } 286 if (otherRules instanceof ZoneRules) { 287 ZoneRules other = (ZoneRules) otherRules; 288 return Arrays.equals(standardTransitions, other.standardTransitions) && 289 Arrays.equals(standardOffsets, other.standardOffsets) && 290 Arrays.equals(savingsInstantTransitions, other.savingsInstantTransitions) && 291 Arrays.equals(wallOffsets, other.wallOffsets) && 292 Arrays.equals(lastRules, other.lastRules); 293 } 294 return false; 295 } 296 297 /** 298 * Returns a suitable hash code given the definition of {@code #equals}. 299 * 300 * @return the hash code 301 */ 302 @Override 303 public int hashCode() { 304 return Arrays.hashCode(standardTransitions) ^ 305 Arrays.hashCode(standardOffsets) ^ 306 Arrays.hashCode(savingsInstantTransitions) ^ 307 Arrays.hashCode(wallOffsets) ^ 308 Arrays.hashCode(lastRules); 309 } 310 311} 312