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