• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
17 package android.util;
18 
19 import android.os.SystemClock;
20 
21 import java.io.PrintWriter;
22 import java.text.SimpleDateFormat;
23 import java.util.ArrayList;
24 import java.util.Calendar;
25 import java.util.Collection;
26 import java.util.Collections;
27 import java.util.Date;
28 import java.util.List;
29 import libcore.util.TimeZoneFinder;
30 import libcore.util.ZoneInfoDB;
31 
32 /**
33  * A class containing utility methods related to time zones.
34  */
35 public class TimeUtils {
TimeUtils()36     /** @hide */ public TimeUtils() {}
37     private static final boolean DBG = false;
38     private static final String TAG = "TimeUtils";
39 
40     /** Cached results of getTimeZonesWithUniqueOffsets */
41     private static final Object sLastUniqueLockObj = new Object();
42     private static List<String> sLastUniqueZoneOffsets = null;
43     private static String sLastUniqueCountry = null;
44 
45     /** {@hide} */
46     private static SimpleDateFormat sLoggingFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
47 
48     /**
49      * Tries to return a time zone that would have had the specified offset
50      * and DST value at the specified moment in the specified country.
51      * Returns null if no suitable zone could be found.
52      */
getTimeZone( int offset, boolean dst, long when, String country)53     public static java.util.TimeZone getTimeZone(
54             int offset, boolean dst, long when, String country) {
55 
56         android.icu.util.TimeZone icuTimeZone = getIcuTimeZone(offset, dst, when, country);
57         // We must expose a java.util.TimeZone here for API compatibility because this is a public
58         // API method.
59         return icuTimeZone != null ? java.util.TimeZone.getTimeZone(icuTimeZone.getID()) : null;
60     }
61 
62     /**
63      * Tries to return a frozen ICU time zone that would have had the specified offset
64      * and DST value at the specified moment in the specified country.
65      * Returns null if no suitable zone could be found.
66      */
getIcuTimeZone( int offset, boolean dst, long when, String country)67     private static android.icu.util.TimeZone getIcuTimeZone(
68             int offset, boolean dst, long when, String country) {
69         if (country == null) {
70             return null;
71         }
72 
73         android.icu.util.TimeZone bias = android.icu.util.TimeZone.getDefault();
74         return TimeZoneFinder.getInstance()
75                 .lookupTimeZoneByCountryAndOffset(country, offset, dst, when, bias);
76     }
77 
78     /**
79      * Returns an immutable list of unique time zone IDs for the country.
80      *
81      * @param country to find
82      * @return unmodifiable list of unique time zones, maybe empty but never null.
83      * @hide
84      */
getTimeZoneIdsWithUniqueOffsets(String country)85     public static List<String> getTimeZoneIdsWithUniqueOffsets(String country) {
86         synchronized(sLastUniqueLockObj) {
87             if ((country != null) && country.equals(sLastUniqueCountry)) {
88                 if (DBG) {
89                     Log.d(TAG, "getTimeZonesWithUniqueOffsets(" +
90                             country + "): return cached version");
91                 }
92                 return sLastUniqueZoneOffsets;
93             }
94         }
95 
96         Collection<android.icu.util.TimeZone> zones = getIcuTimeZones(country);
97         ArrayList<android.icu.util.TimeZone> uniqueTimeZones = new ArrayList<>();
98         for (android.icu.util.TimeZone zone : zones) {
99             // See if we already have this offset,
100             // Using slow but space efficient and these are small.
101             boolean found = false;
102             for (int i = 0; i < uniqueTimeZones.size(); i++) {
103                 if (uniqueTimeZones.get(i).getRawOffset() == zone.getRawOffset()) {
104                     found = true;
105                     break;
106                 }
107             }
108             if (!found) {
109                 if (DBG) {
110                     Log.d(TAG, "getTimeZonesWithUniqueOffsets: add unique offset=" +
111                             zone.getRawOffset() + " zone.getID=" + zone.getID());
112                 }
113                 uniqueTimeZones.add(zone);
114             }
115         }
116 
117         synchronized(sLastUniqueLockObj) {
118             // Cache the last result
119             sLastUniqueZoneOffsets = extractZoneIds(uniqueTimeZones);
120             sLastUniqueCountry = country;
121 
122             return sLastUniqueZoneOffsets;
123         }
124     }
125 
extractZoneIds(List<android.icu.util.TimeZone> timeZones)126     private static List<String> extractZoneIds(List<android.icu.util.TimeZone> timeZones) {
127         List<String> ids = new ArrayList<>(timeZones.size());
128         for (android.icu.util.TimeZone timeZone : timeZones) {
129             ids.add(timeZone.getID());
130         }
131         return Collections.unmodifiableList(ids);
132     }
133 
134     /**
135      * Returns an immutable list of frozen ICU time zones for the country.
136      *
137      * @param countryIso is a two character country code.
138      * @return TimeZone list, maybe empty but never null.
139      * @hide
140      */
getIcuTimeZones(String countryIso)141     private static List<android.icu.util.TimeZone> getIcuTimeZones(String countryIso) {
142         if (countryIso == null) {
143             if (DBG) Log.d(TAG, "getIcuTimeZones(null): return empty list");
144             return Collections.emptyList();
145         }
146         List<android.icu.util.TimeZone> timeZones =
147                 TimeZoneFinder.getInstance().lookupTimeZonesByCountry(countryIso);
148         if (timeZones == null) {
149             if (DBG) {
150                 Log.d(TAG, "getIcuTimeZones(" + countryIso
151                         + "): returned null, converting to empty list");
152             }
153             return Collections.emptyList();
154         }
155         return timeZones;
156     }
157 
158     /**
159      * Returns a String indicating the version of the time zone database currently
160      * in use.  The format of the string is dependent on the underlying time zone
161      * database implementation, but will typically contain the year in which the database
162      * was updated plus a letter from a to z indicating changes made within that year.
163      *
164      * <p>Time zone database updates should be expected to occur periodically due to
165      * political and legal changes that cannot be anticipated in advance.  Therefore,
166      * when computing the UTC time for a future event, applications should be aware that
167      * the results may differ following a time zone database update.  This method allows
168      * applications to detect that a database change has occurred, and to recalculate any
169      * cached times accordingly.
170      *
171      * <p>The time zone database may be assumed to change only when the device runtime
172      * is restarted.  Therefore, it is not necessary to re-query the database version
173      * during the lifetime of an activity.
174      */
getTimeZoneDatabaseVersion()175     public static String getTimeZoneDatabaseVersion() {
176         return ZoneInfoDB.getInstance().getVersion();
177     }
178 
179     /** @hide Field length that can hold 999 days of time */
180     public static final int HUNDRED_DAY_FIELD_LEN = 19;
181 
182     private static final int SECONDS_PER_MINUTE = 60;
183     private static final int SECONDS_PER_HOUR = 60 * 60;
184     private static final int SECONDS_PER_DAY = 24 * 60 * 60;
185 
186     /** @hide */
187     public static final long NANOS_PER_MS = 1000000;
188 
189     private static final Object sFormatSync = new Object();
190     private static char[] sFormatStr = new char[HUNDRED_DAY_FIELD_LEN+10];
191     private static char[] sTmpFormatStr = new char[HUNDRED_DAY_FIELD_LEN+10];
192 
accumField(int amt, int suffix, boolean always, int zeropad)193     static private int accumField(int amt, int suffix, boolean always, int zeropad) {
194         if (amt > 999) {
195             int num = 0;
196             while (amt != 0) {
197                 num++;
198                 amt /= 10;
199             }
200             return num + suffix;
201         } else {
202             if (amt > 99 || (always && zeropad >= 3)) {
203                 return 3+suffix;
204             }
205             if (amt > 9 || (always && zeropad >= 2)) {
206                 return 2+suffix;
207             }
208             if (always || amt > 0) {
209                 return 1+suffix;
210             }
211         }
212         return 0;
213     }
214 
printFieldLocked(char[] formatStr, int amt, char suffix, int pos, boolean always, int zeropad)215     static private int printFieldLocked(char[] formatStr, int amt, char suffix, int pos,
216             boolean always, int zeropad) {
217         if (always || amt > 0) {
218             final int startPos = pos;
219             if (amt > 999) {
220                 int tmp = 0;
221                 while (amt != 0 && tmp < sTmpFormatStr.length) {
222                     int dig = amt % 10;
223                     sTmpFormatStr[tmp] = (char)(dig + '0');
224                     tmp++;
225                     amt /= 10;
226                 }
227                 tmp--;
228                 while (tmp >= 0) {
229                     formatStr[pos] = sTmpFormatStr[tmp];
230                     pos++;
231                     tmp--;
232                 }
233             } else {
234                 if ((always && zeropad >= 3) || amt > 99) {
235                     int dig = amt/100;
236                     formatStr[pos] = (char)(dig + '0');
237                     pos++;
238                     amt -= (dig*100);
239                 }
240                 if ((always && zeropad >= 2) || amt > 9 || startPos != pos) {
241                     int dig = amt/10;
242                     formatStr[pos] = (char)(dig + '0');
243                     pos++;
244                     amt -= (dig*10);
245                 }
246                 formatStr[pos] = (char)(amt + '0');
247                 pos++;
248             }
249             formatStr[pos] = suffix;
250             pos++;
251         }
252         return pos;
253     }
254 
formatDurationLocked(long duration, int fieldLen)255     private static int formatDurationLocked(long duration, int fieldLen) {
256         if (sFormatStr.length < fieldLen) {
257             sFormatStr = new char[fieldLen];
258         }
259 
260         char[] formatStr = sFormatStr;
261 
262         if (duration == 0) {
263             int pos = 0;
264             fieldLen -= 1;
265             while (pos < fieldLen) {
266                 formatStr[pos++] = ' ';
267             }
268             formatStr[pos] = '0';
269             return pos+1;
270         }
271 
272         char prefix;
273         if (duration > 0) {
274             prefix = '+';
275         } else {
276             prefix = '-';
277             duration = -duration;
278         }
279 
280         int millis = (int)(duration%1000);
281         int seconds = (int) Math.floor(duration / 1000);
282         int days = 0, hours = 0, minutes = 0;
283 
284         if (seconds >= SECONDS_PER_DAY) {
285             days = seconds / SECONDS_PER_DAY;
286             seconds -= days * SECONDS_PER_DAY;
287         }
288         if (seconds >= SECONDS_PER_HOUR) {
289             hours = seconds / SECONDS_PER_HOUR;
290             seconds -= hours * SECONDS_PER_HOUR;
291         }
292         if (seconds >= SECONDS_PER_MINUTE) {
293             minutes = seconds / SECONDS_PER_MINUTE;
294             seconds -= minutes * SECONDS_PER_MINUTE;
295         }
296 
297         int pos = 0;
298 
299         if (fieldLen != 0) {
300             int myLen = accumField(days, 1, false, 0);
301             myLen += accumField(hours, 1, myLen > 0, 2);
302             myLen += accumField(minutes, 1, myLen > 0, 2);
303             myLen += accumField(seconds, 1, myLen > 0, 2);
304             myLen += accumField(millis, 2, true, myLen > 0 ? 3 : 0) + 1;
305             while (myLen < fieldLen) {
306                 formatStr[pos] = ' ';
307                 pos++;
308                 myLen++;
309             }
310         }
311 
312         formatStr[pos] = prefix;
313         pos++;
314 
315         int start = pos;
316         boolean zeropad = fieldLen != 0;
317         pos = printFieldLocked(formatStr, days, 'd', pos, false, 0);
318         pos = printFieldLocked(formatStr, hours, 'h', pos, pos != start, zeropad ? 2 : 0);
319         pos = printFieldLocked(formatStr, minutes, 'm', pos, pos != start, zeropad ? 2 : 0);
320         pos = printFieldLocked(formatStr, seconds, 's', pos, pos != start, zeropad ? 2 : 0);
321         pos = printFieldLocked(formatStr, millis, 'm', pos, true, (zeropad && pos != start) ? 3 : 0);
322         formatStr[pos] = 's';
323         return pos + 1;
324     }
325 
326     /** @hide Just for debugging; not internationalized. */
formatDuration(long duration, StringBuilder builder)327     public static void formatDuration(long duration, StringBuilder builder) {
328         synchronized (sFormatSync) {
329             int len = formatDurationLocked(duration, 0);
330             builder.append(sFormatStr, 0, len);
331         }
332     }
333 
334     /** @hide Just for debugging; not internationalized. */
formatDuration(long duration, PrintWriter pw, int fieldLen)335     public static void formatDuration(long duration, PrintWriter pw, int fieldLen) {
336         synchronized (sFormatSync) {
337             int len = formatDurationLocked(duration, fieldLen);
338             pw.print(new String(sFormatStr, 0, len));
339         }
340     }
341 
342     /** @hide Just for debugging; not internationalized. */
formatDuration(long duration, PrintWriter pw)343     public static void formatDuration(long duration, PrintWriter pw) {
344         formatDuration(duration, pw, 0);
345     }
346 
347     /** @hide Just for debugging; not internationalized. */
formatDuration(long time, long now, PrintWriter pw)348     public static void formatDuration(long time, long now, PrintWriter pw) {
349         if (time == 0) {
350             pw.print("--");
351             return;
352         }
353         formatDuration(time-now, pw, 0);
354     }
355 
356     /** @hide Just for debugging; not internationalized. */
formatUptime(long time)357     public static String formatUptime(long time) {
358         final long diff = time - SystemClock.uptimeMillis();
359         if (diff > 0) {
360             return time + " (in " + diff + " ms)";
361         }
362         if (diff < 0) {
363             return time + " (" + -diff + " ms ago)";
364         }
365         return time + " (now)";
366     }
367 
368     /**
369      * Convert a System.currentTimeMillis() value to a time of day value like
370      * that printed in logs. MM-DD HH:MM:SS.MMM
371      *
372      * @param millis since the epoch (1/1/1970)
373      * @return String representation of the time.
374      * @hide
375      */
logTimeOfDay(long millis)376     public static String logTimeOfDay(long millis) {
377         Calendar c = Calendar.getInstance();
378         if (millis >= 0) {
379             c.setTimeInMillis(millis);
380             return String.format("%tm-%td %tH:%tM:%tS.%tL", c, c, c, c, c, c);
381         } else {
382             return Long.toString(millis);
383         }
384     }
385 
386     /** {@hide} */
formatForLogging(long millis)387     public static String formatForLogging(long millis) {
388         if (millis <= 0) {
389             return "unknown";
390         } else {
391             return sLoggingFormat.format(new Date(millis));
392         }
393     }
394 }
395