blob: 99507ae1a24d986edd98512f6ada22fb57a4e4ff [file] [log] [blame]
Elliott Hughesd40e63e2011-02-17 16:20:07 -08001/*
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
17import java.io.IOException;
18import java.util.Arrays;
19import java.util.Date;
20import java.util.TimeZone;
21
22/**
23 * Copied from ZoneInfo and ZoneInfoDB in dalvik.
24 * {@hide}
25 */
26public 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}