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