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