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