TimeUtils.java revision a27421a306c49fbe9b3823b30f7ab1cd58b28854
1/*
2 * Copyright (C) 2006 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 android.util;
18
19import android.content.res.Resources;
20import android.content.res.XmlResourceParser;
21
22import libcore.util.ZoneInfoDB;
23import org.xmlpull.v1.XmlPullParser;
24import org.xmlpull.v1.XmlPullParserException;
25
26import java.io.IOException;
27import java.io.PrintWriter;
28import java.util.ArrayList;
29import java.util.Collection;
30import java.util.TimeZone;
31import java.util.Date;
32
33import com.android.internal.util.XmlUtils;
34
35/**
36 * A class containing utility methods related to time zones.
37 */
38public class TimeUtils {
39    /** @hide */ public TimeUtils() {}
40    private static final boolean DBG = false;
41    private static final String TAG = "TimeUtils";
42
43    /** Cached results of getTineZones */
44    private static final Object sLastLockObj = new Object();
45    private static ArrayList<TimeZone> sLastZones = null;
46    private static String sLastCountry = null;
47
48    /** Cached results of getTimeZonesWithUniqueOffsets */
49    private static final Object sLastUniqueLockObj = new Object();
50    private static ArrayList<TimeZone> sLastUniqueZoneOffsets = null;
51    private static String sLastUniqueCountry = null;
52
53
54    /**
55     * Tries to return a time zone that would have had the specified offset
56     * and DST value at the specified moment in the specified country.
57     * Returns null if no suitable zone could be found.
58     */
59    public static TimeZone getTimeZone(int offset, boolean dst, long when, String country) {
60        TimeZone best = null;
61
62        Resources r = Resources.getSystem();
63        XmlResourceParser parser = r.getXml(com.android.internal.R.xml.time_zones_by_country);
64        Date d = new Date(when);
65
66        TimeZone current = TimeZone.getDefault();
67        String currentName = current.getID();
68        int currentOffset = current.getOffset(when);
69        boolean currentDst = current.inDaylightTime(d);
70
71        for (TimeZone tz : getTimeZones(country)) {
72            // If the current time zone is from the right country
73            // and meets the other known properties, keep it
74            // instead of changing to another one.
75
76            if (tz.getID().equals(currentName)) {
77                if (currentOffset == offset && currentDst == dst) {
78                    return current;
79                }
80            }
81
82            // Otherwise, take the first zone from the right
83            // country that has the correct current offset and DST.
84            // (Keep iterating instead of returning in case we
85            // haven't encountered the current time zone yet.)
86
87            if (best == null) {
88                if (tz.getOffset(when) == offset &&
89                    tz.inDaylightTime(d) == dst) {
90                    best = tz;
91                }
92            }
93        }
94
95        return best;
96    }
97
98    /**
99     * Return list of unique time zones for the country. Do not modify
100     *
101     * @param country to find
102     * @return list of unique time zones, maybe empty but never null. Do not modify.
103     * @hide
104     */
105    public static ArrayList<TimeZone> getTimeZonesWithUniqueOffsets(String country) {
106        synchronized(sLastUniqueLockObj) {
107            if ((country != null) && country.equals(sLastUniqueCountry)) {
108                if (DBG) {
109                    Log.d(TAG, "getTimeZonesWithUniqueOffsets(" +
110                            country + "): return cached version");
111                }
112                return sLastUniqueZoneOffsets;
113            }
114        }
115
116        Collection<TimeZone> zones = getTimeZones(country);
117        ArrayList<TimeZone> uniqueTimeZones = new ArrayList<TimeZone>();
118        for (TimeZone zone : zones) {
119            // See if we already have this offset,
120            // Using slow but space efficient and these are small.
121            boolean found = false;
122            for (int i = 0; i < uniqueTimeZones.size(); i++) {
123                if (uniqueTimeZones.get(i).getRawOffset() == zone.getRawOffset()) {
124                    found = true;
125                    break;
126                }
127            }
128            if (found == false) {
129                if (DBG) {
130                    Log.d(TAG, "getTimeZonesWithUniqueOffsets: add unique offset=" +
131                            zone.getRawOffset() + " zone.getID=" + zone.getID());
132                }
133                uniqueTimeZones.add(zone);
134            }
135        }
136
137        synchronized(sLastUniqueLockObj) {
138            // Cache the last result
139            sLastUniqueZoneOffsets = uniqueTimeZones;
140            sLastUniqueCountry = country;
141
142            return sLastUniqueZoneOffsets;
143        }
144    }
145
146    /**
147     * Returns the time zones for the country, which is the code
148     * attribute of the timezone element in time_zones_by_country.xml. Do not modify.
149     *
150     * @param country is a two character country code.
151     * @return TimeZone list, maybe empty but never null. Do not modify.
152     * @hide
153     */
154    public static ArrayList<TimeZone> getTimeZones(String country) {
155        synchronized (sLastLockObj) {
156            if ((country != null) && country.equals(sLastCountry)) {
157                if (DBG) Log.d(TAG, "getTimeZones(" + country + "): return cached version");
158                return sLastZones;
159            }
160        }
161
162        ArrayList<TimeZone> tzs = new ArrayList<TimeZone>();
163
164        if (country == null) {
165            if (DBG) Log.d(TAG, "getTimeZones(null): return empty list");
166            return tzs;
167        }
168
169        Resources r = Resources.getSystem();
170        XmlResourceParser parser = r.getXml(com.android.internal.R.xml.time_zones_by_country);
171
172        try {
173            XmlUtils.beginDocument(parser, "timezones");
174
175            while (true) {
176                XmlUtils.nextElement(parser);
177
178                String element = parser.getName();
179                if (element == null || !(element.equals("timezone"))) {
180                    break;
181                }
182
183                String code = parser.getAttributeValue(null, "code");
184
185                if (country.equals(code)) {
186                    if (parser.next() == XmlPullParser.TEXT) {
187                        String zoneIdString = parser.getText();
188                        TimeZone tz = TimeZone.getTimeZone(zoneIdString);
189                        if (tz.getID().startsWith("GMT") == false) {
190                            // tz.getID doesn't start not "GMT" so its valid
191                            tzs.add(tz);
192                            if (DBG) {
193                                Log.d(TAG, "getTimeZone('" + country + "'): found tz.getID=="
194                                    + ((tz != null) ? tz.getID() : "<no tz>"));
195                            }
196                        }
197                    }
198                }
199            }
200        } catch (XmlPullParserException e) {
201            Log.e(TAG, "Got xml parser exception getTimeZone('" + country + "'): e=", e);
202        } catch (IOException e) {
203            Log.e(TAG, "Got IO exception getTimeZone('" + country + "'): e=", e);
204        } finally {
205            parser.close();
206        }
207
208        synchronized(sLastLockObj) {
209            // Cache the last result;
210            sLastZones = tzs;
211            sLastCountry = country;
212            return sLastZones;
213        }
214    }
215
216    /**
217     * Returns a String indicating the version of the time zone database currently
218     * in use.  The format of the string is dependent on the underlying time zone
219     * database implementation, but will typically contain the year in which the database
220     * was updated plus a letter from a to z indicating changes made within that year.
221     *
222     * <p>Time zone database updates should be expected to occur periodically due to
223     * political and legal changes that cannot be anticipated in advance.  Therefore,
224     * when computing the UTC time for a future event, applications should be aware that
225     * the results may differ following a time zone database update.  This method allows
226     * applications to detect that a database change has occurred, and to recalculate any
227     * cached times accordingly.
228     *
229     * <p>The time zone database may be assumed to change only when the device runtime
230     * is restarted.  Therefore, it is not necessary to re-query the database version
231     * during the lifetime of an activity.
232     */
233    public static String getTimeZoneDatabaseVersion() {
234        return ZoneInfoDB.getVersion();
235    }
236
237    /** @hide Field length that can hold 999 days of time */
238    public static final int HUNDRED_DAY_FIELD_LEN = 19;
239
240    private static final int SECONDS_PER_MINUTE = 60;
241    private static final int SECONDS_PER_HOUR = 60 * 60;
242    private static final int SECONDS_PER_DAY = 24 * 60 * 60;
243
244    private static final Object sFormatSync = new Object();
245    private static char[] sFormatStr = new char[HUNDRED_DAY_FIELD_LEN+5];
246
247    static private int accumField(int amt, int suffix, boolean always, int zeropad) {
248        if (amt > 99 || (always && zeropad >= 3)) {
249            return 3+suffix;
250        }
251        if (amt > 9 || (always && zeropad >= 2)) {
252            return 2+suffix;
253        }
254        if (always || amt > 0) {
255            return 1+suffix;
256        }
257        return 0;
258    }
259
260    static private int printField(char[] formatStr, int amt, char suffix, int pos,
261            boolean always, int zeropad) {
262        if (always || amt > 0) {
263            final int startPos = pos;
264            if ((always && zeropad >= 3) || amt > 99) {
265                int dig = amt/100;
266                formatStr[pos] = (char)(dig + '0');
267                pos++;
268                amt -= (dig*100);
269            }
270            if ((always && zeropad >= 2) || amt > 9 || startPos != pos) {
271                int dig = amt/10;
272                formatStr[pos] = (char)(dig + '0');
273                pos++;
274                amt -= (dig*10);
275            }
276            formatStr[pos] = (char)(amt + '0');
277            pos++;
278            formatStr[pos] = suffix;
279            pos++;
280        }
281        return pos;
282    }
283
284    private static int formatDurationLocked(long duration, int fieldLen) {
285        if (sFormatStr.length < fieldLen) {
286            sFormatStr = new char[fieldLen];
287        }
288
289        char[] formatStr = sFormatStr;
290
291        if (duration == 0) {
292            int pos = 0;
293            fieldLen -= 1;
294            while (pos < fieldLen) {
295                formatStr[pos++] = ' ';
296            }
297            formatStr[pos] = '0';
298            return pos+1;
299        }
300
301        char prefix;
302        if (duration > 0) {
303            prefix = '+';
304        } else {
305            prefix = '-';
306            duration = -duration;
307        }
308
309        int millis = (int)(duration%1000);
310        int seconds = (int) Math.floor(duration / 1000);
311        int days = 0, hours = 0, minutes = 0;
312
313        if (seconds > SECONDS_PER_DAY) {
314            days = seconds / SECONDS_PER_DAY;
315            seconds -= days * SECONDS_PER_DAY;
316        }
317        if (seconds > SECONDS_PER_HOUR) {
318            hours = seconds / SECONDS_PER_HOUR;
319            seconds -= hours * SECONDS_PER_HOUR;
320        }
321        if (seconds > SECONDS_PER_MINUTE) {
322            minutes = seconds / SECONDS_PER_MINUTE;
323            seconds -= minutes * SECONDS_PER_MINUTE;
324        }
325
326        int pos = 0;
327
328        if (fieldLen != 0) {
329            int myLen = accumField(days, 1, false, 0);
330            myLen += accumField(hours, 1, myLen > 0, 2);
331            myLen += accumField(minutes, 1, myLen > 0, 2);
332            myLen += accumField(seconds, 1, myLen > 0, 2);
333            myLen += accumField(millis, 2, true, myLen > 0 ? 3 : 0) + 1;
334            while (myLen < fieldLen) {
335                formatStr[pos] = ' ';
336                pos++;
337                myLen++;
338            }
339        }
340
341        formatStr[pos] = prefix;
342        pos++;
343
344        int start = pos;
345        boolean zeropad = fieldLen != 0;
346        pos = printField(formatStr, days, 'd', pos, false, 0);
347        pos = printField(formatStr, hours, 'h', pos, pos != start, zeropad ? 2 : 0);
348        pos = printField(formatStr, minutes, 'm', pos, pos != start, zeropad ? 2 : 0);
349        pos = printField(formatStr, seconds, 's', pos, pos != start, zeropad ? 2 : 0);
350        pos = printField(formatStr, millis, 'm', pos, true, (zeropad && pos != start) ? 3 : 0);
351        formatStr[pos] = 's';
352        return pos + 1;
353    }
354
355    /** @hide Just for debugging; not internationalized. */
356    public static void formatDuration(long duration, StringBuilder builder) {
357        synchronized (sFormatSync) {
358            int len = formatDurationLocked(duration, 0);
359            builder.append(sFormatStr, 0, len);
360        }
361    }
362
363    /** @hide Just for debugging; not internationalized. */
364    public static void formatDuration(long duration, PrintWriter pw, int fieldLen) {
365        synchronized (sFormatSync) {
366            int len = formatDurationLocked(duration, fieldLen);
367            pw.print(new String(sFormatStr, 0, len));
368        }
369    }
370
371    /** @hide Just for debugging; not internationalized. */
372    public static void formatDuration(long duration, PrintWriter pw) {
373        formatDuration(duration, pw, 0);
374    }
375
376    /** @hide Just for debugging; not internationalized. */
377    public static void formatDuration(long time, long now, PrintWriter pw) {
378        if (time == 0) {
379            pw.print("--");
380            return;
381        }
382        formatDuration(time-now, pw, 0);
383    }
384}
385