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