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
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}
273