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