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