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