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.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.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 * 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 */ getIcuTimeZone( int offset, boolean dst, long when, String country)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 /** 81 * 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 */ getTimeZoneIdsForCountryCode(@onNull String countryCode)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 /** 113 * 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. 117 * 118 * <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. 124 * 125 * <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 */ getTimeZoneDatabaseVersion()129 public static String getTimeZoneDatabaseVersion() { 130 return ZoneInfoDB.getInstance().getVersion(); 131 } 132 133 /** @hide Field length that can hold 999 days of time */ 134 public static final int HUNDRED_DAY_FIELD_LEN = 19; 135 136 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 140 /** @hide */ 141 public static final long NANOS_PER_MS = 1000000; 142 143 private static final Object sFormatSync = new Object(); 144 private static char[] sFormatStr = new char[HUNDRED_DAY_FIELD_LEN+10]; 145 private static char[] sTmpFormatStr = new char[HUNDRED_DAY_FIELD_LEN+10]; 146 accumField(int amt, int suffix, boolean always, int zeropad)147 static private int accumField(int amt, int suffix, boolean always, int zeropad) { 148 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 } 165 } 166 return 0; 167 } 168 printFieldLocked(char[] formatStr, int amt, char suffix, int pos, boolean always, int zeropad)169 static private int printFieldLocked(char[] formatStr, int amt, char suffix, int pos, 170 boolean always, int zeropad) { 171 if (always || amt > 0) { 172 final int startPos = pos; 173 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'); 201 pos++; 202 } 203 formatStr[pos] = suffix; 204 pos++; 205 } 206 return pos; 207 } 208 formatDurationLocked(long duration, int fieldLen)209 private static int formatDurationLocked(long duration, int fieldLen) { 210 if (sFormatStr.length < fieldLen) { 211 sFormatStr = new char[fieldLen]; 212 } 213 214 char[] formatStr = sFormatStr; 215 216 if (duration == 0) { 217 int pos = 0; 218 fieldLen -= 1; 219 while (pos < fieldLen) { 220 formatStr[pos++] = ' '; 221 } 222 formatStr[pos] = '0'; 223 return pos+1; 224 } 225 226 char prefix; 227 if (duration > 0) { 228 prefix = '+'; 229 } else { 230 prefix = '-'; 231 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 238 if (seconds >= SECONDS_PER_DAY) { 239 days = seconds / SECONDS_PER_DAY; 240 seconds -= days * SECONDS_PER_DAY; 241 } 242 if (seconds >= SECONDS_PER_HOUR) { 243 hours = seconds / SECONDS_PER_HOUR; 244 seconds -= hours * SECONDS_PER_HOUR; 245 } 246 if (seconds >= SECONDS_PER_MINUTE) { 247 minutes = seconds / SECONDS_PER_MINUTE; 248 seconds -= minutes * SECONDS_PER_MINUTE; 249 } 250 251 int pos = 0; 252 253 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 } 264 } 265 266 formatStr[pos] = prefix; 267 pos++; 268 269 int start = pos; 270 boolean zeropad = fieldLen != 0; 271 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); 276 formatStr[pos] = 's'; 277 return pos + 1; 278 } 279 280 /** @hide Just for debugging; not internationalized. */ formatDuration(long duration, StringBuilder builder)281 public static void formatDuration(long duration, StringBuilder builder) { 282 synchronized (sFormatSync) { 283 int len = formatDurationLocked(duration, 0); 284 builder.append(sFormatStr, 0, len); 285 } 286 } 287 288 /** @hide Just for debugging; not internationalized. */ formatDuration(long duration, StringBuilder builder, int fieldLen)289 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. */ 297 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) formatDuration(long duration, PrintWriter pw, int fieldLen)298 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)); 302 } 303 } 304 305 /** @hide Just for debugging; not internationalized. */ 306 @TestApi formatDuration(long duration)307 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. */ 315 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) formatDuration(long duration, PrintWriter pw)316 public static void formatDuration(long duration, PrintWriter pw) { 317 formatDuration(duration, pw, 0); 318 } 319 320 /** @hide Just for debugging; not internationalized. */ formatDuration(long time, long now, PrintWriter pw)321 public static void formatDuration(long time, long now, PrintWriter pw) { 322 if (time == 0) { 323 pw.print("--"); 324 return; 325 } 326 formatDuration(time-now, pw, 0); 327 } 328 329 /** @hide Just for debugging; not internationalized. */ formatUptime(long time)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 341 /** 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 */ 349 @UnsupportedAppUsage logTimeOfDay(long millis)350 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 } 359 360 /** {@hide} */ formatForLogging(long millis)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 } 368 369 /** 370 * Dump a currentTimeMillis style timestamp for dumpsys. 371 * 372 * @hide 373 */ dumpTime(PrintWriter pw, long time)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 */ dumpTimeWithDelta(PrintWriter pw, long time, long now)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 }} 393