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 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 63 SparseArray<String> mLocalTimeCache = new SparseArray<String>(); 64 long mLocalTimeCacheReferenceTime = 0; 65 static private long mGmtDisplayNameUpdateTime; 66 static private SparseArray<CharSequence> mGmtDisplayNameCache = 67 new SparseArray<CharSequence>(); 68 69 public String getLocalTime(long referenceTime) { 70 recycledTime.timezone = TimeZone.getDefault().getID(); 71 recycledTime.set(referenceTime); 72 73 int currYearDay = recycledTime.year * 366 + recycledTime.yearDay; 74 75 recycledTime.timezone = mTzId; 76 recycledTime.set(referenceTime); 77 78 String localTimeStr = null; 79 80 int hourMinute = recycledTime.hour * 60 + 81 recycledTime.minute; 82 83 if (mLocalTimeCacheReferenceTime != referenceTime) { 84 mLocalTimeCacheReferenceTime = referenceTime; 85 mLocalTimeCache.clear(); 86 } else { 87 localTimeStr = mLocalTimeCache.get(hourMinute); 88 } 89 90 if (localTimeStr == null) { 91 String format = "%I:%M %p"; 92 if (currYearDay != (recycledTime.year * 366 + recycledTime.yearDay)) { 93 if (is24HourFormat) { 94 format = "%b %d %H:%M"; 95 } else { 96 format = "%b %d %I:%M %p"; 97 } 98 } else if (is24HourFormat) { 99 format = "%H:%M"; 100 } 101 102 // format = "%Y-%m-%d %H:%M"; 103 localTimeStr = recycledTime.format(format); 104 mLocalTimeCache.put(hourMinute, localTimeStr); 105 } 106 107 return localTimeStr; 108 } 109 110 public int getLocalHr(long referenceTime) { 111 recycledTime.timezone = mTzId; 112 recycledTime.set(referenceTime); 113 return recycledTime.hour; 114 } 115 116 public int getNowOffsetMillis() { 117 return mTz.getOffset(System.currentTimeMillis()); 118 } 119 120 /* 121 * The method is synchronized because there's one mSB, which is used by 122 * mFormatter, per instance. If there are multiple callers for 123 * getGmtDisplayName, the output may be mangled. 124 */ 125 public synchronized CharSequence getGmtDisplayName(Context context) { 126 // TODO Note: The local time is shown in current time (current GMT 127 // offset) which may be different from the time specified by 128 // mTimeMillis 129 130 final long nowMinute = System.currentTimeMillis() / DateUtils.MINUTE_IN_MILLIS; 131 final long now = nowMinute * DateUtils.MINUTE_IN_MILLIS; 132 final int gmtOffset = mTz.getOffset(now); 133 int cacheKey; 134 135 boolean hasFutureDST = mTz.useDaylightTime(); 136 if (hasFutureDST) { 137 cacheKey = (int) (gmtOffset + 36 * DateUtils.HOUR_IN_MILLIS); 138 } else { 139 cacheKey = (int) (gmtOffset - 36 * DateUtils.HOUR_IN_MILLIS); 140 } 141 142 CharSequence displayName = null; 143 if (mGmtDisplayNameUpdateTime != nowMinute) { 144 mGmtDisplayNameUpdateTime = nowMinute; 145 mGmtDisplayNameCache.clear(); 146 } else { 147 displayName = mGmtDisplayNameCache.get(cacheKey); 148 } 149 150 if (displayName == null) { 151 mSB.setLength(0); 152 int flags = DateUtils.FORMAT_ABBREV_ALL; 153 flags |= DateUtils.FORMAT_SHOW_TIME; 154 if (TimeZoneInfo.is24HourFormat) { 155 flags |= DateUtils.FORMAT_24HOUR; 156 } 157 158 // mFormatter writes to mSB 159 DateUtils.formatDateRange(context, mFormatter, now, now, flags, mTzId); 160 mSB.append(" "); 161 int gmtStart = mSB.length(); 162 TimeZonePickerUtils.appendGmtOffset(mSB, gmtOffset); 163 int gmtEnd = mSB.length(); 164 165 int symbolStart = 0; 166 int symbolEnd = 0; 167 if (hasFutureDST) { 168 mSB.append(' '); 169 symbolStart = mSB.length(); 170 mSB.append(TimeZonePickerUtils.getDstSymbol()); // Sun symbol 171 symbolEnd = mSB.length(); 172 } 173 174 // Set the gray colors. 175 Spannable spannableText = mSpannableFactory.newSpannable(mSB); 176 spannableText.setSpan(new ForegroundColorSpan(GMT_TEXT_COLOR), 177 gmtStart, gmtEnd, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); 178 179 if (hasFutureDST) { 180 spannableText.setSpan(new ForegroundColorSpan(DST_SYMBOL_COLOR), 181 symbolStart, symbolEnd, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); 182 } 183 displayName = spannableText; 184 mGmtDisplayNameCache.put(cacheKey, displayName); 185 } 186 return displayName; 187 } 188 189 public boolean hasSameRules(TimeZoneInfo tzi) { 190 return this.mTz.hasSameRules(tzi.mTz); 191 } 192 193 @Override 194 public String toString() { 195 StringBuilder sb = new StringBuilder(); 196 197 final String country = this.mCountry; 198 final TimeZone tz = this.mTz; 199 200 sb.append(mTzId); 201 sb.append(SEPARATOR); 202 sb.append(tz.getDisplayName(false /* daylightTime */, TimeZone.LONG)); 203 sb.append(SEPARATOR); 204 sb.append(tz.getDisplayName(false /* daylightTime */, TimeZone.SHORT)); 205 sb.append(SEPARATOR); 206 if (tz.useDaylightTime()) { 207 sb.append(tz.getDisplayName(true, TimeZone.LONG)); 208 sb.append(SEPARATOR); 209 sb.append(tz.getDisplayName(true, TimeZone.SHORT)); 210 } else { 211 sb.append(SEPARATOR); 212 } 213 sb.append(SEPARATOR); 214 sb.append(tz.getRawOffset() / 3600000f); 215 sb.append(SEPARATOR); 216 sb.append(tz.getDSTSavings() / 3600000f); 217 sb.append(SEPARATOR); 218 sb.append(country); 219 sb.append(SEPARATOR); 220 221 // 1-1-2013 noon GMT 222 sb.append(getLocalTime(1357041600000L)); 223 sb.append(SEPARATOR); 224 225 // 3-15-2013 noon GMT 226 sb.append(getLocalTime(1363348800000L)); 227 sb.append(SEPARATOR); 228 229 // 7-1-2013 noon GMT 230 sb.append(getLocalTime(1372680000000L)); 231 sb.append(SEPARATOR); 232 233 // 11-01-2013 noon GMT 234 sb.append(getLocalTime(1383307200000L)); 235 sb.append(SEPARATOR); 236 237 sb.append('\n'); 238 return sb.toString(); 239 } 240 241 private static String formatTime(DateFormat df, int s) { 242 long ms = s * 1000L; 243 return df.format(new Date(ms)); 244 } 245 246 /* 247 * Returns a negative integer if this instance is less than the other; a 248 * positive integer if this instance is greater than the other; 0 if this 249 * instance has the same order as the other. 250 */ 251 @Override 252 public int compareTo(TimeZoneInfo other) { 253 if (this.getNowOffsetMillis() != other.getNowOffsetMillis()) { 254 return (other.getNowOffsetMillis() < this.getNowOffsetMillis()) ? -1 : 1; 255 } 256 257 // By country 258 if (this.mCountry == null) { 259 if (other.mCountry != null) { 260 return 1; 261 } 262 } 263 264 if (other.mCountry == null) { 265 return -1; 266 } else { 267 int diff = this.mCountry.compareTo(other.mCountry); 268 269 if (diff != 0) { 270 return diff; 271 } 272 } 273 274 // Finally diff by display name 275 if (mDisplayName != null && other.mDisplayName != null) 276 return this.mDisplayName.compareTo(other.mDisplayName); 277 278 return this.mTz.getDisplayName(Locale.getDefault()).compareTo( 279 other.mTz.getDisplayName(Locale.getDefault())); 280 281 } 282 } 283