blob: 57d55bf31375b7b1123503eb700b4b779d452dd1 [file] [log] [blame]
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001/*
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
Neil Fuller6caa9542018-10-03 13:59:29 +010019import android.annotation.NonNull;
20import android.annotation.Nullable;
Mathew Inwoodb4075682018-08-14 17:32:44 +010021import android.annotation.UnsupportedAppUsage;
Jeff Brown96307042012-07-27 15:51:34 -070022import android.os.SystemClock;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080023
Neil Fuller69ffbd62018-11-16 21:14:13 +000024import libcore.timezone.CountryTimeZones;
25import libcore.timezone.CountryTimeZones.TimeZoneMapping;
26import libcore.timezone.TimeZoneFinder;
Neil Fullerb8383a12018-11-16 21:46:33 +000027import libcore.timezone.ZoneInfoDB;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080028
Neil Fuller11351b62018-01-09 18:38:08 +000029import java.io.PrintWriter;
30import java.text.SimpleDateFormat;
Neil Fuller6caa9542018-10-03 13:59:29 +010031import java.util.ArrayList;
Neil Fuller11351b62018-01-09 18:38:08 +000032import java.util.Calendar;
Neil Fuller6caa9542018-10-03 13:59:29 +010033import java.util.Collections;
Neil Fuller11351b62018-01-09 18:38:08 +000034import java.util.Date;
Neil Fuller6caa9542018-10-03 13:59:29 +010035import java.util.List;
36
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080037/**
38 * A class containing utility methods related to time zones.
39 */
40public class TimeUtils {
Jesse Wilsona0f8bc52011-02-24 10:44:33 -080041 /** @hide */ public TimeUtils() {}
Jeff Sharkeye8a4b662015-06-27 15:43:45 -070042 /** {@hide} */
43 private static SimpleDateFormat sLoggingFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
Wink Savillea27421a2012-03-01 10:25:48 -080044
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080045 /**
46 * Tries to return a time zone that would have had the specified offset
47 * and DST value at the specified moment in the specified country.
48 * Returns null if no suitable zone could be found.
49 */
Neil Fuller1e49f6c2017-03-31 13:05:01 +010050 public static java.util.TimeZone getTimeZone(
51 int offset, boolean dst, long when, String country) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080052
Neil Fuller1e49f6c2017-03-31 13:05:01 +010053 android.icu.util.TimeZone icuTimeZone = getIcuTimeZone(offset, dst, when, country);
54 // We must expose a java.util.TimeZone here for API compatibility because this is a public
55 // API method.
56 return icuTimeZone != null ? java.util.TimeZone.getTimeZone(icuTimeZone.getID()) : null;
Wink Savillea27421a2012-03-01 10:25:48 -080057 }
58
59 /**
Neil Fuller1e49f6c2017-03-31 13:05:01 +010060 * Tries to return a frozen ICU time zone that would have had the specified offset
61 * and DST value at the specified moment in the specified country.
62 * Returns null if no suitable zone could be found.
63 */
64 private static android.icu.util.TimeZone getIcuTimeZone(
65 int offset, boolean dst, long when, String country) {
66 if (country == null) {
67 return null;
68 }
69
70 android.icu.util.TimeZone bias = android.icu.util.TimeZone.getDefault();
71 return TimeZoneFinder.getInstance()
72 .lookupTimeZoneByCountryAndOffset(country, offset, dst, when, bias);
73 }
74
75 /**
Neil Fuller6caa9542018-10-03 13:59:29 +010076 * Returns time zone IDs for time zones known to be associated with a country.
77 *
78 * <p>The list returned may be different from other on-device sources like
79 * {@link android.icu.util.TimeZone#getRegion(String)} as it can be curated to avoid
80 * contentious mappings.
81 *
82 * @param countryCode the ISO 3166-1 alpha-2 code for the country as can be obtained using
83 * {@link java.util.Locale#getCountry()}
84 * @return IDs that can be passed to {@link java.util.TimeZone#getTimeZone(String)} or similar
85 * methods, or {@code null} if the countryCode is unrecognized
86 */
87 public static @Nullable List<String> getTimeZoneIdsForCountryCode(@NonNull String countryCode) {
88 if (countryCode == null) {
89 throw new NullPointerException("countryCode == null");
90 }
91 TimeZoneFinder timeZoneFinder = TimeZoneFinder.getInstance();
92 CountryTimeZones countryTimeZones =
93 timeZoneFinder.lookupCountryTimeZones(countryCode.toLowerCase());
94 if (countryTimeZones == null) {
95 return null;
96 }
97
98 List<String> timeZoneIds = new ArrayList<>();
99 for (TimeZoneMapping timeZoneMapping : countryTimeZones.getTimeZoneMappings()) {
100 if (timeZoneMapping.showInPicker) {
101 timeZoneIds.add(timeZoneMapping.timeZoneId);
102 }
103 }
104 return Collections.unmodifiableList(timeZoneIds);
105 }
106
107 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800108 * Returns a String indicating the version of the time zone database currently
109 * in use. The format of the string is dependent on the underlying time zone
110 * database implementation, but will typically contain the year in which the database
111 * was updated plus a letter from a to z indicating changes made within that year.
Jesse Wilsona0f8bc52011-02-24 10:44:33 -0800112 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800113 * <p>Time zone database updates should be expected to occur periodically due to
114 * political and legal changes that cannot be anticipated in advance. Therefore,
115 * when computing the UTC time for a future event, applications should be aware that
116 * the results may differ following a time zone database update. This method allows
117 * applications to detect that a database change has occurred, and to recalculate any
118 * cached times accordingly.
Jesse Wilsona0f8bc52011-02-24 10:44:33 -0800119 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800120 * <p>The time zone database may be assumed to change only when the device runtime
121 * is restarted. Therefore, it is not necessary to re-query the database version
122 * during the lifetime of an activity.
123 */
124 public static String getTimeZoneDatabaseVersion() {
Elliott Hughes48289432013-04-26 11:30:54 -0700125 return ZoneInfoDB.getInstance().getVersion();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800126 }
Dianne Hackborn1ebccf52010-08-15 13:04:34 -0700127
Dianne Hackbornb5e31652010-09-07 12:13:55 -0700128 /** @hide Field length that can hold 999 days of time */
129 public static final int HUNDRED_DAY_FIELD_LEN = 19;
Jesse Wilsona0f8bc52011-02-24 10:44:33 -0800130
Dianne Hackborn1ebccf52010-08-15 13:04:34 -0700131 private static final int SECONDS_PER_MINUTE = 60;
132 private static final int SECONDS_PER_HOUR = 60 * 60;
133 private static final int SECONDS_PER_DAY = 24 * 60 * 60;
134
John Reck315c3292014-05-09 19:21:04 -0700135 /** @hide */
136 public static final long NANOS_PER_MS = 1000000;
137
Dianne Hackbornb5e31652010-09-07 12:13:55 -0700138 private static final Object sFormatSync = new Object();
Dianne Hackborn3d1933c42015-06-10 16:25:57 -0700139 private static char[] sFormatStr = new char[HUNDRED_DAY_FIELD_LEN+10];
140 private static char[] sTmpFormatStr = new char[HUNDRED_DAY_FIELD_LEN+10];
Jeff Sharkeyeaaf3962012-07-21 12:37:26 -0700141
Dianne Hackbornb5e31652010-09-07 12:13:55 -0700142 static private int accumField(int amt, int suffix, boolean always, int zeropad) {
Dianne Hackborn3d1933c42015-06-10 16:25:57 -0700143 if (amt > 999) {
144 int num = 0;
145 while (amt != 0) {
146 num++;
147 amt /= 10;
148 }
149 return num + suffix;
150 } else {
151 if (amt > 99 || (always && zeropad >= 3)) {
152 return 3+suffix;
153 }
154 if (amt > 9 || (always && zeropad >= 2)) {
155 return 2+suffix;
156 }
157 if (always || amt > 0) {
158 return 1+suffix;
159 }
Dianne Hackbornb5e31652010-09-07 12:13:55 -0700160 }
161 return 0;
162 }
Jesse Wilsona0f8bc52011-02-24 10:44:33 -0800163
Dianne Hackborn3d1933c42015-06-10 16:25:57 -0700164 static private int printFieldLocked(char[] formatStr, int amt, char suffix, int pos,
Dianne Hackbornb5e31652010-09-07 12:13:55 -0700165 boolean always, int zeropad) {
166 if (always || amt > 0) {
Bjorn Bringert901b3792010-11-23 14:43:12 +0000167 final int startPos = pos;
Dianne Hackborn3d1933c42015-06-10 16:25:57 -0700168 if (amt > 999) {
169 int tmp = 0;
170 while (amt != 0 && tmp < sTmpFormatStr.length) {
171 int dig = amt % 10;
172 sTmpFormatStr[tmp] = (char)(dig + '0');
173 tmp++;
174 amt /= 10;
175 }
176 tmp--;
177 while (tmp >= 0) {
178 formatStr[pos] = sTmpFormatStr[tmp];
179 pos++;
180 tmp--;
181 }
182 } else {
183 if ((always && zeropad >= 3) || amt > 99) {
184 int dig = amt/100;
185 formatStr[pos] = (char)(dig + '0');
186 pos++;
187 amt -= (dig*100);
188 }
189 if ((always && zeropad >= 2) || amt > 9 || startPos != pos) {
190 int dig = amt/10;
191 formatStr[pos] = (char)(dig + '0');
192 pos++;
193 amt -= (dig*10);
194 }
195 formatStr[pos] = (char)(amt + '0');
Dianne Hackbornb5e31652010-09-07 12:13:55 -0700196 pos++;
Dianne Hackbornb5e31652010-09-07 12:13:55 -0700197 }
Dianne Hackbornb5e31652010-09-07 12:13:55 -0700198 formatStr[pos] = suffix;
199 pos++;
200 }
201 return pos;
202 }
Jesse Wilsona0f8bc52011-02-24 10:44:33 -0800203
Dianne Hackbornb5e31652010-09-07 12:13:55 -0700204 private static int formatDurationLocked(long duration, int fieldLen) {
205 if (sFormatStr.length < fieldLen) {
206 sFormatStr = new char[fieldLen];
207 }
Jesse Wilsona0f8bc52011-02-24 10:44:33 -0800208
Dianne Hackbornb5e31652010-09-07 12:13:55 -0700209 char[] formatStr = sFormatStr;
Jesse Wilsona0f8bc52011-02-24 10:44:33 -0800210
Dianne Hackbornb5e31652010-09-07 12:13:55 -0700211 if (duration == 0) {
212 int pos = 0;
213 fieldLen -= 1;
214 while (pos < fieldLen) {
Jozef BABJAK47f13e72011-02-22 07:20:30 +0100215 formatStr[pos++] = ' ';
Dianne Hackbornb5e31652010-09-07 12:13:55 -0700216 }
217 formatStr[pos] = '0';
218 return pos+1;
219 }
Jesse Wilsona0f8bc52011-02-24 10:44:33 -0800220
Dianne Hackbornb5e31652010-09-07 12:13:55 -0700221 char prefix;
Dianne Hackborn1ebccf52010-08-15 13:04:34 -0700222 if (duration > 0) {
Dianne Hackbornb5e31652010-09-07 12:13:55 -0700223 prefix = '+';
Dianne Hackborn1ebccf52010-08-15 13:04:34 -0700224 } else {
Dianne Hackbornb5e31652010-09-07 12:13:55 -0700225 prefix = '-';
Dianne Hackborn1ebccf52010-08-15 13:04:34 -0700226 duration = -duration;
227 }
228
229 int millis = (int)(duration%1000);
230 int seconds = (int) Math.floor(duration / 1000);
231 int days = 0, hours = 0, minutes = 0;
232
Mitchell Willsf9a80cb2015-09-22 15:30:08 -0700233 if (seconds >= SECONDS_PER_DAY) {
Dianne Hackborn1ebccf52010-08-15 13:04:34 -0700234 days = seconds / SECONDS_PER_DAY;
235 seconds -= days * SECONDS_PER_DAY;
236 }
Mitchell Willsf9a80cb2015-09-22 15:30:08 -0700237 if (seconds >= SECONDS_PER_HOUR) {
Dianne Hackborn1ebccf52010-08-15 13:04:34 -0700238 hours = seconds / SECONDS_PER_HOUR;
239 seconds -= hours * SECONDS_PER_HOUR;
240 }
Mitchell Willsf9a80cb2015-09-22 15:30:08 -0700241 if (seconds >= SECONDS_PER_MINUTE) {
Dianne Hackborn1ebccf52010-08-15 13:04:34 -0700242 minutes = seconds / SECONDS_PER_MINUTE;
243 seconds -= minutes * SECONDS_PER_MINUTE;
244 }
245
Dianne Hackbornb5e31652010-09-07 12:13:55 -0700246 int pos = 0;
Jesse Wilsona0f8bc52011-02-24 10:44:33 -0800247
Dianne Hackbornb5e31652010-09-07 12:13:55 -0700248 if (fieldLen != 0) {
249 int myLen = accumField(days, 1, false, 0);
250 myLen += accumField(hours, 1, myLen > 0, 2);
251 myLen += accumField(minutes, 1, myLen > 0, 2);
252 myLen += accumField(seconds, 1, myLen > 0, 2);
253 myLen += accumField(millis, 2, true, myLen > 0 ? 3 : 0) + 1;
254 while (myLen < fieldLen) {
255 formatStr[pos] = ' ';
256 pos++;
257 myLen++;
258 }
Dianne Hackborn1ebccf52010-08-15 13:04:34 -0700259 }
Jesse Wilsona0f8bc52011-02-24 10:44:33 -0800260
Dianne Hackbornb5e31652010-09-07 12:13:55 -0700261 formatStr[pos] = prefix;
262 pos++;
Jesse Wilsona0f8bc52011-02-24 10:44:33 -0800263
Dianne Hackbornb5e31652010-09-07 12:13:55 -0700264 int start = pos;
265 boolean zeropad = fieldLen != 0;
Dianne Hackborn3d1933c42015-06-10 16:25:57 -0700266 pos = printFieldLocked(formatStr, days, 'd', pos, false, 0);
267 pos = printFieldLocked(formatStr, hours, 'h', pos, pos != start, zeropad ? 2 : 0);
268 pos = printFieldLocked(formatStr, minutes, 'm', pos, pos != start, zeropad ? 2 : 0);
269 pos = printFieldLocked(formatStr, seconds, 's', pos, pos != start, zeropad ? 2 : 0);
270 pos = printFieldLocked(formatStr, millis, 'm', pos, true, (zeropad && pos != start) ? 3 : 0);
Dianne Hackbornb5e31652010-09-07 12:13:55 -0700271 formatStr[pos] = 's';
272 return pos + 1;
273 }
Jesse Wilsona0f8bc52011-02-24 10:44:33 -0800274
Dianne Hackbornb5e31652010-09-07 12:13:55 -0700275 /** @hide Just for debugging; not internationalized. */
276 public static void formatDuration(long duration, StringBuilder builder) {
277 synchronized (sFormatSync) {
278 int len = formatDurationLocked(duration, 0);
279 builder.append(sFormatStr, 0, len);
Dianne Hackborn1ebccf52010-08-15 13:04:34 -0700280 }
Dianne Hackbornb5e31652010-09-07 12:13:55 -0700281 }
282
283 /** @hide Just for debugging; not internationalized. */
Kweku Adams71a95312018-04-16 16:54:24 -0700284 public static void formatDuration(long duration, StringBuilder builder, int fieldLen) {
285 synchronized (sFormatSync) {
286 int len = formatDurationLocked(duration, fieldLen);
287 builder.append(sFormatStr, 0, len);
288 }
289 }
290
291 /** @hide Just for debugging; not internationalized. */
Mathew Inwoodb4075682018-08-14 17:32:44 +0100292 @UnsupportedAppUsage
Dianne Hackbornb5e31652010-09-07 12:13:55 -0700293 public static void formatDuration(long duration, PrintWriter pw, int fieldLen) {
294 synchronized (sFormatSync) {
295 int len = formatDurationLocked(duration, fieldLen);
296 pw.print(new String(sFormatStr, 0, len));
Dianne Hackborn1ebccf52010-08-15 13:04:34 -0700297 }
Dianne Hackborn1ebccf52010-08-15 13:04:34 -0700298 }
299
300 /** @hide Just for debugging; not internationalized. */
Jeff Sharkeyd0fff2e2017-11-07 16:55:06 -0700301 public static String formatDuration(long duration) {
302 synchronized (sFormatSync) {
303 int len = formatDurationLocked(duration, 0);
304 return new String(sFormatStr, 0, len);
305 }
306 }
307
308 /** @hide Just for debugging; not internationalized. */
Mathew Inwoodb4075682018-08-14 17:32:44 +0100309 @UnsupportedAppUsage
Dianne Hackborn1ebccf52010-08-15 13:04:34 -0700310 public static void formatDuration(long duration, PrintWriter pw) {
Dianne Hackbornb5e31652010-09-07 12:13:55 -0700311 formatDuration(duration, pw, 0);
Dianne Hackborn1ebccf52010-08-15 13:04:34 -0700312 }
Jesse Wilsona0f8bc52011-02-24 10:44:33 -0800313
Dianne Hackborn1ebccf52010-08-15 13:04:34 -0700314 /** @hide Just for debugging; not internationalized. */
315 public static void formatDuration(long time, long now, PrintWriter pw) {
316 if (time == 0) {
317 pw.print("--");
318 return;
319 }
Dianne Hackbornb5e31652010-09-07 12:13:55 -0700320 formatDuration(time-now, pw, 0);
Dianne Hackborn1ebccf52010-08-15 13:04:34 -0700321 }
Wink Saville26e1a022012-05-10 16:23:39 -0700322
Jeff Brown96307042012-07-27 15:51:34 -0700323 /** @hide Just for debugging; not internationalized. */
324 public static String formatUptime(long time) {
325 final long diff = time - SystemClock.uptimeMillis();
326 if (diff > 0) {
327 return time + " (in " + diff + " ms)";
328 }
329 if (diff < 0) {
330 return time + " (" + -diff + " ms ago)";
331 }
332 return time + " (now)";
333 }
334
Wink Saville26e1a022012-05-10 16:23:39 -0700335 /**
336 * Convert a System.currentTimeMillis() value to a time of day value like
337 * that printed in logs. MM-DD HH:MM:SS.MMM
338 *
339 * @param millis since the epoch (1/1/1970)
340 * @return String representation of the time.
341 * @hide
342 */
Mathew Inwoodb4075682018-08-14 17:32:44 +0100343 @UnsupportedAppUsage
Wink Saville26e1a022012-05-10 16:23:39 -0700344 public static String logTimeOfDay(long millis) {
345 Calendar c = Calendar.getInstance();
346 if (millis >= 0) {
347 c.setTimeInMillis(millis);
348 return String.format("%tm-%td %tH:%tM:%tS.%tL", c, c, c, c, c, c);
349 } else {
350 return Long.toString(millis);
351 }
352 }
Jeff Sharkeye8a4b662015-06-27 15:43:45 -0700353
354 /** {@hide} */
355 public static String formatForLogging(long millis) {
356 if (millis <= 0) {
357 return "unknown";
358 } else {
359 return sLoggingFormat.format(new Date(millis));
360 }
361 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800362}