TimeZoneInfo.java revision 09a2165919cb9b67c95b7885357c78e20bf5d9fb
1/*
2 * Copyright (C) 2013 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 com.android.timezonepicker;
18
19import android.content.Context;
20import android.text.Spannable;
21import android.text.Spannable.Factory;
22import android.text.format.DateUtils;
23import android.text.format.Time;
24import android.text.style.ForegroundColorSpan;
25import android.util.Log;
26import android.util.SparseArray;
27
28import java.lang.reflect.Field;
29import java.text.DateFormat;
30import java.util.Arrays;
31import java.util.Date;
32import java.util.Formatter;
33import java.util.Locale;
34import java.util.TimeZone;
35
36public class TimeZoneInfo implements Comparable<TimeZoneInfo> {
37    private static final int DST_SYMBOL_COLOR = 0xFF606060;
38    private static final char SEPARATOR = ',';
39    private static final String TAG = null;
40    public static int NUM_OF_TRANSITIONS = 6;
41    public static long time = System.currentTimeMillis() / 1000;
42    public static boolean is24HourFormat;
43    private static final Factory mSpannableFactory = Spannable.Factory.getInstance();
44
45    TimeZone mTz;
46    public String mTzId;
47    int mRawoffset;
48    public int[] mTransitions; // may have trailing 0's.
49    public String mCountry;
50    public int groupId;
51    public String mDisplayName;
52    private Time recycledTime = new Time();
53    private static StringBuilder mSB = new StringBuilder(50);
54    private static Formatter mFormatter = new Formatter(mSB, Locale.getDefault());
55
56    public TimeZoneInfo(TimeZone tz, String country) {
57        mTz = tz;
58        mTzId = tz.getID();
59        mCountry = country;
60        mRawoffset = tz.getRawOffset();
61
62        try {
63            mTransitions = getTransitions(tz, time);
64        } catch (NoSuchFieldException ignored) {
65        } catch (IllegalAccessException ignored) {
66            ignored.printStackTrace();
67        }
68    }
69
70    SparseArray<String> mLocalTimeCache = new SparseArray<String>();
71    long mLocalTimeCacheReferenceTime = 0;
72    static private long mGmtDisplayNameUpdateTime;
73    static private SparseArray<CharSequence> mGmtDisplayNameCache =
74            new SparseArray<CharSequence>();
75
76    public String getLocalTime(long referenceTime) {
77        recycledTime.timezone = TimeZone.getDefault().getID();
78        recycledTime.set(referenceTime);
79
80        int currYearDay = recycledTime.year * 366 + recycledTime.yearDay;
81
82        recycledTime.timezone = mTzId;
83        recycledTime.set(referenceTime);
84
85        String localTimeStr = null;
86
87        int hourMinute = recycledTime.hour * 60 +
88                recycledTime.minute;
89
90        if (mLocalTimeCacheReferenceTime != referenceTime) {
91            mLocalTimeCacheReferenceTime = referenceTime;
92            mLocalTimeCache.clear();
93        } else {
94            localTimeStr = mLocalTimeCache.get(hourMinute);
95        }
96
97        if (localTimeStr == null) {
98            String format = "%I:%M %p";
99            if (currYearDay != (recycledTime.year * 366 + recycledTime.yearDay)) {
100                if (is24HourFormat) {
101                    format = "%b %d %H:%M";
102                } else {
103                    format = "%b %d %I:%M %p";
104                }
105            } else if (is24HourFormat) {
106                format = "%H:%M";
107            }
108
109            // format = "%Y-%m-%d %H:%M";
110            localTimeStr = recycledTime.format(format);
111            mLocalTimeCache.put(hourMinute, localTimeStr);
112        }
113
114        return localTimeStr;
115    }
116
117    public int getLocalHr(long referenceTime) {
118        recycledTime.timezone = mTzId;
119        recycledTime.set(referenceTime);
120        return recycledTime.hour;
121    }
122
123    public int getNowOffsetMillis() {
124        return mTz.getOffset(System.currentTimeMillis());
125    }
126
127    /*
128     * The method is synchronized because there's one mSB, which is used by
129     * mFormatter, per instance. If there are multiple callers for
130     * getGmtDisplayName, the output may be mangled.
131     */
132    public synchronized CharSequence getGmtDisplayName(Context context) {
133        // TODO Note: The local time is shown in current time (current GMT
134        // offset) which may be different from the time specified by
135        // mTimeMillis
136
137        final long nowMinute = System.currentTimeMillis() / DateUtils.MINUTE_IN_MILLIS;
138        final long now = nowMinute * DateUtils.MINUTE_IN_MILLIS;
139        final int gmtOffset = mTz.getOffset(now);
140        int cacheKey;
141
142        boolean hasFutureDST = mTz.useDaylightTime();
143        if (hasFutureDST) {
144            cacheKey = (int) (gmtOffset + 36 * DateUtils.HOUR_IN_MILLIS);
145        } else {
146            cacheKey = (int) (gmtOffset - 36 * DateUtils.HOUR_IN_MILLIS);
147        }
148
149        CharSequence displayName = null;
150        if (mGmtDisplayNameUpdateTime != nowMinute) {
151            mGmtDisplayNameUpdateTime = nowMinute;
152            mGmtDisplayNameCache.clear();
153        } else {
154            displayName = mGmtDisplayNameCache.get(cacheKey);
155        }
156
157        if (displayName == null) {
158            mSB.setLength(0);
159            int flags = DateUtils.FORMAT_ABBREV_ALL;
160            flags |= DateUtils.FORMAT_SHOW_TIME;
161            if (TimeZoneInfo.is24HourFormat) {
162                flags |= DateUtils.FORMAT_24HOUR;
163            }
164
165            // mFormatter writes to mSB
166            DateUtils.formatDateRange(context, mFormatter, now, now, flags, mTzId);
167            mSB.append(' ');
168            TimeZonePickerUtils.appendGmtOffset(mSB, gmtOffset);
169
170            if (hasFutureDST) {
171                mSB.append(' ');
172                mSB.append(TimeZonePickerUtils.getDstSymbol()); // Sun symbol
173
174                final int end = mSB.length();
175                final int start = end - 1;
176                Spannable spannableText = mSpannableFactory.newSpannable(mSB);
177                spannableText.setSpan(new ForegroundColorSpan(DST_SYMBOL_COLOR), start, end,
178                        Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
179                displayName = spannableText;
180            } else {
181                displayName = mSB.toString();
182            }
183            mGmtDisplayNameCache.put(cacheKey, displayName);
184        }
185        return displayName;
186    }
187
188    private static int[] getTransitions(TimeZone tz, long time)
189            throws IllegalAccessException, NoSuchFieldException {
190        Class<?> zoneInfoClass = tz.getClass();
191        Field mTransitionsField = zoneInfoClass.getDeclaredField("mTransitions");
192        mTransitionsField.setAccessible(true);
193        int[] objTransitions = (int[]) mTransitionsField.get(tz);
194        int[] transitions = null;
195        if (objTransitions.length != 0) {
196            transitions = new int[NUM_OF_TRANSITIONS];
197            int numOfTransitions = 0;
198            for (int i = 0; i < objTransitions.length; ++i) {
199                if (objTransitions[i] < time) {
200                    continue;
201                }
202                transitions[numOfTransitions++] = objTransitions[i];
203                if (numOfTransitions == NUM_OF_TRANSITIONS) {
204                    break;
205                }
206            }
207        }
208        return transitions;
209    }
210
211    public boolean hasSameRules(TimeZoneInfo tzi) {
212        // this.mTz.hasSameRules(tzi.mTz)
213
214        return this.mRawoffset == tzi.mRawoffset
215                && Arrays.equals(this.mTransitions, tzi.mTransitions);
216    }
217
218    @Override
219    public String toString() {
220        StringBuilder sb = new StringBuilder();
221
222        final String country = this.mCountry;
223        final TimeZone tz = this.mTz;
224
225        sb.append(mTzId);
226        sb.append(SEPARATOR);
227        sb.append(tz.getDisplayName(false /* daylightTime */, TimeZone.LONG));
228        sb.append(SEPARATOR);
229        sb.append(tz.getDisplayName(false /* daylightTime */, TimeZone.SHORT));
230        sb.append(SEPARATOR);
231        if (tz.useDaylightTime()) {
232            sb.append(tz.getDisplayName(true, TimeZone.LONG));
233            sb.append(SEPARATOR);
234            sb.append(tz.getDisplayName(true, TimeZone.SHORT));
235        } else {
236            sb.append(SEPARATOR);
237        }
238        sb.append(SEPARATOR);
239        sb.append(tz.getRawOffset() / 3600000f);
240        sb.append(SEPARATOR);
241        sb.append(tz.getDSTSavings() / 3600000f);
242        sb.append(SEPARATOR);
243        sb.append(country);
244        sb.append(SEPARATOR);
245
246        // 1-1-2013 noon GMT
247        sb.append(getLocalTime(1357041600000L));
248        sb.append(SEPARATOR);
249
250        // 3-15-2013 noon GMT
251        sb.append(getLocalTime(1363348800000L));
252        sb.append(SEPARATOR);
253
254        // 7-1-2013 noon GMT
255        sb.append(getLocalTime(1372680000000L));
256        sb.append(SEPARATOR);
257
258        // 11-01-2013 noon GMT
259        sb.append(getLocalTime(1383307200000L));
260        sb.append(SEPARATOR);
261
262        // if (this.mTransitions != null && this.mTransitions.length != 0) {
263        // sb.append('"');
264        // DateFormat df = new SimpleDateFormat("yyyy-MM-dd' 'HH:mm:ss Z",
265        // Locale.US);
266        // df.setTimeZone(tz);
267        // DateFormat weekdayFormat = new SimpleDateFormat("EEEE", Locale.US);
268        // weekdayFormat.setTimeZone(tz);
269        // Formatter f = new Formatter(sb);
270        // for (int i = 0; i < this.mTransitions.length; ++i) {
271        // if (this.mTransitions[i] < time) {
272        // continue;
273        // }
274        //
275        // String fromTime = formatTime(df, this.mTransitions[i] - 1);
276        // String toTime = formatTime(df, this.mTransitions[i]);
277        // f.format("%s -> %s (%d)", fromTime, toTime, this.mTransitions[i]);
278        //
279        // String weekday = weekdayFormat.format(new Date(1000L *
280        // this.mTransitions[i]));
281        // if (!weekday.equals("Sunday")) {
282        // f.format(" -- %s", weekday);
283        // }
284        // sb.append("##");
285        // }
286        // sb.append('"');
287        // }
288        // sb.append(SEPARATOR);
289        sb.append('\n');
290        return sb.toString();
291    }
292
293    private static String formatTime(DateFormat df, int s) {
294        long ms = s * 1000L;
295        return df.format(new Date(ms));
296    }
297
298    /*
299     * Returns a negative integer if this instance is less than the other; a
300     * positive integer if this instance is greater than the other; 0 if this
301     * instance has the same order as the other.
302     */
303    @Override
304    public int compareTo(TimeZoneInfo other) {
305        if (this.getNowOffsetMillis() != other.getNowOffsetMillis()) {
306            return (other.getNowOffsetMillis() < this.getNowOffsetMillis()) ? -1 : 1;
307        }
308
309        // By country
310        if (this.mCountry == null) {
311            if (other.mCountry != null) {
312                return 1;
313            }
314        }
315
316        if (other.mCountry == null) {
317            return -1;
318        } else {
319            int diff = this.mCountry.compareTo(other.mCountry);
320
321            if (diff != 0) {
322                return diff;
323            }
324        }
325
326        if (Arrays.equals(this.mTransitions, other.mTransitions)) {
327            Log.e(TAG, "Not expected to be comparing tz with the same country, same offset," +
328                    " same dst, same transitions:\n" + this.toString() + "\n" + other.toString());
329        }
330
331        // Finally diff by display name
332        if (mDisplayName != null && other.mDisplayName != null)
333            return this.mDisplayName.compareTo(other.mDisplayName);
334
335        return this.mTz.getDisplayName(Locale.getDefault()).compareTo(
336                other.mTz.getDisplayName(Locale.getDefault()));
337
338    }
339}
340