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