1cc955106e2889ec351593b913cb7af4328dca153James Kung/* 2cc955106e2889ec351593b913cb7af4328dca153James Kung * Copyright (C) 2013 The Android Open Source Project 3cc955106e2889ec351593b913cb7af4328dca153James Kung * 4cc955106e2889ec351593b913cb7af4328dca153James Kung * Licensed under the Apache License, Version 2.0 (the "License"); 5cc955106e2889ec351593b913cb7af4328dca153James Kung * you may not use this file except in compliance with the License. 6cc955106e2889ec351593b913cb7af4328dca153James Kung * You may obtain a copy of the License at 7cc955106e2889ec351593b913cb7af4328dca153James Kung * 8cc955106e2889ec351593b913cb7af4328dca153James Kung * http://www.apache.org/licenses/LICENSE-2.0 9cc955106e2889ec351593b913cb7af4328dca153James Kung * 10cc955106e2889ec351593b913cb7af4328dca153James Kung * Unless required by applicable law or agreed to in writing, software 11cc955106e2889ec351593b913cb7af4328dca153James Kung * distributed under the License is distributed on an "AS IS" BASIS, 12cc955106e2889ec351593b913cb7af4328dca153James Kung * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13cc955106e2889ec351593b913cb7af4328dca153James Kung * See the License for the specific language governing permissions and 14cc955106e2889ec351593b913cb7af4328dca153James Kung * limitations under the License. 15cc955106e2889ec351593b913cb7af4328dca153James Kung */ 16cc955106e2889ec351593b913cb7af4328dca153James Kung 17cc955106e2889ec351593b913cb7af4328dca153James Kungpackage com.android.timezonepicker; 18cc955106e2889ec351593b913cb7af4328dca153James Kung 199489460af729fad751cbf42838b303ae85b22079Sam Blitzsteinimport android.content.Context; 20099b3306d12e24f8f7e76a140c69791739fe6ed0Sam Blitzsteinimport android.content.res.Resources; 219489460af729fad751cbf42838b303ae85b22079Sam Blitzsteinimport android.os.Build; 22adbe2ac08b1456d2ed2e4adc2afa9c078a3e028eSam Blitzsteinimport android.text.Spannable; 23adbe2ac08b1456d2ed2e4adc2afa9c078a3e028eSam Blitzsteinimport android.text.Spannable.Factory; 24cc955106e2889ec351593b913cb7af4328dca153James Kungimport android.text.format.DateUtils; 25cc955106e2889ec351593b913cb7af4328dca153James Kungimport android.text.format.Time; 26adbe2ac08b1456d2ed2e4adc2afa9c078a3e028eSam Blitzsteinimport android.text.style.ForegroundColorSpan; 27099b3306d12e24f8f7e76a140c69791739fe6ed0Sam Blitzsteinimport android.util.Log; 28cc955106e2889ec351593b913cb7af4328dca153James Kung 29cc955106e2889ec351593b913cb7af4328dca153James Kungimport java.util.Locale; 30cc955106e2889ec351593b913cb7af4328dca153James Kungimport java.util.TimeZone; 31cc955106e2889ec351593b913cb7af4328dca153James Kung 32cc955106e2889ec351593b913cb7af4328dca153James Kungpublic class TimeZonePickerUtils { 33099b3306d12e24f8f7e76a140c69791739fe6ed0Sam Blitzstein private static final String TAG = "TimeZonePickerUtils"; 34cc955106e2889ec351593b913cb7af4328dca153James Kung 355f2204bc83982ec4ffec0e01efa39325b4fe58aeSam Blitzstein public static final int GMT_TEXT_COLOR = 0xFF888888; 36adbe2ac08b1456d2ed2e4adc2afa9c078a3e028eSam Blitzstein public static final int DST_SYMBOL_COLOR = 0xFFBFBFBF; 37adbe2ac08b1456d2ed2e4adc2afa9c078a3e028eSam Blitzstein private static final Factory mSpannableFactory = Spannable.Factory.getInstance(); 38adbe2ac08b1456d2ed2e4adc2afa9c078a3e028eSam Blitzstein 39099b3306d12e24f8f7e76a140c69791739fe6ed0Sam Blitzstein private Locale mDefaultLocale; 40099b3306d12e24f8f7e76a140c69791739fe6ed0Sam Blitzstein private String[] mOverrideIds; 41099b3306d12e24f8f7e76a140c69791739fe6ed0Sam Blitzstein private String[] mOverrideLabels; 42099b3306d12e24f8f7e76a140c69791739fe6ed0Sam Blitzstein 43099b3306d12e24f8f7e76a140c69791739fe6ed0Sam Blitzstein /** 44099b3306d12e24f8f7e76a140c69791739fe6ed0Sam Blitzstein * This needs to be an instantiated class so that it doesn't need to continuously re-load the 45099b3306d12e24f8f7e76a140c69791739fe6ed0Sam Blitzstein * list of timezone IDs that need to be overridden. 46099b3306d12e24f8f7e76a140c69791739fe6ed0Sam Blitzstein * @param context 47099b3306d12e24f8f7e76a140c69791739fe6ed0Sam Blitzstein */ 48099b3306d12e24f8f7e76a140c69791739fe6ed0Sam Blitzstein public TimeZonePickerUtils(Context context) { 49099b3306d12e24f8f7e76a140c69791739fe6ed0Sam Blitzstein // Instead of saving a reference to the context (because we might need to look up the 50099b3306d12e24f8f7e76a140c69791739fe6ed0Sam Blitzstein // labels every time getGmtDisplayName is called), we'll cache the lists of override IDs 51099b3306d12e24f8f7e76a140c69791739fe6ed0Sam Blitzstein // and labels now. 52099b3306d12e24f8f7e76a140c69791739fe6ed0Sam Blitzstein cacheOverrides(context); 53099b3306d12e24f8f7e76a140c69791739fe6ed0Sam Blitzstein } 549489460af729fad751cbf42838b303ae85b22079Sam Blitzstein 55cc955106e2889ec351593b913cb7af4328dca153James Kung /** 56cc955106e2889ec351593b913cb7af4328dca153James Kung * Given a timezone id (e.g. America/Los_Angeles), returns the corresponding timezone 57adbe2ac08b1456d2ed2e4adc2afa9c078a3e028eSam Blitzstein * display name (e.g. Pacific Time GMT-7). 58cc955106e2889ec351593b913cb7af4328dca153James Kung * 59099b3306d12e24f8f7e76a140c69791739fe6ed0Sam Blitzstein * @param context Context in case the override labels need to be re-cached. 60cc955106e2889ec351593b913cb7af4328dca153James Kung * @param id The timezone id 61cc955106e2889ec351593b913cb7af4328dca153James Kung * @param millis The time (daylight savings or not) 625f2204bc83982ec4ffec0e01efa39325b4fe58aeSam Blitzstein * @param grayGmt Whether the "GMT+x" part of the returned string should be colored gray. 63cc955106e2889ec351593b913cb7af4328dca153James Kung * @return The display name of the timezone. 64cc955106e2889ec351593b913cb7af4328dca153James Kung */ 655f2204bc83982ec4ffec0e01efa39325b4fe58aeSam Blitzstein public CharSequence getGmtDisplayName(Context context, String id, long millis, 665f2204bc83982ec4ffec0e01efa39325b4fe58aeSam Blitzstein boolean grayGmt) { 67cc955106e2889ec351593b913cb7af4328dca153James Kung TimeZone timezone = TimeZone.getTimeZone(id); 68cc955106e2889ec351593b913cb7af4328dca153James Kung if (timezone == null) { 69cc955106e2889ec351593b913cb7af4328dca153James Kung return null; 70cc955106e2889ec351593b913cb7af4328dca153James Kung } 71099b3306d12e24f8f7e76a140c69791739fe6ed0Sam Blitzstein 72099b3306d12e24f8f7e76a140c69791739fe6ed0Sam Blitzstein final Locale defaultLocale = Locale.getDefault(); 73099b3306d12e24f8f7e76a140c69791739fe6ed0Sam Blitzstein if (!defaultLocale.equals(mDefaultLocale)) { 74099b3306d12e24f8f7e76a140c69791739fe6ed0Sam Blitzstein // If the IDs and labels haven't been set yet, or if the locale has been changed 75099b3306d12e24f8f7e76a140c69791739fe6ed0Sam Blitzstein // recently, we'll need to re-cache them. 76099b3306d12e24f8f7e76a140c69791739fe6ed0Sam Blitzstein mDefaultLocale = defaultLocale; 77099b3306d12e24f8f7e76a140c69791739fe6ed0Sam Blitzstein cacheOverrides(context); 78099b3306d12e24f8f7e76a140c69791739fe6ed0Sam Blitzstein } 795f2204bc83982ec4ffec0e01efa39325b4fe58aeSam Blitzstein return buildGmtDisplayName(timezone, millis, grayGmt); 80cc955106e2889ec351593b913cb7af4328dca153James Kung } 81cc955106e2889ec351593b913cb7af4328dca153James Kung 825f2204bc83982ec4ffec0e01efa39325b4fe58aeSam Blitzstein private CharSequence buildGmtDisplayName(TimeZone tz, long timeMillis, boolean grayGmt) { 83cc955106e2889ec351593b913cb7af4328dca153James Kung Time time = new Time(tz.getID()); 84cc955106e2889ec351593b913cb7af4328dca153James Kung time.set(timeMillis); 85cc955106e2889ec351593b913cb7af4328dca153James Kung 86cc955106e2889ec351593b913cb7af4328dca153James Kung StringBuilder sb = new StringBuilder(); 87143a7e20a46dbd66bec4e4d7e8e0dcb693d343d3Michael Chan 88143a7e20a46dbd66bec4e4d7e8e0dcb693d343d3Michael Chan String displayName = getDisplayName(tz, time.isDst != 0); 89143a7e20a46dbd66bec4e4d7e8e0dcb693d343d3Michael Chan sb.append(displayName); 90143a7e20a46dbd66bec4e4d7e8e0dcb693d343d3Michael Chan 915f2204bc83982ec4ffec0e01efa39325b4fe58aeSam Blitzstein sb.append(" "); 92adbe2ac08b1456d2ed2e4adc2afa9c078a3e028eSam Blitzstein final int gmtOffset = tz.getOffset(timeMillis); 93adbe2ac08b1456d2ed2e4adc2afa9c078a3e028eSam Blitzstein int gmtStart = sb.length(); 94adbe2ac08b1456d2ed2e4adc2afa9c078a3e028eSam Blitzstein appendGmtOffset(sb, gmtOffset); 95adbe2ac08b1456d2ed2e4adc2afa9c078a3e028eSam Blitzstein int gmtEnd = sb.length(); 96adbe2ac08b1456d2ed2e4adc2afa9c078a3e028eSam Blitzstein 97adbe2ac08b1456d2ed2e4adc2afa9c078a3e028eSam Blitzstein int symbolStart = 0; 98adbe2ac08b1456d2ed2e4adc2afa9c078a3e028eSam Blitzstein int symbolEnd = 0; 99143a7e20a46dbd66bec4e4d7e8e0dcb693d343d3Michael Chan if (tz.useDaylightTime()) { 100143a7e20a46dbd66bec4e4d7e8e0dcb693d343d3Michael Chan sb.append(" "); 101adbe2ac08b1456d2ed2e4adc2afa9c078a3e028eSam Blitzstein symbolStart = sb.length(); 102143a7e20a46dbd66bec4e4d7e8e0dcb693d343d3Michael Chan sb.append(getDstSymbol()); // Sun symbol 103adbe2ac08b1456d2ed2e4adc2afa9c078a3e028eSam Blitzstein symbolEnd = sb.length(); 104adbe2ac08b1456d2ed2e4adc2afa9c078a3e028eSam Blitzstein } 105adbe2ac08b1456d2ed2e4adc2afa9c078a3e028eSam Blitzstein 106adbe2ac08b1456d2ed2e4adc2afa9c078a3e028eSam Blitzstein // Set the gray colors. 107adbe2ac08b1456d2ed2e4adc2afa9c078a3e028eSam Blitzstein Spannable spannableText = mSpannableFactory.newSpannable(sb); 1085f2204bc83982ec4ffec0e01efa39325b4fe58aeSam Blitzstein if (grayGmt) { 1095f2204bc83982ec4ffec0e01efa39325b4fe58aeSam Blitzstein spannableText.setSpan(new ForegroundColorSpan(GMT_TEXT_COLOR), 1105f2204bc83982ec4ffec0e01efa39325b4fe58aeSam Blitzstein gmtStart, gmtEnd, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); 1115f2204bc83982ec4ffec0e01efa39325b4fe58aeSam Blitzstein } 112adbe2ac08b1456d2ed2e4adc2afa9c078a3e028eSam Blitzstein if (tz.useDaylightTime()) { 113adbe2ac08b1456d2ed2e4adc2afa9c078a3e028eSam Blitzstein spannableText.setSpan(new ForegroundColorSpan(DST_SYMBOL_COLOR), 114adbe2ac08b1456d2ed2e4adc2afa9c078a3e028eSam Blitzstein symbolStart, symbolEnd, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); 115143a7e20a46dbd66bec4e4d7e8e0dcb693d343d3Michael Chan } 116adbe2ac08b1456d2ed2e4adc2afa9c078a3e028eSam Blitzstein 117adbe2ac08b1456d2ed2e4adc2afa9c078a3e028eSam Blitzstein CharSequence gmtDisplayName = spannableText; 118adbe2ac08b1456d2ed2e4adc2afa9c078a3e028eSam Blitzstein return gmtDisplayName; 119143a7e20a46dbd66bec4e4d7e8e0dcb693d343d3Michael Chan } 120143a7e20a46dbd66bec4e4d7e8e0dcb693d343d3Michael Chan 121143a7e20a46dbd66bec4e4d7e8e0dcb693d343d3Michael Chan public static void appendGmtOffset(StringBuilder sb, final int gmtOffset) { 122adbe2ac08b1456d2ed2e4adc2afa9c078a3e028eSam Blitzstein sb.append("GMT"); 123cc955106e2889ec351593b913cb7af4328dca153James Kung 124cc955106e2889ec351593b913cb7af4328dca153James Kung if (gmtOffset < 0) { 125cc955106e2889ec351593b913cb7af4328dca153James Kung sb.append('-'); 126cc955106e2889ec351593b913cb7af4328dca153James Kung } else { 127cc955106e2889ec351593b913cb7af4328dca153James Kung sb.append('+'); 128cc955106e2889ec351593b913cb7af4328dca153James Kung } 129cc955106e2889ec351593b913cb7af4328dca153James Kung 130cc955106e2889ec351593b913cb7af4328dca153James Kung final int p = Math.abs(gmtOffset); 131cc955106e2889ec351593b913cb7af4328dca153James Kung sb.append(p / DateUtils.HOUR_IN_MILLIS); // Hour 132cc955106e2889ec351593b913cb7af4328dca153James Kung 133cc955106e2889ec351593b913cb7af4328dca153James Kung final int min = (p / (int) DateUtils.MINUTE_IN_MILLIS) % 60; 134cc955106e2889ec351593b913cb7af4328dca153James Kung if (min != 0) { // Show minutes if non-zero 135cc955106e2889ec351593b913cb7af4328dca153James Kung sb.append(':'); 136cc955106e2889ec351593b913cb7af4328dca153James Kung if (min < 10) { 137cc955106e2889ec351593b913cb7af4328dca153James Kung sb.append('0'); 138cc955106e2889ec351593b913cb7af4328dca153James Kung } 139cc955106e2889ec351593b913cb7af4328dca153James Kung sb.append(min); 140cc955106e2889ec351593b913cb7af4328dca153James Kung } 141cc955106e2889ec351593b913cb7af4328dca153James Kung } 142cc955106e2889ec351593b913cb7af4328dca153James Kung 143143a7e20a46dbd66bec4e4d7e8e0dcb693d343d3Michael Chan public static char getDstSymbol() { 1449489460af729fad751cbf42838b303ae85b22079Sam Blitzstein if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { 145143a7e20a46dbd66bec4e4d7e8e0dcb693d343d3Michael Chan return '\u2600'; // The Sun emoji icon. 1469489460af729fad751cbf42838b303ae85b22079Sam Blitzstein } else { 147143a7e20a46dbd66bec4e4d7e8e0dcb693d343d3Michael Chan return '*'; 1489489460af729fad751cbf42838b303ae85b22079Sam Blitzstein } 1499489460af729fad751cbf42838b303ae85b22079Sam Blitzstein } 1509489460af729fad751cbf42838b303ae85b22079Sam Blitzstein 151099b3306d12e24f8f7e76a140c69791739fe6ed0Sam Blitzstein /** 152099b3306d12e24f8f7e76a140c69791739fe6ed0Sam Blitzstein * Gets the display name for the specified Timezone ID. If the ID matches the list of IDs that 153099b3306d12e24f8f7e76a140c69791739fe6ed0Sam Blitzstein * need to be have their default display names overriden, use the pre-set display name from 154099b3306d12e24f8f7e76a140c69791739fe6ed0Sam Blitzstein * R.arrays. 155099b3306d12e24f8f7e76a140c69791739fe6ed0Sam Blitzstein * @param id The timezone ID. 156099b3306d12e24f8f7e76a140c69791739fe6ed0Sam Blitzstein * @param daylightTime True for daylight time, false for standard time 157099b3306d12e24f8f7e76a140c69791739fe6ed0Sam Blitzstein * @return The display name of the timezone. This will just use the default display name, 158099b3306d12e24f8f7e76a140c69791739fe6ed0Sam Blitzstein * except that certain timezones have poor defaults, and should use the pre-set override labels 159099b3306d12e24f8f7e76a140c69791739fe6ed0Sam Blitzstein * from R.arrays. 160099b3306d12e24f8f7e76a140c69791739fe6ed0Sam Blitzstein */ 161099b3306d12e24f8f7e76a140c69791739fe6ed0Sam Blitzstein private String getDisplayName(TimeZone tz, boolean daylightTime) { 162099b3306d12e24f8f7e76a140c69791739fe6ed0Sam Blitzstein if (mOverrideIds == null || mOverrideLabels == null) { 163099b3306d12e24f8f7e76a140c69791739fe6ed0Sam Blitzstein // Just in case they somehow didn't get loaded correctly. 164099b3306d12e24f8f7e76a140c69791739fe6ed0Sam Blitzstein return tz.getDisplayName(daylightTime, TimeZone.LONG, Locale.getDefault()); 165099b3306d12e24f8f7e76a140c69791739fe6ed0Sam Blitzstein } 166099b3306d12e24f8f7e76a140c69791739fe6ed0Sam Blitzstein 167099b3306d12e24f8f7e76a140c69791739fe6ed0Sam Blitzstein for (int i = 0; i < mOverrideIds.length; i++) { 168099b3306d12e24f8f7e76a140c69791739fe6ed0Sam Blitzstein if (tz.getID().equals(mOverrideIds[i])) { 169099b3306d12e24f8f7e76a140c69791739fe6ed0Sam Blitzstein if (mOverrideLabels.length > i) { 170099b3306d12e24f8f7e76a140c69791739fe6ed0Sam Blitzstein return mOverrideLabels[i]; 171099b3306d12e24f8f7e76a140c69791739fe6ed0Sam Blitzstein } 172099b3306d12e24f8f7e76a140c69791739fe6ed0Sam Blitzstein Log.e(TAG, "timezone_rename_ids len=" + mOverrideIds.length + 173099b3306d12e24f8f7e76a140c69791739fe6ed0Sam Blitzstein " timezone_rename_labels len=" + mOverrideLabels.length); 174099b3306d12e24f8f7e76a140c69791739fe6ed0Sam Blitzstein break; 175099b3306d12e24f8f7e76a140c69791739fe6ed0Sam Blitzstein } 176099b3306d12e24f8f7e76a140c69791739fe6ed0Sam Blitzstein } 177099b3306d12e24f8f7e76a140c69791739fe6ed0Sam Blitzstein 178099b3306d12e24f8f7e76a140c69791739fe6ed0Sam Blitzstein // If the ID doesn't need to have the display name overridden, or if the labels were 179099b3306d12e24f8f7e76a140c69791739fe6ed0Sam Blitzstein // malformed, just use the default. 180099b3306d12e24f8f7e76a140c69791739fe6ed0Sam Blitzstein return tz.getDisplayName(daylightTime, TimeZone.LONG, Locale.getDefault()); 181099b3306d12e24f8f7e76a140c69791739fe6ed0Sam Blitzstein } 182099b3306d12e24f8f7e76a140c69791739fe6ed0Sam Blitzstein 183099b3306d12e24f8f7e76a140c69791739fe6ed0Sam Blitzstein private void cacheOverrides(Context context) { 184099b3306d12e24f8f7e76a140c69791739fe6ed0Sam Blitzstein Resources res = context.getResources(); 185099b3306d12e24f8f7e76a140c69791739fe6ed0Sam Blitzstein mOverrideIds = res.getStringArray(R.array.timezone_rename_ids); 186099b3306d12e24f8f7e76a140c69791739fe6ed0Sam Blitzstein mOverrideLabels = res.getStringArray(R.array.timezone_rename_labels); 187099b3306d12e24f8f7e76a140c69791739fe6ed0Sam Blitzstein } 188cc955106e2889ec351593b913cb7af4328dca153James Kung} 189