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