blob: 91bc3eb5f83aa143aeb52799d0aea50390e1ef86 [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;
Mathew Inwood55418ea2018-12-20 15:30:45 +000022import android.os.Build;
Jeff Brown96307042012-07-27 15:51:34 -070023import android.os.SystemClock;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080024
Neil Fuller69ffbd62018-11-16 21:14:13 +000025import libcore.timezone.CountryTimeZones;
26import libcore.timezone.CountryTimeZones.TimeZoneMapping;
27import libcore.timezone.TimeZoneFinder;
Neil Fullerb8383a12018-11-16 21:46:33 +000028import libcore.timezone.ZoneInfoDB;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080029
Neil Fuller11351b62018-01-09 18:38:08 +000030import java.io.PrintWriter;
31import java.text.SimpleDateFormat;
Neil Fuller6caa9542018-10-03 13:59:29 +010032import java.util.ArrayList;
Neil Fuller11351b62018-01-09 18:38:08 +000033import java.util.Calendar;
Neil Fuller6caa9542018-10-03 13:59:29 +010034import java.util.Collections;
Neil Fuller11351b62018-01-09 18:38:08 +000035import java.util.Date;
Neil Fuller6caa9542018-10-03 13:59:29 +010036import java.util.List;
37
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080038/**
39 * A class containing utility methods related to time zones.
40 */
41public class TimeUtils {
Jesse Wilsona0f8bc52011-02-24 10:44:33 -080042 /** @hide */ public TimeUtils() {}
Jeff Sharkeye8a4b662015-06-27 15:43:45 -070043 /** {@hide} */
44 private static SimpleDateFormat sLoggingFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
Wink Savillea27421a2012-03-01 10:25:48 -080045
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080046 /**
47 * Tries to return a time zone that would have had the specified offset
48 * and DST value at the specified moment in the specified country.
49 * Returns null if no suitable zone could be found.
50 */
Neil Fuller1e49f6c2017-03-31 13:05:01 +010051 public static java.util.TimeZone getTimeZone(
52 int offset, boolean dst, long when, String country) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080053
Neil Fuller1e49f6c2017-03-31 13:05:01 +010054 android.icu.util.TimeZone icuTimeZone = getIcuTimeZone(offset, dst, when, country);
55 // We must expose a java.util.TimeZone here for API compatibility because this is a public
56 // API method.
57 return icuTimeZone != null ? java.util.TimeZone.getTimeZone(icuTimeZone.getID()) : null;
Wink Savillea27421a2012-03-01 10:25:48 -080058 }
59
60 /**
Neil Fuller1e49f6c2017-03-31 13:05:01 +010061 * Tries to return a frozen ICU time zone that would have had the specified offset
62 * and DST value at the specified moment in the specified country.
63 * Returns null if no suitable zone could be found.
64 */
65 private static android.icu.util.TimeZone getIcuTimeZone(
66 int offset, boolean dst, long when, String country) {
67 if (country == null) {
68 return null;
69 }
70
71 android.icu.util.TimeZone bias = android.icu.util.TimeZone.getDefault();
72 return TimeZoneFinder.getInstance()
73 .lookupTimeZoneByCountryAndOffset(country, offset, dst, when, bias);
74 }
75
76 /**
Neil Fuller6caa9542018-10-03 13:59:29 +010077 * Returns time zone IDs for time zones known to be associated with a country.
78 *
79 * <p>The list returned may be different from other on-device sources like
80 * {@link android.icu.util.TimeZone#getRegion(String)} as it can be curated to avoid
81 * contentious mappings.
82 *
83 * @param countryCode the ISO 3166-1 alpha-2 code for the country as can be obtained using
84 * {@link java.util.Locale#getCountry()}
85 * @return IDs that can be passed to {@link java.util.TimeZone#getTimeZone(String)} or similar
86 * methods, or {@code null} if the countryCode is unrecognized
87 */
88 public static @Nullable List<String> getTimeZoneIdsForCountryCode(@NonNull String countryCode) {
89 if (countryCode == null) {
90 throw new NullPointerException("countryCode == null");
91 }
92 TimeZoneFinder timeZoneFinder = TimeZoneFinder.getInstance();
93 CountryTimeZones countryTimeZones =
94 timeZoneFinder.lookupCountryTimeZones(countryCode.toLowerCase());
95 if (countryTimeZones == null) {
96 return null;
97 }
98
99 List<String> timeZoneIds = new ArrayList<>();
100 for (TimeZoneMapping timeZoneMapping : countryTimeZones.getTimeZoneMappings()) {
101 if (timeZoneMapping.showInPicker) {
102 timeZoneIds.add(timeZoneMapping.timeZoneId);
103 }
104 }
105 return Collections.unmodifiableList(timeZoneIds);
106 }
107
108 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800109 * Returns a String indicating the version of the time zone database currently
110 * in use. The format of the string is dependent on the underlying time zone
111 * database implementation, but will typically contain the year in which the database
112 * was updated plus a letter from a to z indicating changes made within that year.
Jesse Wilsona0f8bc52011-02-24 10:44:33 -0800113 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800114 * <p>Time zone database updates should be expected to occur periodically due to
115 * political and legal changes that cannot be anticipated in advance. Therefore,
116 * when computing the UTC time for a future event, applications should be aware that
117 * the results may differ following a time zone database update. This method allows
118 * applications to detect that a database change has occurred, and to recalculate any
119 * cached times accordingly.
Jesse Wilsona0f8bc52011-02-24 10:44:33 -0800120 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800121 * <p>The time zone database may be assumed to change only when the device runtime
122 * is restarted. Therefore, it is not necessary to re-query the database version
123 * during the lifetime of an activity.
124 */
125 public static String getTimeZoneDatabaseVersion() {
Elliott Hughes48289432013-04-26 11:30:54 -0700126 return ZoneInfoDB.getInstance().getVersion();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800127 }
Dianne Hackborn1ebccf52010-08-15 13:04:34 -0700128
Dianne Hackbornb5e31652010-09-07 12:13:55 -0700129 /** @hide Field length that can hold 999 days of time */
130 public static final int HUNDRED_DAY_FIELD_LEN = 19;
Jesse Wilsona0f8bc52011-02-24 10:44:33 -0800131
Dianne Hackborn1ebccf52010-08-15 13:04:34 -0700132 private static final int SECONDS_PER_MINUTE = 60;
133 private static final int SECONDS_PER_HOUR = 60 * 60;
134 private static final int SECONDS_PER_DAY = 24 * 60 * 60;
135
John Reck315c3292014-05-09 19:21:04 -0700136 /** @hide */
137 public static final long NANOS_PER_MS = 1000000;
138
Dianne Hackbornb5e31652010-09-07 12:13:55 -0700139 private static final Object sFormatSync = new Object();
Dianne Hackborn3d1933c42015-06-10 16:25:57 -0700140 private static char[] sFormatStr = new char[HUNDRED_DAY_FIELD_LEN+10];
141 private static char[] sTmpFormatStr = new char[HUNDRED_DAY_FIELD_LEN+10];
Jeff Sharkeyeaaf3962012-07-21 12:37:26 -0700142
Dianne Hackbornb5e31652010-09-07 12:13:55 -0700143 static private int accumField(int amt, int suffix, boolean always, int zeropad) {
Dianne Hackborn3d1933c42015-06-10 16:25:57 -0700144 if (amt > 999) {
145 int num = 0;
146 while (amt != 0) {
147 num++;
148 amt /= 10;
149 }
150 return num + suffix;
151 } else {
152 if (amt > 99 || (always && zeropad >= 3)) {
153 return 3+suffix;
154 }
155 if (amt > 9 || (always && zeropad >= 2)) {
156 return 2+suffix;
157 }
158 if (always || amt > 0) {
159 return 1+suffix;
160 }
Dianne Hackbornb5e31652010-09-07 12:13:55 -0700161 }
162 return 0;
163 }
Jesse Wilsona0f8bc52011-02-24 10:44:33 -0800164
Dianne Hackborn3d1933c42015-06-10 16:25:57 -0700165 static private int printFieldLocked(char[] formatStr, int amt, char suffix, int pos,
Dianne Hackbornb5e31652010-09-07 12:13:55 -0700166 boolean always, int zeropad) {
167 if (always || amt > 0) {
Bjorn Bringert901b3792010-11-23 14:43:12 +0000168 final int startPos = pos;
Dianne Hackborn3d1933c42015-06-10 16:25:57 -0700169 if (amt > 999) {
170 int tmp = 0;
171 while (amt != 0 && tmp < sTmpFormatStr.length) {
172 int dig = amt % 10;
173 sTmpFormatStr[tmp] = (char)(dig + '0');
174 tmp++;
175 amt /= 10;
176 }
177 tmp--;
178 while (tmp >= 0) {
179 formatStr[pos] = sTmpFormatStr[tmp];
180 pos++;
181 tmp--;
182 }
183 } else {
184 if ((always && zeropad >= 3) || amt > 99) {
185 int dig = amt/100;
186 formatStr[pos] = (char)(dig + '0');
187 pos++;
188 amt -= (dig*100);
189 }
190 if ((always && zeropad >= 2) || amt > 9 || startPos != pos) {
191 int dig = amt/10;
192 formatStr[pos] = (char)(dig + '0');
193 pos++;
194 amt -= (dig*10);
195 }
196 formatStr[pos] = (char)(amt + '0');
Dianne Hackbornb5e31652010-09-07 12:13:55 -0700197 pos++;
Dianne Hackbornb5e31652010-09-07 12:13:55 -0700198 }
Dianne Hackbornb5e31652010-09-07 12:13:55 -0700199 formatStr[pos] = suffix;
200 pos++;
201 }
202 return pos;
203 }
Jesse Wilsona0f8bc52011-02-24 10:44:33 -0800204
Dianne Hackbornb5e31652010-09-07 12:13:55 -0700205 private static int formatDurationLocked(long duration, int fieldLen) {
206 if (sFormatStr.length < fieldLen) {
207 sFormatStr = new char[fieldLen];
208 }
Jesse Wilsona0f8bc52011-02-24 10:44:33 -0800209
Dianne Hackbornb5e31652010-09-07 12:13:55 -0700210 char[] formatStr = sFormatStr;
Jesse Wilsona0f8bc52011-02-24 10:44:33 -0800211
Dianne Hackbornb5e31652010-09-07 12:13:55 -0700212 if (duration == 0) {
213 int pos = 0;
214 fieldLen -= 1;
215 while (pos < fieldLen) {
Jozef BABJAK47f13e72011-02-22 07:20:30 +0100216 formatStr[pos++] = ' ';
Dianne Hackbornb5e31652010-09-07 12:13:55 -0700217 }
218 formatStr[pos] = '0';
219 return pos+1;
220 }
Jesse Wilsona0f8bc52011-02-24 10:44:33 -0800221
Dianne Hackbornb5e31652010-09-07 12:13:55 -0700222 char prefix;
Dianne Hackborn1ebccf52010-08-15 13:04:34 -0700223 if (duration > 0) {
Dianne Hackbornb5e31652010-09-07 12:13:55 -0700224 prefix = '+';
Dianne Hackborn1ebccf52010-08-15 13:04:34 -0700225 } else {
Dianne Hackbornb5e31652010-09-07 12:13:55 -0700226 prefix = '-';
Dianne Hackborn1ebccf52010-08-15 13:04:34 -0700227 duration = -duration;
228 }
229
230 int millis = (int)(duration%1000);
231 int seconds = (int) Math.floor(duration / 1000);
232 int days = 0, hours = 0, minutes = 0;
233
Mitchell Willsf9a80cb2015-09-22 15:30:08 -0700234 if (seconds >= SECONDS_PER_DAY) {
Dianne Hackborn1ebccf52010-08-15 13:04:34 -0700235 days = seconds / SECONDS_PER_DAY;
236 seconds -= days * SECONDS_PER_DAY;
237 }
Mitchell Willsf9a80cb2015-09-22 15:30:08 -0700238 if (seconds >= SECONDS_PER_HOUR) {
Dianne Hackborn1ebccf52010-08-15 13:04:34 -0700239 hours = seconds / SECONDS_PER_HOUR;
240 seconds -= hours * SECONDS_PER_HOUR;
241 }
Mitchell Willsf9a80cb2015-09-22 15:30:08 -0700242 if (seconds >= SECONDS_PER_MINUTE) {
Dianne Hackborn1ebccf52010-08-15 13:04:34 -0700243 minutes = seconds / SECONDS_PER_MINUTE;
244 seconds -= minutes * SECONDS_PER_MINUTE;
245 }
246
Dianne Hackbornb5e31652010-09-07 12:13:55 -0700247 int pos = 0;
Jesse Wilsona0f8bc52011-02-24 10:44:33 -0800248
Dianne Hackbornb5e31652010-09-07 12:13:55 -0700249 if (fieldLen != 0) {
250 int myLen = accumField(days, 1, false, 0);
251 myLen += accumField(hours, 1, myLen > 0, 2);
252 myLen += accumField(minutes, 1, myLen > 0, 2);
253 myLen += accumField(seconds, 1, myLen > 0, 2);
254 myLen += accumField(millis, 2, true, myLen > 0 ? 3 : 0) + 1;
255 while (myLen < fieldLen) {
256 formatStr[pos] = ' ';
257 pos++;
258 myLen++;
259 }
Dianne Hackborn1ebccf52010-08-15 13:04:34 -0700260 }
Jesse Wilsona0f8bc52011-02-24 10:44:33 -0800261
Dianne Hackbornb5e31652010-09-07 12:13:55 -0700262 formatStr[pos] = prefix;
263 pos++;
Jesse Wilsona0f8bc52011-02-24 10:44:33 -0800264
Dianne Hackbornb5e31652010-09-07 12:13:55 -0700265 int start = pos;
266 boolean zeropad = fieldLen != 0;
Dianne Hackborn3d1933c42015-06-10 16:25:57 -0700267 pos = printFieldLocked(formatStr, days, 'd', pos, false, 0);
268 pos = printFieldLocked(formatStr, hours, 'h', pos, pos != start, zeropad ? 2 : 0);
269 pos = printFieldLocked(formatStr, minutes, 'm', pos, pos != start, zeropad ? 2 : 0);
270 pos = printFieldLocked(formatStr, seconds, 's', pos, pos != start, zeropad ? 2 : 0);
271 pos = printFieldLocked(formatStr, millis, 'm', pos, true, (zeropad && pos != start) ? 3 : 0);
Dianne Hackbornb5e31652010-09-07 12:13:55 -0700272 formatStr[pos] = 's';
273 return pos + 1;
274 }
Jesse Wilsona0f8bc52011-02-24 10:44:33 -0800275
Dianne Hackbornb5e31652010-09-07 12:13:55 -0700276 /** @hide Just for debugging; not internationalized. */
277 public static void formatDuration(long duration, StringBuilder builder) {
278 synchronized (sFormatSync) {
279 int len = formatDurationLocked(duration, 0);
280 builder.append(sFormatStr, 0, len);
Dianne Hackborn1ebccf52010-08-15 13:04:34 -0700281 }
Dianne Hackbornb5e31652010-09-07 12:13:55 -0700282 }
283
284 /** @hide Just for debugging; not internationalized. */
Kweku Adams71a95312018-04-16 16:54:24 -0700285 public static void formatDuration(long duration, StringBuilder builder, int fieldLen) {
286 synchronized (sFormatSync) {
287 int len = formatDurationLocked(duration, fieldLen);
288 builder.append(sFormatStr, 0, len);
289 }
290 }
291
292 /** @hide Just for debugging; not internationalized. */
Mathew Inwood55418ea2018-12-20 15:30:45 +0000293 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
Dianne Hackbornb5e31652010-09-07 12:13:55 -0700294 public static void formatDuration(long duration, PrintWriter pw, int fieldLen) {
295 synchronized (sFormatSync) {
296 int len = formatDurationLocked(duration, fieldLen);
297 pw.print(new String(sFormatStr, 0, len));
Dianne Hackborn1ebccf52010-08-15 13:04:34 -0700298 }
Dianne Hackborn1ebccf52010-08-15 13:04:34 -0700299 }
300
301 /** @hide Just for debugging; not internationalized. */
Jeff Sharkeyd0fff2e2017-11-07 16:55:06 -0700302 public static String formatDuration(long duration) {
303 synchronized (sFormatSync) {
304 int len = formatDurationLocked(duration, 0);
305 return new String(sFormatStr, 0, len);
306 }
307 }
308
309 /** @hide Just for debugging; not internationalized. */
Mathew Inwood55418ea2018-12-20 15:30:45 +0000310 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
Dianne Hackborn1ebccf52010-08-15 13:04:34 -0700311 public static void formatDuration(long duration, PrintWriter pw) {
Dianne Hackbornb5e31652010-09-07 12:13:55 -0700312 formatDuration(duration, pw, 0);
Dianne Hackborn1ebccf52010-08-15 13:04:34 -0700313 }
Jesse Wilsona0f8bc52011-02-24 10:44:33 -0800314
Dianne Hackborn1ebccf52010-08-15 13:04:34 -0700315 /** @hide Just for debugging; not internationalized. */
316 public static void formatDuration(long time, long now, PrintWriter pw) {
317 if (time == 0) {
318 pw.print("--");
319 return;
320 }
Dianne Hackbornb5e31652010-09-07 12:13:55 -0700321 formatDuration(time-now, pw, 0);
Dianne Hackborn1ebccf52010-08-15 13:04:34 -0700322 }
Wink Saville26e1a022012-05-10 16:23:39 -0700323
Jeff Brown96307042012-07-27 15:51:34 -0700324 /** @hide Just for debugging; not internationalized. */
325 public static String formatUptime(long time) {
326 final long diff = time - SystemClock.uptimeMillis();
327 if (diff > 0) {
328 return time + " (in " + diff + " ms)";
329 }
330 if (diff < 0) {
331 return time + " (" + -diff + " ms ago)";
332 }
333 return time + " (now)";
334 }
335
Wink Saville26e1a022012-05-10 16:23:39 -0700336 /**
337 * Convert a System.currentTimeMillis() value to a time of day value like
338 * that printed in logs. MM-DD HH:MM:SS.MMM
339 *
340 * @param millis since the epoch (1/1/1970)
341 * @return String representation of the time.
342 * @hide
343 */
Mathew Inwoodb4075682018-08-14 17:32:44 +0100344 @UnsupportedAppUsage
Wink Saville26e1a022012-05-10 16:23:39 -0700345 public static String logTimeOfDay(long millis) {
346 Calendar c = Calendar.getInstance();
347 if (millis >= 0) {
348 c.setTimeInMillis(millis);
349 return String.format("%tm-%td %tH:%tM:%tS.%tL", c, c, c, c, c, c);
350 } else {
351 return Long.toString(millis);
352 }
353 }
Jeff Sharkeye8a4b662015-06-27 15:43:45 -0700354
355 /** {@hide} */
356 public static String formatForLogging(long millis) {
357 if (millis <= 0) {
358 return "unknown";
359 } else {
360 return sLoggingFormat.format(new Date(millis));
361 }
362 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800363}