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 17package org.apache.harmony.luni.internal.util; 18 19import java.io.IOException; 20import java.util.Arrays; 21import java.util.Date; 22import java.util.TimeZone; 23 24/** 25 * {@hide} 26 */ 27public class ZoneInfo extends TimeZone { 28 29 private static final long MILLISECONDS_PER_DAY = 24 * 60 * 60 * 1000; 30 private static final long MILLISECONDS_PER_400_YEARS = 31 MILLISECONDS_PER_DAY * (400 * 365 + 100 - 3); 32 33 private static final long UNIX_OFFSET = 62167219200000L; 34 35 private static final int[] NORMAL = new int[] { 36 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 37 }; 38 39 private static final int[] LEAP = new int[] { 40 0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, 41 }; 42 43 public static TimeZone getTimeZone(String name) { 44 if (name == null) 45 { 46 return null; 47 } 48 49 try { 50 return ZoneInfoDB._getTimeZone(name); 51 } catch (IOException e) { 52 return null; 53 } 54 } 55 56 private static String nullName(byte[] data, int where, int off) { 57 if (off < 0) 58 return null; 59 60 int end = where + off; 61 while (end < data.length && data[end] != '\0') 62 end++; 63 64 return new String(data, where + off, end - (where + off)); 65 } 66 67 /*package*/ ZoneInfo(String name, int[] transitions, byte[] type, 68 int[] gmtoff, byte[] isdst, byte[] abbrev, 69 byte[] data, int abbrevoff) { 70 mTransitions = transitions; 71 mTypes = type; 72 mGmtOffs = gmtoff; 73 mIsDsts = isdst; 74 mUseDst = false; 75 setID(name); 76 77 // Find the latest GMT and non-GMT offsets for their abbreviations 78 79 int lastdst; 80 for (lastdst = mTransitions.length - 1; lastdst >= 0; lastdst--) { 81 if (mIsDsts[mTypes[lastdst] & 0xFF] != 0) 82 break; 83 } 84 85 int laststd; 86 for (laststd = mTransitions.length - 1; laststd >= 0; laststd--) { 87 if (mIsDsts[mTypes[laststd] & 0xFF] == 0) 88 break; 89 } 90 91 if (lastdst >= 0) { 92 mDaylightName = nullName(data, abbrevoff, 93 abbrev[mTypes[lastdst] & 0xFF]); 94 } 95 if (laststd >= 0) { 96 mStandardName = nullName(data, abbrevoff, 97 abbrev[mTypes[laststd] & 0xFF]); 98 } 99 100 // Use the latest non-DST offset if any as the raw offset 101 102 if (laststd < 0) { 103 laststd = 0; 104 } 105 106 if (laststd >= mTypes.length) { 107 mRawOffset = mGmtOffs[0]; 108 } else { 109 mRawOffset = mGmtOffs[mTypes[laststd] & 0xFF]; 110 } 111 112 // Subtract the raw offset from all offsets so it can be changed 113 // and affect them too. 114 for (int i = 0; i < mGmtOffs.length; i++) { 115 mGmtOffs[i] -= mRawOffset; 116 } 117 118 // Is this zone still observing DST? 119 // We don't care if they've historically used it: most places have at least once. 120 // We want to know whether the last "schedule info" (the unix times in the mTransitions 121 // array) is in the future. If it is, DST is still relevant. 122 // See http://code.google.com/p/android/issues/detail?id=877. 123 // This test means that for somewhere like Morocco, which tried DST in 2009 but has 124 // no future plans (and thus no future schedule info) will report "true" from 125 // useDaylightTime at the start of 2009 but "false" at the end. This seems appropriate. 126 long currentUnixTime = System.currentTimeMillis() / 1000; 127 if (mTransitions.length > 0) { 128 // (We're really dealing with uint32_t values, so long is most convenient in Java.) 129 long latestScheduleTime = mTransitions[mTransitions.length - 1] & 0xffffffff; 130 if (currentUnixTime < latestScheduleTime) { 131 mUseDst = true; 132 } 133 } 134 135 mRawOffset *= 1000; 136 } 137 138 @Override 139 public int getOffset(@SuppressWarnings("unused") int era, 140 int year, int month, int day, 141 @SuppressWarnings("unused") int dayOfWeek, 142 int millis) { 143 // XXX This assumes Gregorian always; Calendar switches from 144 // Julian to Gregorian in 1582. What calendar system are the 145 // arguments supposed to come from? 146 147 long calc = (year / 400) * MILLISECONDS_PER_400_YEARS; 148 year %= 400; 149 150 calc += year * (365 * MILLISECONDS_PER_DAY); 151 calc += ((year + 3) / 4) * MILLISECONDS_PER_DAY; 152 153 if (year > 0) 154 calc -= ((year - 1) / 100) * MILLISECONDS_PER_DAY; 155 156 boolean isLeap = (year == 0 || (year % 4 == 0 && year % 100 != 0)); 157 int[] mlen = isLeap ? LEAP : NORMAL; 158 159 calc += mlen[month] * MILLISECONDS_PER_DAY; 160 calc += (day - 1) * MILLISECONDS_PER_DAY; 161 calc += millis; 162 163 calc -= mRawOffset; 164 calc -= UNIX_OFFSET; 165 166 return getOffset(calc); 167 } 168 169 @Override 170 public int getOffset(long when) { 171 int unix = (int) (when / 1000); 172 int trans = Arrays.binarySearch(mTransitions, unix); 173 174 if (trans == ~0) { 175 return mGmtOffs[0] * 1000 + mRawOffset; 176 } 177 if (trans < 0) { 178 trans = ~trans - 1; 179 } 180 181 return mGmtOffs[mTypes[trans] & 0xFF] * 1000 + mRawOffset; 182 } 183 184 @Override 185 public int getRawOffset() { 186 return mRawOffset; 187 } 188 189 @Override 190 public void setRawOffset(int off) { 191 mRawOffset = off; 192 } 193 194 @Override 195 public boolean inDaylightTime(Date when) { 196 int unix = (int) (when.getTime() / 1000); 197 int trans = Arrays.binarySearch(mTransitions, unix); 198 199 if (trans == ~0) { 200 return mIsDsts[0] != 0; 201 } 202 if (trans < 0) { 203 trans = ~trans - 1; 204 } 205 206 return mIsDsts[mTypes[trans] & 0xFF] != 0; 207 } 208 209 @Override 210 public boolean useDaylightTime() { 211 return mUseDst; 212 } 213 214 private int mRawOffset; 215 private int[] mTransitions; 216 private int[] mGmtOffs; 217 private byte[] mTypes; 218 private byte[] mIsDsts; 219 private boolean mUseDst; 220 private String mDaylightName; 221 private String mStandardName; 222 223 @Override 224 public boolean equals(Object obj) { 225 if (this == obj) { 226 return true; 227 } 228 if (!(obj instanceof ZoneInfo)) { 229 return false; 230 } 231 ZoneInfo other = (ZoneInfo) obj; 232 return mUseDst == other.mUseDst 233 && (mDaylightName == null ? other.mDaylightName == null : 234 mDaylightName.equals(other.mDaylightName)) 235 && (mStandardName == null ? other.mStandardName == null : 236 mStandardName.equals(other.mStandardName)) 237 && mRawOffset == other.mRawOffset 238 // Arrays.equals returns true if both arrays are null 239 && Arrays.equals(mGmtOffs, other.mGmtOffs) 240 && Arrays.equals(mIsDsts, other.mIsDsts) 241 && Arrays.equals(mTypes, other.mTypes) 242 && Arrays.equals(mTransitions, other.mTransitions); 243 } 244 245 @Override 246 public int hashCode() { 247 final int prime = 31; 248 int result = 1; 249 result = prime * result + ((mDaylightName == null) ? 0 : 250 mDaylightName.hashCode()); 251 result = prime * result + Arrays.hashCode(mGmtOffs); 252 result = prime * result + Arrays.hashCode(mIsDsts); 253 result = prime * result + mRawOffset; 254 result = prime * result + ((mStandardName == null) ? 0 : 255 mStandardName.hashCode()); 256 result = prime * result + Arrays.hashCode(mTransitions); 257 result = prime * result + Arrays.hashCode(mTypes); 258 result = prime * result + (mUseDst ? 1231 : 1237); 259 return result; 260 } 261 262 @Override 263 public String toString() { 264 return getClass().getName() + 265 "[\"" + mStandardName + "\",mRawOffset=" + mRawOffset + ",mUseDst=" + mUseDst + "]"; 266 } 267} 268