1 /* 2 * Copyright (C) 2007 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 import java.io.IOException; 18 import java.util.Arrays; 19 import java.util.Date; 20 import java.util.TimeZone; 21 22 /** 23 * Copied from ZoneInfo and ZoneInfoDB in dalvik. 24 * {@hide} 25 */ 26 public class ZoneInfo extends TimeZone { 27 28 private static final long MILLISECONDS_PER_DAY = 24 * 60 * 60 * 1000; 29 private static final long MILLISECONDS_PER_400_YEARS = 30 MILLISECONDS_PER_DAY * (400 * 365 + 100 - 3); 31 32 private static final long UNIX_OFFSET = 62167219200000L; 33 34 private static final int[] NORMAL = new int[] { 35 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 36 }; 37 38 private static final int[] LEAP = new int[] { 39 0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, 40 }; 41 42 private static String nullName(byte[] data, int where, int off) { 43 if (off < 0) 44 return null; 45 46 int end = where + off; 47 while (end < data.length && data[end] != '\0') 48 end++; 49 50 return new String(data, where + off, end - (where + off)); 51 } 52 53 public static ZoneInfo make(String name, byte[] data) { 54 int ntransition = read4(data, 32); 55 int ngmtoff = read4(data, 36); 56 int base = 44; 57 58 int[] transitions = new int[ntransition]; 59 for (int i = 0; i < ntransition; i++) 60 transitions[i] = read4(data, base + 4 * i); 61 base += 4 * ntransition; 62 63 byte[] type = new byte[ntransition]; 64 for (int i = 0; i < ntransition; i++) 65 type[i] = data[base + i]; 66 base += ntransition; 67 68 int[] gmtoff = new int[ngmtoff]; 69 byte[] isdst = new byte[ngmtoff]; 70 byte[] abbrev = new byte[ngmtoff]; 71 for (int i = 0; i < ngmtoff; i++) { 72 gmtoff[i] = read4(data, base + 6 * i); 73 isdst[i] = data[base + 6 * i + 4]; 74 abbrev[i] = data[base + 6 * i + 5]; 75 } 76 77 base += 6 * ngmtoff; 78 79 return new ZoneInfo(name, transitions, type, gmtoff, isdst, abbrev, data, base); 80 } 81 82 private static int read4(byte[] data, int off) { 83 return ((data[off ] & 0xFF) << 24) | 84 ((data[off + 1] & 0xFF) << 16) | 85 ((data[off + 2] & 0xFF) << 8) | 86 ((data[off + 3] & 0xFF) << 0); 87 } 88 89 /*package*/ ZoneInfo(String name, int[] transitions, byte[] type, 90 int[] gmtoff, byte[] isdst, byte[] abbrev, 91 byte[] data, int abbrevoff) { 92 mTransitions = transitions; 93 mTypes = type; 94 mGmtOffs = gmtoff; 95 mIsDsts = isdst; 96 mUseDst = false; 97 setID(name); 98 99 // Find the latest GMT and non-GMT offsets for their abbreviations 100 101 int lastdst; 102 for (lastdst = mTransitions.length - 1; lastdst >= 0; lastdst--) { 103 if (mIsDsts[mTypes[lastdst] & 0xFF] != 0) 104 break; 105 } 106 107 int laststd; 108 for (laststd = mTransitions.length - 1; laststd >= 0; laststd--) { 109 if (mIsDsts[mTypes[laststd] & 0xFF] == 0) 110 break; 111 } 112 113 if (lastdst >= 0) { 114 mDaylightName = nullName(data, abbrevoff, 115 abbrev[mTypes[lastdst] & 0xFF]); 116 } 117 if (laststd >= 0) { 118 mStandardName = nullName(data, abbrevoff, 119 abbrev[mTypes[laststd] & 0xFF]); 120 } 121 122 // Use the latest non-DST offset if any as the raw offset 123 124 if (laststd < 0) { 125 laststd = 0; 126 } 127 128 if (laststd >= mTypes.length) { 129 mRawOffset = mGmtOffs[0]; 130 } else { 131 mRawOffset = mGmtOffs[mTypes[laststd] & 0xFF]; 132 } 133 134 // Subtract the raw offset from all offsets so it can be changed 135 // and affect them too. 136 // Find whether there exist any observances of DST. 137 138 for (int i = 0; i < mGmtOffs.length; i++) { 139 mGmtOffs[i] -= mRawOffset; 140 141 if (mIsDsts[i] != 0) { 142 mUseDst = true; 143 } 144 } 145 146 mRawOffset *= 1000; 147 } 148 149 @Override 150 public int getOffset(@SuppressWarnings("unused") int era, 151 int year, int month, int day, 152 @SuppressWarnings("unused") int dayOfWeek, 153 int millis) { 154 // XXX This assumes Gregorian always; Calendar switches from 155 // Julian to Gregorian in 1582. What calendar system are the 156 // arguments supposed to come from? 157 158 long calc = (year / 400) * MILLISECONDS_PER_400_YEARS; 159 year %= 400; 160 161 calc += year * (365 * MILLISECONDS_PER_DAY); 162 calc += ((year + 3) / 4) * MILLISECONDS_PER_DAY; 163 164 if (year > 0) 165 calc -= ((year - 1) / 100) * MILLISECONDS_PER_DAY; 166 167 boolean isLeap = (year == 0 || (year % 4 == 0 && year % 100 != 0)); 168 int[] mlen = isLeap ? LEAP : NORMAL; 169 170 calc += mlen[month] * MILLISECONDS_PER_DAY; 171 calc += (day - 1) * MILLISECONDS_PER_DAY; 172 calc += millis; 173 174 calc -= mRawOffset; 175 calc -= UNIX_OFFSET; 176 177 return getOffset(calc); 178 } 179 180 @Override 181 public int getOffset(long when) { 182 int unix = (int) (when / 1000); 183 int trans = Arrays.binarySearch(mTransitions, unix); 184 185 if (trans == ~0) { 186 return mGmtOffs[0] * 1000 + mRawOffset; 187 } 188 if (trans < 0) { 189 trans = ~trans - 1; 190 } 191 192 return mGmtOffs[mTypes[trans] & 0xFF] * 1000 + mRawOffset; 193 } 194 195 @Override 196 public int getRawOffset() { 197 return mRawOffset; 198 } 199 200 @Override 201 public void setRawOffset(int off) { 202 mRawOffset = off; 203 } 204 205 @Override 206 public boolean inDaylightTime(Date when) { 207 int unix = (int) (when.getTime() / 1000); 208 int trans = Arrays.binarySearch(mTransitions, unix); 209 210 if (trans == ~0) { 211 return mIsDsts[0] != 0; 212 } 213 if (trans < 0) { 214 trans = ~trans - 1; 215 } 216 217 return mIsDsts[mTypes[trans] & 0xFF] != 0; 218 } 219 220 @Override 221 public boolean useDaylightTime() { 222 return mUseDst; 223 } 224 225 private int mRawOffset; 226 private int[] mTransitions; 227 private int[] mGmtOffs; 228 private byte[] mTypes; 229 private byte[] mIsDsts; 230 private boolean mUseDst; 231 private String mDaylightName; 232 private String mStandardName; 233 234 @Override 235 public boolean equals(Object obj) { 236 if (this == obj) { 237 return true; 238 } 239 if (!(obj instanceof ZoneInfo)) { 240 return false; 241 } 242 ZoneInfo other = (ZoneInfo) obj; 243 return mUseDst == other.mUseDst 244 && (mDaylightName == null ? other.mDaylightName == null : 245 mDaylightName.equals(other.mDaylightName)) 246 && (mStandardName == null ? other.mStandardName == null : 247 mStandardName.equals(other.mStandardName)) 248 && mRawOffset == other.mRawOffset 249 // Arrays.equals returns true if both arrays are null 250 && Arrays.equals(mGmtOffs, other.mGmtOffs) 251 && Arrays.equals(mIsDsts, other.mIsDsts) 252 && Arrays.equals(mTypes, other.mTypes) 253 && Arrays.equals(mTransitions, other.mTransitions); 254 } 255 256 @Override 257 public int hashCode() { 258 final int prime = 31; 259 int result = 1; 260 result = prime * result + ((mDaylightName == null) ? 0 : 261 mDaylightName.hashCode()); 262 result = prime * result + Arrays.hashCode(mGmtOffs); 263 result = prime * result + Arrays.hashCode(mIsDsts); 264 result = prime * result + mRawOffset; 265 result = prime * result + ((mStandardName == null) ? 0 : 266 mStandardName.hashCode()); 267 result = prime * result + Arrays.hashCode(mTransitions); 268 result = prime * result + Arrays.hashCode(mTypes); 269 result = prime * result + (mUseDst ? 1231 : 1237); 270 return result; 271 } 272 } 273