blob: f8b38e9d215da989b79576ce59c9607b09dfd713 [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;
Wale Ogunwale691af682019-02-11 03:09:10 -080021import android.annotation.TestApi;
Mathew Inwoodb4075682018-08-14 17:32:44 +010022import android.annotation.UnsupportedAppUsage;
Mathew Inwood31755f92018-12-20 13:53:36 +000023import android.os.Build;
Jeff Brown96307042012-07-27 15:51:34 -070024import android.os.SystemClock;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080025
Neil Fuller69ffbd62018-11-16 21:14:13 +000026import libcore.timezone.CountryTimeZones;
27import libcore.timezone.CountryTimeZones.TimeZoneMapping;
28import libcore.timezone.TimeZoneFinder;
Neil Fullerb8383a12018-11-16 21:46:33 +000029import libcore.timezone.ZoneInfoDB;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080030
Neil Fuller11351b62018-01-09 18:38:08 +000031import java.io.PrintWriter;
32import java.text.SimpleDateFormat;
Neil Fuller6caa9542018-10-03 13:59:29 +010033import java.util.ArrayList;
Neil Fuller11351b62018-01-09 18:38:08 +000034import java.util.Calendar;
Neil Fuller6caa9542018-10-03 13:59:29 +010035import java.util.Collections;
Neil Fuller11351b62018-01-09 18:38:08 +000036import java.util.Date;
Neil Fuller6caa9542018-10-03 13:59:29 +010037import java.util.List;
38
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080039/**
40 * A class containing utility methods related to time zones.
41 */
42public class TimeUtils {
Jesse Wilsona0f8bc52011-02-24 10:44:33 -080043 /** @hide */ public TimeUtils() {}
Jeff Sharkeye8a4b662015-06-27 15:43:45 -070044 /** {@hide} */
45 private static SimpleDateFormat sLoggingFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
Wink Savillea27421a2012-03-01 10:25:48 -080046
Makoto Onukic279f2b2019-01-09 13:11:37 -080047 /** @hide */
48 public static final SimpleDateFormat sDumpDateFormat =
49 new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080050 /**
51 * Tries to return a 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 */
Neil Fuller1e49f6c2017-03-31 13:05:01 +010055 public static java.util.TimeZone getTimeZone(
56 int offset, boolean dst, long when, String country) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080057
Neil Fuller1e49f6c2017-03-31 13:05:01 +010058 android.icu.util.TimeZone icuTimeZone = getIcuTimeZone(offset, dst, when, country);
59 // We must expose a java.util.TimeZone here for API compatibility because this is a public
60 // API method.
61 return icuTimeZone != null ? java.util.TimeZone.getTimeZone(icuTimeZone.getID()) : null;
Wink Savillea27421a2012-03-01 10:25:48 -080062 }
63
64 /**
Neil Fuller1e49f6c2017-03-31 13:05:01 +010065 * Tries to return a frozen ICU time zone that would have had the specified offset
66 * and DST value at the specified moment in the specified country.
67 * Returns null if no suitable zone could be found.
68 */
69 private static android.icu.util.TimeZone getIcuTimeZone(
70 int offset, boolean dst, long when, String country) {
71 if (country == null) {
72 return null;
73 }
74
75 android.icu.util.TimeZone bias = android.icu.util.TimeZone.getDefault();
76 return TimeZoneFinder.getInstance()
77 .lookupTimeZoneByCountryAndOffset(country, offset, dst, when, bias);
78 }
79
80 /**
Neil Fuller6caa9542018-10-03 13:59:29 +010081 * Returns time zone IDs for time zones known to be associated with a country.
82 *
83 * <p>The list returned may be different from other on-device sources like
84 * {@link android.icu.util.TimeZone#getRegion(String)} as it can be curated to avoid
85 * contentious mappings.
86 *
87 * @param countryCode the ISO 3166-1 alpha-2 code for the country as can be obtained using
88 * {@link java.util.Locale#getCountry()}
89 * @return IDs that can be passed to {@link java.util.TimeZone#getTimeZone(String)} or similar
90 * methods, or {@code null} if the countryCode is unrecognized
91 */
92 public static @Nullable List<String> getTimeZoneIdsForCountryCode(@NonNull String countryCode) {
93 if (countryCode == null) {
94 throw new NullPointerException("countryCode == null");
95 }
96 TimeZoneFinder timeZoneFinder = TimeZoneFinder.getInstance();
97 CountryTimeZones countryTimeZones =
98 timeZoneFinder.lookupCountryTimeZones(countryCode.toLowerCase());
99 if (countryTimeZones == null) {
100 return null;
101 }
102
103 List<String> timeZoneIds = new ArrayList<>();
104 for (TimeZoneMapping timeZoneMapping : countryTimeZones.getTimeZoneMappings()) {
105 if (timeZoneMapping.showInPicker) {
106 timeZoneIds.add(timeZoneMapping.timeZoneId);
107 }
108 }
109 return Collections.unmodifiableList(timeZoneIds);
110 }
111
112 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800113 * Returns a String indicating the version of the time zone database currently
114 * in use. The format of the string is dependent on the underlying time zone
115 * database implementation, but will typically contain the year in which the database
116 * was updated plus a letter from a to z indicating changes made within that year.
Jesse Wilsona0f8bc52011-02-24 10:44:33 -0800117 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800118 * <p>Time zone database updates should be expected to occur periodically due to
119 * political and legal changes that cannot be anticipated in advance. Therefore,
120 * when computing the UTC time for a future event, applications should be aware that
121 * the results may differ following a time zone database update. This method allows
122 * applications to detect that a database change has occurred, and to recalculate any
123 * cached times accordingly.
Jesse Wilsona0f8bc52011-02-24 10:44:33 -0800124 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800125 * <p>The time zone database may be assumed to change only when the device runtime
126 * is restarted. Therefore, it is not necessary to re-query the database version
127 * during the lifetime of an activity.
128 */
129 public static String getTimeZoneDatabaseVersion() {
Elliott Hughes48289432013-04-26 11:30:54 -0700130 return ZoneInfoDB.getInstance().getVersion();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800131 }
Dianne Hackborn1ebccf52010-08-15 13:04:34 -0700132
Dianne Hackbornb5e31652010-09-07 12:13:55 -0700133 /** @hide Field length that can hold 999 days of time */
134 public static final int HUNDRED_DAY_FIELD_LEN = 19;
Jesse Wilsona0f8bc52011-02-24 10:44:33 -0800135
Dianne Hackborn1ebccf52010-08-15 13:04:34 -0700136 private static final int SECONDS_PER_MINUTE = 60;
137 private static final int SECONDS_PER_HOUR = 60 * 60;
138 private static final int SECONDS_PER_DAY = 24 * 60 * 60;
139
John Reck315c3292014-05-09 19:21:04 -0700140 /** @hide */
141 public static final long NANOS_PER_MS = 1000000;
142
Dianne Hackbornb5e31652010-09-07 12:13:55 -0700143 private static final Object sFormatSync = new Object();
Dianne Hackborn3d1933c42015-06-10 16:25:57 -0700144 private static char[] sFormatStr = new char[HUNDRED_DAY_FIELD_LEN+10];
145 private static char[] sTmpFormatStr = new char[HUNDRED_DAY_FIELD_LEN+10];
Jeff Sharkeyeaaf3962012-07-21 12:37:26 -0700146
Dianne Hackbornb5e31652010-09-07 12:13:55 -0700147 static private int accumField(int amt, int suffix, boolean always, int zeropad) {
Dianne Hackborn3d1933c42015-06-10 16:25:57 -0700148 if (amt > 999) {
149 int num = 0;
150 while (amt != 0) {
151 num++;
152 amt /= 10;
153 }
154 return num + suffix;
155 } else {
156 if (amt > 99 || (always && zeropad >= 3)) {
157 return 3+suffix;
158 }
159 if (amt > 9 || (always && zeropad >= 2)) {
160 return 2+suffix;
161 }
162 if (always || amt > 0) {
163 return 1+suffix;
164 }
Dianne Hackbornb5e31652010-09-07 12:13:55 -0700165 }
166 return 0;
167 }
Jesse Wilsona0f8bc52011-02-24 10:44:33 -0800168
Dianne Hackborn3d1933c42015-06-10 16:25:57 -0700169 static private int printFieldLocked(char[] formatStr, int amt, char suffix, int pos,
Dianne Hackbornb5e31652010-09-07 12:13:55 -0700170 boolean always, int zeropad) {
171 if (always || amt > 0) {
Bjorn Bringert901b3792010-11-23 14:43:12 +0000172 final int startPos = pos;
Dianne Hackborn3d1933c42015-06-10 16:25:57 -0700173 if (amt > 999) {
174 int tmp = 0;
175 while (amt != 0 && tmp < sTmpFormatStr.length) {
176 int dig = amt % 10;
177 sTmpFormatStr[tmp] = (char)(dig + '0');
178 tmp++;
179 amt /= 10;
180 }
181 tmp--;
182 while (tmp >= 0) {
183 formatStr[pos] = sTmpFormatStr[tmp];
184 pos++;
185 tmp--;
186 }
187 } else {
188 if ((always && zeropad >= 3) || amt > 99) {
189 int dig = amt/100;
190 formatStr[pos] = (char)(dig + '0');
191 pos++;
192 amt -= (dig*100);
193 }
194 if ((always && zeropad >= 2) || amt > 9 || startPos != pos) {
195 int dig = amt/10;
196 formatStr[pos] = (char)(dig + '0');
197 pos++;
198 amt -= (dig*10);
199 }
200 formatStr[pos] = (char)(amt + '0');
Dianne Hackbornb5e31652010-09-07 12:13:55 -0700201 pos++;
Dianne Hackbornb5e31652010-09-07 12:13:55 -0700202 }
Dianne Hackbornb5e31652010-09-07 12:13:55 -0700203 formatStr[pos] = suffix;
204 pos++;
205 }
206 return pos;
207 }
Jesse Wilsona0f8bc52011-02-24 10:44:33 -0800208
Dianne Hackbornb5e31652010-09-07 12:13:55 -0700209 private static int formatDurationLocked(long duration, int fieldLen) {
210 if (sFormatStr.length < fieldLen) {
211 sFormatStr = new char[fieldLen];
212 }
Jesse Wilsona0f8bc52011-02-24 10:44:33 -0800213
Dianne Hackbornb5e31652010-09-07 12:13:55 -0700214 char[] formatStr = sFormatStr;
Jesse Wilsona0f8bc52011-02-24 10:44:33 -0800215
Dianne Hackbornb5e31652010-09-07 12:13:55 -0700216 if (duration == 0) {
217 int pos = 0;
218 fieldLen -= 1;
219 while (pos < fieldLen) {
Jozef BABJAK47f13e72011-02-22 07:20:30 +0100220 formatStr[pos++] = ' ';
Dianne Hackbornb5e31652010-09-07 12:13:55 -0700221 }
222 formatStr[pos] = '0';
223 return pos+1;
224 }
Jesse Wilsona0f8bc52011-02-24 10:44:33 -0800225
Dianne Hackbornb5e31652010-09-07 12:13:55 -0700226 char prefix;
Dianne Hackborn1ebccf52010-08-15 13:04:34 -0700227 if (duration > 0) {
Dianne Hackbornb5e31652010-09-07 12:13:55 -0700228 prefix = '+';
Dianne Hackborn1ebccf52010-08-15 13:04:34 -0700229 } else {
Dianne Hackbornb5e31652010-09-07 12:13:55 -0700230 prefix = '-';
Dianne Hackborn1ebccf52010-08-15 13:04:34 -0700231 duration = -duration;
232 }
233
234 int millis = (int)(duration%1000);
235 int seconds = (int) Math.floor(duration / 1000);
236 int days = 0, hours = 0, minutes = 0;
237
Mitchell Willsf9a80cb2015-09-22 15:30:08 -0700238 if (seconds >= SECONDS_PER_DAY) {
Dianne Hackborn1ebccf52010-08-15 13:04:34 -0700239 days = seconds / SECONDS_PER_DAY;
240 seconds -= days * SECONDS_PER_DAY;
241 }
Mitchell Willsf9a80cb2015-09-22 15:30:08 -0700242 if (seconds >= SECONDS_PER_HOUR) {
Dianne Hackborn1ebccf52010-08-15 13:04:34 -0700243 hours = seconds / SECONDS_PER_HOUR;
244 seconds -= hours * SECONDS_PER_HOUR;
245 }
Mitchell Willsf9a80cb2015-09-22 15:30:08 -0700246 if (seconds >= SECONDS_PER_MINUTE) {
Dianne Hackborn1ebccf52010-08-15 13:04:34 -0700247 minutes = seconds / SECONDS_PER_MINUTE;
248 seconds -= minutes * SECONDS_PER_MINUTE;
249 }
250
Dianne Hackbornb5e31652010-09-07 12:13:55 -0700251 int pos = 0;
Jesse Wilsona0f8bc52011-02-24 10:44:33 -0800252
Dianne Hackbornb5e31652010-09-07 12:13:55 -0700253 if (fieldLen != 0) {
254 int myLen = accumField(days, 1, false, 0);
255 myLen += accumField(hours, 1, myLen > 0, 2);
256 myLen += accumField(minutes, 1, myLen > 0, 2);
257 myLen += accumField(seconds, 1, myLen > 0, 2);
258 myLen += accumField(millis, 2, true, myLen > 0 ? 3 : 0) + 1;
259 while (myLen < fieldLen) {
260 formatStr[pos] = ' ';
261 pos++;
262 myLen++;
263 }
Dianne Hackborn1ebccf52010-08-15 13:04:34 -0700264 }
Jesse Wilsona0f8bc52011-02-24 10:44:33 -0800265
Dianne Hackbornb5e31652010-09-07 12:13:55 -0700266 formatStr[pos] = prefix;
267 pos++;
Jesse Wilsona0f8bc52011-02-24 10:44:33 -0800268
Dianne Hackbornb5e31652010-09-07 12:13:55 -0700269 int start = pos;
270 boolean zeropad = fieldLen != 0;
Dianne Hackborn3d1933c42015-06-10 16:25:57 -0700271 pos = printFieldLocked(formatStr, days, 'd', pos, false, 0);
272 pos = printFieldLocked(formatStr, hours, 'h', pos, pos != start, zeropad ? 2 : 0);
273 pos = printFieldLocked(formatStr, minutes, 'm', pos, pos != start, zeropad ? 2 : 0);
274 pos = printFieldLocked(formatStr, seconds, 's', pos, pos != start, zeropad ? 2 : 0);
275 pos = printFieldLocked(formatStr, millis, 'm', pos, true, (zeropad && pos != start) ? 3 : 0);
Dianne Hackbornb5e31652010-09-07 12:13:55 -0700276 formatStr[pos] = 's';
277 return pos + 1;
278 }
Jesse Wilsona0f8bc52011-02-24 10:44:33 -0800279
Dianne Hackbornb5e31652010-09-07 12:13:55 -0700280 /** @hide Just for debugging; not internationalized. */
281 public static void formatDuration(long duration, StringBuilder builder) {
282 synchronized (sFormatSync) {
283 int len = formatDurationLocked(duration, 0);
284 builder.append(sFormatStr, 0, len);
Dianne Hackborn1ebccf52010-08-15 13:04:34 -0700285 }
Dianne Hackbornb5e31652010-09-07 12:13:55 -0700286 }
287
288 /** @hide Just for debugging; not internationalized. */
Kweku Adams71a95312018-04-16 16:54:24 -0700289 public static void formatDuration(long duration, StringBuilder builder, int fieldLen) {
290 synchronized (sFormatSync) {
291 int len = formatDurationLocked(duration, fieldLen);
292 builder.append(sFormatStr, 0, len);
293 }
294 }
295
296 /** @hide Just for debugging; not internationalized. */
Mathew Inwood31755f92018-12-20 13:53:36 +0000297 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
Dianne Hackbornb5e31652010-09-07 12:13:55 -0700298 public static void formatDuration(long duration, PrintWriter pw, int fieldLen) {
299 synchronized (sFormatSync) {
300 int len = formatDurationLocked(duration, fieldLen);
301 pw.print(new String(sFormatStr, 0, len));
Dianne Hackborn1ebccf52010-08-15 13:04:34 -0700302 }
Dianne Hackborn1ebccf52010-08-15 13:04:34 -0700303 }
304
305 /** @hide Just for debugging; not internationalized. */
Wale Ogunwale691af682019-02-11 03:09:10 -0800306 @TestApi
Jeff Sharkeyd0fff2e2017-11-07 16:55:06 -0700307 public static String formatDuration(long duration) {
308 synchronized (sFormatSync) {
309 int len = formatDurationLocked(duration, 0);
310 return new String(sFormatStr, 0, len);
311 }
312 }
313
314 /** @hide Just for debugging; not internationalized. */
Mathew Inwood31755f92018-12-20 13:53:36 +0000315 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
Dianne Hackborn1ebccf52010-08-15 13:04:34 -0700316 public static void formatDuration(long duration, PrintWriter pw) {
Dianne Hackbornb5e31652010-09-07 12:13:55 -0700317 formatDuration(duration, pw, 0);
Dianne Hackborn1ebccf52010-08-15 13:04:34 -0700318 }
Jesse Wilsona0f8bc52011-02-24 10:44:33 -0800319
Dianne Hackborn1ebccf52010-08-15 13:04:34 -0700320 /** @hide Just for debugging; not internationalized. */
321 public static void formatDuration(long time, long now, PrintWriter pw) {
322 if (time == 0) {
323 pw.print("--");
324 return;
325 }
Dianne Hackbornb5e31652010-09-07 12:13:55 -0700326 formatDuration(time-now, pw, 0);
Dianne Hackborn1ebccf52010-08-15 13:04:34 -0700327 }
Wink Saville26e1a022012-05-10 16:23:39 -0700328
Jeff Brown96307042012-07-27 15:51:34 -0700329 /** @hide Just for debugging; not internationalized. */
330 public static String formatUptime(long time) {
331 final long diff = time - SystemClock.uptimeMillis();
332 if (diff > 0) {
333 return time + " (in " + diff + " ms)";
334 }
335 if (diff < 0) {
336 return time + " (" + -diff + " ms ago)";
337 }
338 return time + " (now)";
339 }
340
Wink Saville26e1a022012-05-10 16:23:39 -0700341 /**
342 * Convert a System.currentTimeMillis() value to a time of day value like
343 * that printed in logs. MM-DD HH:MM:SS.MMM
344 *
345 * @param millis since the epoch (1/1/1970)
346 * @return String representation of the time.
347 * @hide
348 */
Mathew Inwoodb4075682018-08-14 17:32:44 +0100349 @UnsupportedAppUsage
Wink Saville26e1a022012-05-10 16:23:39 -0700350 public static String logTimeOfDay(long millis) {
351 Calendar c = Calendar.getInstance();
352 if (millis >= 0) {
353 c.setTimeInMillis(millis);
354 return String.format("%tm-%td %tH:%tM:%tS.%tL", c, c, c, c, c, c);
355 } else {
356 return Long.toString(millis);
357 }
358 }
Jeff Sharkeye8a4b662015-06-27 15:43:45 -0700359
360 /** {@hide} */
361 public static String formatForLogging(long millis) {
362 if (millis <= 0) {
363 return "unknown";
364 } else {
365 return sLoggingFormat.format(new Date(millis));
366 }
367 }
Makoto Onukic279f2b2019-01-09 13:11:37 -0800368
369 /**
370 * Dump a currentTimeMillis style timestamp for dumpsys.
371 *
372 * @hide
373 */
374 public static void dumpTime(PrintWriter pw, long time) {
375 pw.print(sDumpDateFormat.format(new Date(time)));
376 }
377
378 /**
379 * Dump a currentTimeMillis style timestamp for dumpsys, with the delta time from now.
380 *
381 * @hide
382 */
383 public static void dumpTimeWithDelta(PrintWriter pw, long time, long now) {
384 pw.print(sDumpDateFormat.format(new Date(time)));
385 if (time == now) {
386 pw.print(" (now)");
387 } else {
388 pw.print(" (");
389 TimeUtils.formatDuration(time, now, pw);
390 pw.print(")");
391 }
392 }}