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.content.res.Resources; 21 import android.os.Build; 22 import android.text.Spannable; 23 import android.text.Spannable.Factory; 24 import android.text.format.DateUtils; 25 import android.text.format.Time; 26 import android.text.style.ForegroundColorSpan; 27 import android.util.Log; 28 29 import java.util.Locale; 30 import java.util.TimeZone; 31 32 public class TimeZonePickerUtils { 33 private static final String TAG = "TimeZonePickerUtils"; 34 35 public static final int GMT_TEXT_COLOR = 0xFF888888; 36 public static final int DST_SYMBOL_COLOR = 0xFFBFBFBF; 37 private static final Factory mSpannableFactory = Spannable.Factory.getInstance(); 38 39 private Locale mDefaultLocale; 40 private String[] mOverrideIds; 41 private String[] mOverrideLabels; 42 43 /** 44 * This needs to be an instantiated class so that it doesn't need to continuously re-load the 45 * list of timezone IDs that need to be overridden. 46 * @param context 47 */ TimeZonePickerUtils(Context context)48 public TimeZonePickerUtils(Context context) { 49 // Instead of saving a reference to the context (because we might need to look up the 50 // labels every time getGmtDisplayName is called), we'll cache the lists of override IDs 51 // and labels now. 52 cacheOverrides(context); 53 } 54 55 /** 56 * Given a timezone id (e.g. America/Los_Angeles), returns the corresponding timezone 57 * display name (e.g. Pacific Time GMT-7). 58 * 59 * @param context Context in case the override labels need to be re-cached. 60 * @param id The timezone id 61 * @param millis The time (daylight savings or not) 62 * @param grayGmt Whether the "GMT+x" part of the returned string should be colored gray. 63 * @return The display name of the timezone. 64 */ getGmtDisplayName(Context context, String id, long millis, boolean grayGmt)65 public CharSequence getGmtDisplayName(Context context, String id, long millis, 66 boolean grayGmt) { 67 TimeZone timezone = TimeZone.getTimeZone(id); 68 if (timezone == null) { 69 return null; 70 } 71 72 final Locale defaultLocale = Locale.getDefault(); 73 if (!defaultLocale.equals(mDefaultLocale)) { 74 // If the IDs and labels haven't been set yet, or if the locale has been changed 75 // recently, we'll need to re-cache them. 76 mDefaultLocale = defaultLocale; 77 cacheOverrides(context); 78 } 79 return buildGmtDisplayName(timezone, millis, grayGmt); 80 } 81 buildGmtDisplayName(TimeZone tz, long timeMillis, boolean grayGmt)82 private CharSequence buildGmtDisplayName(TimeZone tz, long timeMillis, boolean grayGmt) { 83 Time time = new Time(tz.getID()); 84 time.set(timeMillis); 85 86 StringBuilder sb = new StringBuilder(); 87 88 String displayName = getDisplayName(tz, time.isDst != 0); 89 sb.append(displayName); 90 91 sb.append(" "); 92 final int gmtOffset = tz.getOffset(timeMillis); 93 int gmtStart = sb.length(); 94 appendGmtOffset(sb, gmtOffset); 95 int gmtEnd = sb.length(); 96 97 int symbolStart = 0; 98 int symbolEnd = 0; 99 if (tz.useDaylightTime()) { 100 sb.append(" "); 101 symbolStart = sb.length(); 102 sb.append(getDstSymbol()); // Sun symbol 103 symbolEnd = sb.length(); 104 } 105 106 // Set the gray colors. 107 Spannable spannableText = mSpannableFactory.newSpannable(sb); 108 if (grayGmt) { 109 spannableText.setSpan(new ForegroundColorSpan(GMT_TEXT_COLOR), 110 gmtStart, gmtEnd, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); 111 } 112 if (tz.useDaylightTime()) { 113 spannableText.setSpan(new ForegroundColorSpan(DST_SYMBOL_COLOR), 114 symbolStart, symbolEnd, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); 115 } 116 117 CharSequence gmtDisplayName = spannableText; 118 return gmtDisplayName; 119 } 120 appendGmtOffset(StringBuilder sb, final int gmtOffset)121 public static void appendGmtOffset(StringBuilder sb, final int gmtOffset) { 122 sb.append("GMT"); 123 124 if (gmtOffset < 0) { 125 sb.append('-'); 126 } else { 127 sb.append('+'); 128 } 129 130 final int p = Math.abs(gmtOffset); 131 sb.append(p / DateUtils.HOUR_IN_MILLIS); // Hour 132 133 final int min = (p / (int) DateUtils.MINUTE_IN_MILLIS) % 60; 134 if (min != 0) { // Show minutes if non-zero 135 sb.append(':'); 136 if (min < 10) { 137 sb.append('0'); 138 } 139 sb.append(min); 140 } 141 } 142 getDstSymbol()143 public static char getDstSymbol() { 144 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { 145 return '\u2600'; // The Sun emoji icon. 146 } else { 147 return '*'; 148 } 149 } 150 151 /** 152 * Gets the display name for the specified Timezone ID. If the ID matches the list of IDs that 153 * need to be have their default display names overriden, use the pre-set display name from 154 * R.arrays. 155 * @param id The timezone ID. 156 * @param daylightTime True for daylight time, false for standard time 157 * @return The display name of the timezone. This will just use the default display name, 158 * except that certain timezones have poor defaults, and should use the pre-set override labels 159 * from R.arrays. 160 */ getDisplayName(TimeZone tz, boolean daylightTime)161 private String getDisplayName(TimeZone tz, boolean daylightTime) { 162 if (mOverrideIds == null || mOverrideLabels == null) { 163 // Just in case they somehow didn't get loaded correctly. 164 return tz.getDisplayName(daylightTime, TimeZone.LONG, Locale.getDefault()); 165 } 166 167 for (int i = 0; i < mOverrideIds.length; i++) { 168 if (tz.getID().equals(mOverrideIds[i])) { 169 if (mOverrideLabels.length > i) { 170 return mOverrideLabels[i]; 171 } 172 Log.e(TAG, "timezone_rename_ids len=" + mOverrideIds.length + 173 " timezone_rename_labels len=" + mOverrideLabels.length); 174 break; 175 } 176 } 177 178 // If the ID doesn't need to have the display name overridden, or if the labels were 179 // malformed, just use the default. 180 return tz.getDisplayName(daylightTime, TimeZone.LONG, Locale.getDefault()); 181 } 182 cacheOverrides(Context context)183 private void cacheOverrides(Context context) { 184 Resources res = context.getResources(); 185 mOverrideIds = res.getStringArray(R.array.timezone_rename_ids); 186 mOverrideLabels = res.getStringArray(R.array.timezone_rename_labels); 187 } 188 } 189