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.annotation.NonNull; 20 import android.annotation.Nullable; 21 import android.annotation.TestApi; 22 import android.compat.annotation.UnsupportedAppUsage; 23 import android.os.Build; 24 import android.os.SystemClock; 25 26 import libcore.timezone.CountryTimeZones; 27 import libcore.timezone.CountryTimeZones.TimeZoneMapping; 28 import libcore.timezone.TimeZoneFinder; 29 import libcore.timezone.ZoneInfoDb; 30 31 import java.io.PrintWriter; 32 import java.text.SimpleDateFormat; 33 import java.time.LocalTime; 34 import java.util.ArrayList; 35 import java.util.Calendar; 36 import java.util.Collections; 37 import java.util.Date; 38 import java.util.List; 39 40 /** 41 * A class containing utility methods related to time zones. 42 */ 43 public class TimeUtils { TimeUtils()44 /** @hide */ public TimeUtils() {} 45 /** {@hide} */ 46 private static SimpleDateFormat sLoggingFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); 47 48 /** @hide */ 49 public static final SimpleDateFormat sDumpDateFormat = 50 new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS"); 51 /** 52 * Tries to return a time zone that would have had the specified offset 53 * and DST value at the specified moment in the specified country. 54 * Returns null if no suitable zone could be found. 55 */ getTimeZone( int offset, boolean dst, long when, String country)56 public static java.util.TimeZone getTimeZone( 57 int offset, boolean dst, long when, String country) { 58 59 android.icu.util.TimeZone icuTimeZone = getIcuTimeZone(offset, dst, when, country); 60 // We must expose a java.util.TimeZone here for API compatibility because this is a public 61 // API method. 62 return icuTimeZone != null ? java.util.TimeZone.getTimeZone(icuTimeZone.getID()) : null; 63 } 64 65 /** 66 * Returns a frozen ICU time zone that has / would have had the specified offset and DST value 67 * at the specified moment in the specified country. Returns null if no suitable zone could be 68 * found. 69 */ getIcuTimeZone( int offsetMillis, boolean isDst, long whenMillis, String countryIso)70 private static android.icu.util.TimeZone getIcuTimeZone( 71 int offsetMillis, boolean isDst, long whenMillis, String countryIso) { 72 if (countryIso == null) { 73 return null; 74 } 75 76 android.icu.util.TimeZone bias = android.icu.util.TimeZone.getDefault(); 77 CountryTimeZones countryTimeZones = 78 TimeZoneFinder.getInstance().lookupCountryTimeZones(countryIso); 79 if (countryTimeZones == null) { 80 return null; 81 } 82 CountryTimeZones.OffsetResult offsetResult = countryTimeZones.lookupByOffsetWithBias( 83 whenMillis, bias, offsetMillis, isDst); 84 return offsetResult != null ? offsetResult.getTimeZone() : null; 85 } 86 87 /** 88 * Returns time zone IDs for time zones known to be associated with a country. 89 * 90 * <p>The list returned may be different from other on-device sources like 91 * {@link android.icu.util.TimeZone#getRegion(String)} as it can be curated to avoid 92 * contentious or obsolete mappings. 93 * 94 * @param countryCode the ISO 3166-1 alpha-2 code for the country as can be obtained using 95 * {@link java.util.Locale#getCountry()} 96 * @return IDs that can be passed to {@link java.util.TimeZone#getTimeZone(String)} or similar 97 * methods, or {@code null} if the countryCode is unrecognized 98 */ getTimeZoneIdsForCountryCode(@onNull String countryCode)99 public static @Nullable List<String> getTimeZoneIdsForCountryCode(@NonNull String countryCode) { 100 if (countryCode == null) { 101 throw new NullPointerException("countryCode == null"); 102 } 103 TimeZoneFinder timeZoneFinder = TimeZoneFinder.getInstance(); 104 CountryTimeZones countryTimeZones = 105 timeZoneFinder.lookupCountryTimeZones(countryCode.toLowerCase()); 106 if (countryTimeZones == null) { 107 return null; 108 } 109 110 List<String> timeZoneIds = new ArrayList<>(); 111 for (TimeZoneMapping timeZoneMapping : countryTimeZones.getTimeZoneMappings()) { 112 if (timeZoneMapping.isShownInPicker()) { 113 timeZoneIds.add(timeZoneMapping.getTimeZoneId()); 114 } 115 } 116 return Collections.unmodifiableList(timeZoneIds); 117 } 118 119 /** 120 * Returns a String indicating the version of the time zone database currently 121 * in use. The format of the string is dependent on the underlying time zone 122 * database implementation, but will typically contain the year in which the database 123 * was updated plus a letter from a to z indicating changes made within that year. 124 * 125 * <p>Time zone database updates should be expected to occur periodically due to 126 * political and legal changes that cannot be anticipated in advance. Therefore, 127 * when computing the UTC time for a future event, applications should be aware that 128 * the results may differ following a time zone database update. This method allows 129 * applications to detect that a database change has occurred, and to recalculate any 130 * cached times accordingly. 131 * 132 * <p>The time zone database may be assumed to change only when the device runtime 133 * is restarted. Therefore, it is not necessary to re-query the database version 134 * during the lifetime of an activity. 135 */ getTimeZoneDatabaseVersion()136 public static String getTimeZoneDatabaseVersion() { 137 return ZoneInfoDb.getInstance().getVersion(); 138 } 139 140 /** @hide Field length that can hold 999 days of time */ 141 public static final int HUNDRED_DAY_FIELD_LEN = 19; 142 143 private static final int SECONDS_PER_MINUTE = 60; 144 private static final int SECONDS_PER_HOUR = 60 * 60; 145 private static final int SECONDS_PER_DAY = 24 * 60 * 60; 146 147 /** @hide */ 148 public static final long NANOS_PER_MS = 1000000; 149 150 private static final Object sFormatSync = new Object(); 151 private static char[] sFormatStr = new char[HUNDRED_DAY_FIELD_LEN+10]; 152 private static char[] sTmpFormatStr = new char[HUNDRED_DAY_FIELD_LEN+10]; 153 accumField(int amt, int suffix, boolean always, int zeropad)154 static private int accumField(int amt, int suffix, boolean always, int zeropad) { 155 if (amt > 999) { 156 int num = 0; 157 while (amt != 0) { 158 num++; 159 amt /= 10; 160 } 161 return num + suffix; 162 } else { 163 if (amt > 99 || (always && zeropad >= 3)) { 164 return 3+suffix; 165 } 166 if (amt > 9 || (always && zeropad >= 2)) { 167 return 2+suffix; 168 } 169 if (always || amt > 0) { 170 return 1+suffix; 171 } 172 } 173 return 0; 174 } 175 printFieldLocked(char[] formatStr, int amt, char suffix, int pos, boolean always, int zeropad)176 static private int printFieldLocked(char[] formatStr, int amt, char suffix, int pos, 177 boolean always, int zeropad) { 178 if (always || amt > 0) { 179 final int startPos = pos; 180 if (amt > 999) { 181 int tmp = 0; 182 while (amt != 0 && tmp < sTmpFormatStr.length) { 183 int dig = amt % 10; 184 sTmpFormatStr[tmp] = (char)(dig + '0'); 185 tmp++; 186 amt /= 10; 187 } 188 tmp--; 189 while (tmp >= 0) { 190 formatStr[pos] = sTmpFormatStr[tmp]; 191 pos++; 192 tmp--; 193 } 194 } else { 195 if ((always && zeropad >= 3) || amt > 99) { 196 int dig = amt/100; 197 formatStr[pos] = (char)(dig + '0'); 198 pos++; 199 amt -= (dig*100); 200 } 201 if ((always && zeropad >= 2) || amt > 9 || startPos != pos) { 202 int dig = amt/10; 203 formatStr[pos] = (char)(dig + '0'); 204 pos++; 205 amt -= (dig*10); 206 } 207 formatStr[pos] = (char)(amt + '0'); 208 pos++; 209 } 210 formatStr[pos] = suffix; 211 pos++; 212 } 213 return pos; 214 } 215 formatDurationLocked(long duration, int fieldLen)216 private static int formatDurationLocked(long duration, int fieldLen) { 217 if (sFormatStr.length < fieldLen) { 218 sFormatStr = new char[fieldLen]; 219 } 220 221 char[] formatStr = sFormatStr; 222 223 if (duration == 0) { 224 int pos = 0; 225 fieldLen -= 1; 226 while (pos < fieldLen) { 227 formatStr[pos++] = ' '; 228 } 229 formatStr[pos] = '0'; 230 return pos+1; 231 } 232 233 char prefix; 234 if (duration > 0) { 235 prefix = '+'; 236 } else { 237 prefix = '-'; 238 duration = -duration; 239 } 240 241 int millis = (int)(duration%1000); 242 int seconds = (int) Math.floor(duration / 1000); 243 int days = 0, hours = 0, minutes = 0; 244 245 if (seconds >= SECONDS_PER_DAY) { 246 days = seconds / SECONDS_PER_DAY; 247 seconds -= days * SECONDS_PER_DAY; 248 } 249 if (seconds >= SECONDS_PER_HOUR) { 250 hours = seconds / SECONDS_PER_HOUR; 251 seconds -= hours * SECONDS_PER_HOUR; 252 } 253 if (seconds >= SECONDS_PER_MINUTE) { 254 minutes = seconds / SECONDS_PER_MINUTE; 255 seconds -= minutes * SECONDS_PER_MINUTE; 256 } 257 258 int pos = 0; 259 260 if (fieldLen != 0) { 261 int myLen = accumField(days, 1, false, 0); 262 myLen += accumField(hours, 1, myLen > 0, 2); 263 myLen += accumField(minutes, 1, myLen > 0, 2); 264 myLen += accumField(seconds, 1, myLen > 0, 2); 265 myLen += accumField(millis, 2, true, myLen > 0 ? 3 : 0) + 1; 266 while (myLen < fieldLen) { 267 formatStr[pos] = ' '; 268 pos++; 269 myLen++; 270 } 271 } 272 273 formatStr[pos] = prefix; 274 pos++; 275 276 int start = pos; 277 boolean zeropad = fieldLen != 0; 278 pos = printFieldLocked(formatStr, days, 'd', pos, false, 0); 279 pos = printFieldLocked(formatStr, hours, 'h', pos, pos != start, zeropad ? 2 : 0); 280 pos = printFieldLocked(formatStr, minutes, 'm', pos, pos != start, zeropad ? 2 : 0); 281 pos = printFieldLocked(formatStr, seconds, 's', pos, pos != start, zeropad ? 2 : 0); 282 pos = printFieldLocked(formatStr, millis, 'm', pos, true, (zeropad && pos != start) ? 3 : 0); 283 formatStr[pos] = 's'; 284 return pos + 1; 285 } 286 287 /** @hide Just for debugging; not internationalized. */ formatDuration(long duration, StringBuilder builder)288 public static void formatDuration(long duration, StringBuilder builder) { 289 synchronized (sFormatSync) { 290 int len = formatDurationLocked(duration, 0); 291 builder.append(sFormatStr, 0, len); 292 } 293 } 294 295 /** @hide Just for debugging; not internationalized. */ formatDuration(long duration, StringBuilder builder, int fieldLen)296 public static void formatDuration(long duration, StringBuilder builder, int fieldLen) { 297 synchronized (sFormatSync) { 298 int len = formatDurationLocked(duration, fieldLen); 299 builder.append(sFormatStr, 0, len); 300 } 301 } 302 303 /** @hide Just for debugging; not internationalized. */ 304 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) formatDuration(long duration, PrintWriter pw, int fieldLen)305 public static void formatDuration(long duration, PrintWriter pw, int fieldLen) { 306 synchronized (sFormatSync) { 307 int len = formatDurationLocked(duration, fieldLen); 308 pw.print(new String(sFormatStr, 0, len)); 309 } 310 } 311 312 /** @hide Just for debugging; not internationalized. */ 313 @TestApi formatDuration(long duration)314 public static String formatDuration(long duration) { 315 synchronized (sFormatSync) { 316 int len = formatDurationLocked(duration, 0); 317 return new String(sFormatStr, 0, len); 318 } 319 } 320 321 /** @hide Just for debugging; not internationalized. */ 322 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) formatDuration(long duration, PrintWriter pw)323 public static void formatDuration(long duration, PrintWriter pw) { 324 formatDuration(duration, pw, 0); 325 } 326 327 /** @hide Just for debugging; not internationalized. */ formatDuration(long time, long now, PrintWriter pw)328 public static void formatDuration(long time, long now, PrintWriter pw) { 329 if (time == 0) { 330 pw.print("--"); 331 return; 332 } 333 formatDuration(time-now, pw, 0); 334 } 335 336 /** @hide Just for debugging; not internationalized. */ formatUptime(long time)337 public static String formatUptime(long time) { 338 return formatTime(time, SystemClock.uptimeMillis()); 339 } 340 341 /** @hide Just for debugging; not internationalized. */ formatRealtime(long time)342 public static String formatRealtime(long time) { 343 return formatTime(time, SystemClock.elapsedRealtime()); 344 } 345 346 /** @hide Just for debugging; not internationalized. */ formatTime(long time, long referenceTime)347 public static String formatTime(long time, long referenceTime) { 348 long diff = time - referenceTime; 349 if (diff > 0) { 350 return time + " (in " + diff + " ms)"; 351 } 352 if (diff < 0) { 353 return time + " (" + -diff + " ms ago)"; 354 } 355 return time + " (now)"; 356 } 357 358 /** 359 * Convert a System.currentTimeMillis() value to a time of day value like 360 * that printed in logs. MM-DD HH:MM:SS.MMM 361 * 362 * @param millis since the epoch (1/1/1970) 363 * @return String representation of the time. 364 * @hide 365 */ 366 @UnsupportedAppUsage logTimeOfDay(long millis)367 public static String logTimeOfDay(long millis) { 368 Calendar c = Calendar.getInstance(); 369 if (millis >= 0) { 370 c.setTimeInMillis(millis); 371 return String.format("%tm-%td %tH:%tM:%tS.%tL", c, c, c, c, c, c); 372 } else { 373 return Long.toString(millis); 374 } 375 } 376 377 /** {@hide} */ formatForLogging(long millis)378 public static String formatForLogging(long millis) { 379 if (millis <= 0) { 380 return "unknown"; 381 } else { 382 return sLoggingFormat.format(new Date(millis)); 383 } 384 } 385 386 /** 387 * Dump a currentTimeMillis style timestamp for dumpsys. 388 * 389 * @hide 390 */ dumpTime(PrintWriter pw, long time)391 public static void dumpTime(PrintWriter pw, long time) { 392 pw.print(sDumpDateFormat.format(new Date(time))); 393 } 394 395 /** 396 * This method is used to find if a clock time is inclusively between two other clock times 397 * @param reference The time of the day we want check if it is between start and end 398 * @param start The start time reference 399 * @param end The end time 400 * @return true if the reference time is between the two clock times, and false otherwise. 401 */ isTimeBetween(@onNull LocalTime reference, @NonNull LocalTime start, @NonNull LocalTime end)402 public static boolean isTimeBetween(@NonNull LocalTime reference, 403 @NonNull LocalTime start, 404 @NonNull LocalTime end) { 405 // ////////E----+-----S//////// 406 if ((reference.isBefore(start) && reference.isAfter(end) 407 // -----+----S//////////E------ 408 || (reference.isBefore(end) && reference.isBefore(start) && start.isBefore(end)) 409 // ---------S//////////E---+--- 410 || (reference.isAfter(end) && reference.isAfter(start)) && start.isBefore(end))) { 411 return false; 412 } else { 413 return true; 414 } 415 } 416 417 /** 418 * Dump a currentTimeMillis style timestamp for dumpsys, with the delta time from now. 419 * 420 * @hide 421 */ dumpTimeWithDelta(PrintWriter pw, long time, long now)422 public static void dumpTimeWithDelta(PrintWriter pw, long time, long now) { 423 pw.print(sDumpDateFormat.format(new Date(time))); 424 if (time == now) { 425 pw.print(" (now)"); 426 } else { 427 pw.print(" ("); 428 TimeUtils.formatDuration(time, now, pw); 429 pw.print(")"); 430 } 431 }} 432