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.content.res.Resources; 20 import android.content.res.XmlResourceParser; 21 import android.os.SystemClock; 22 23 import com.android.internal.util.XmlUtils; 24 25 import org.xmlpull.v1.XmlPullParser; 26 import org.xmlpull.v1.XmlPullParserException; 27 28 import java.io.IOException; 29 import java.io.PrintWriter; 30 import java.text.SimpleDateFormat; 31 import java.util.ArrayList; 32 import java.util.Calendar; 33 import java.util.Collection; 34 import java.util.Date; 35 import java.util.TimeZone; 36 37 import libcore.util.ZoneInfoDB; 38 39 /** 40 * A class containing utility methods related to time zones. 41 */ 42 public class TimeUtils { TimeUtils()43 /** @hide */ public TimeUtils() {} 44 private static final boolean DBG = false; 45 private static final String TAG = "TimeUtils"; 46 47 /** Cached results of getTineZones */ 48 private static final Object sLastLockObj = new Object(); 49 private static ArrayList<TimeZone> sLastZones = null; 50 private static String sLastCountry = null; 51 52 /** Cached results of getTimeZonesWithUniqueOffsets */ 53 private static final Object sLastUniqueLockObj = new Object(); 54 private static ArrayList<TimeZone> sLastUniqueZoneOffsets = null; 55 private static String sLastUniqueCountry = null; 56 57 /** {@hide} */ 58 private static SimpleDateFormat sLoggingFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); 59 60 /** 61 * Tries to return a 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 */ getTimeZone(int offset, boolean dst, long when, String country)65 public static TimeZone getTimeZone(int offset, boolean dst, long when, String country) { 66 TimeZone best = null; 67 final Date d = new Date(when); 68 69 TimeZone current = TimeZone.getDefault(); 70 String currentName = current.getID(); 71 int currentOffset = current.getOffset(when); 72 boolean currentDst = current.inDaylightTime(d); 73 74 for (TimeZone tz : getTimeZones(country)) { 75 // If the current time zone is from the right country 76 // and meets the other known properties, keep it 77 // instead of changing to another one. 78 79 if (tz.getID().equals(currentName)) { 80 if (currentOffset == offset && currentDst == dst) { 81 return current; 82 } 83 } 84 85 // Otherwise, take the first zone from the right 86 // country that has the correct current offset and DST. 87 // (Keep iterating instead of returning in case we 88 // haven't encountered the current time zone yet.) 89 90 if (best == null) { 91 if (tz.getOffset(when) == offset && 92 tz.inDaylightTime(d) == dst) { 93 best = tz; 94 } 95 } 96 } 97 98 return best; 99 } 100 101 /** 102 * Return list of unique time zones for the country. Do not modify 103 * 104 * @param country to find 105 * @return list of unique time zones, maybe empty but never null. Do not modify. 106 * @hide 107 */ getTimeZonesWithUniqueOffsets(String country)108 public static ArrayList<TimeZone> getTimeZonesWithUniqueOffsets(String country) { 109 synchronized(sLastUniqueLockObj) { 110 if ((country != null) && country.equals(sLastUniqueCountry)) { 111 if (DBG) { 112 Log.d(TAG, "getTimeZonesWithUniqueOffsets(" + 113 country + "): return cached version"); 114 } 115 return sLastUniqueZoneOffsets; 116 } 117 } 118 119 Collection<TimeZone> zones = getTimeZones(country); 120 ArrayList<TimeZone> uniqueTimeZones = new ArrayList<TimeZone>(); 121 for (TimeZone zone : zones) { 122 // See if we already have this offset, 123 // Using slow but space efficient and these are small. 124 boolean found = false; 125 for (int i = 0; i < uniqueTimeZones.size(); i++) { 126 if (uniqueTimeZones.get(i).getRawOffset() == zone.getRawOffset()) { 127 found = true; 128 break; 129 } 130 } 131 if (found == false) { 132 if (DBG) { 133 Log.d(TAG, "getTimeZonesWithUniqueOffsets: add unique offset=" + 134 zone.getRawOffset() + " zone.getID=" + zone.getID()); 135 } 136 uniqueTimeZones.add(zone); 137 } 138 } 139 140 synchronized(sLastUniqueLockObj) { 141 // Cache the last result 142 sLastUniqueZoneOffsets = uniqueTimeZones; 143 sLastUniqueCountry = country; 144 145 return sLastUniqueZoneOffsets; 146 } 147 } 148 149 /** 150 * Returns the time zones for the country, which is the code 151 * attribute of the timezone element in time_zones_by_country.xml. Do not modify. 152 * 153 * @param country is a two character country code. 154 * @return TimeZone list, maybe empty but never null. Do not modify. 155 * @hide 156 */ getTimeZones(String country)157 public static ArrayList<TimeZone> getTimeZones(String country) { 158 synchronized (sLastLockObj) { 159 if ((country != null) && country.equals(sLastCountry)) { 160 if (DBG) Log.d(TAG, "getTimeZones(" + country + "): return cached version"); 161 return sLastZones; 162 } 163 } 164 165 ArrayList<TimeZone> tzs = new ArrayList<TimeZone>(); 166 167 if (country == null) { 168 if (DBG) Log.d(TAG, "getTimeZones(null): return empty list"); 169 return tzs; 170 } 171 172 Resources r = Resources.getSystem(); 173 XmlResourceParser parser = r.getXml(com.android.internal.R.xml.time_zones_by_country); 174 175 try { 176 XmlUtils.beginDocument(parser, "timezones"); 177 178 while (true) { 179 XmlUtils.nextElement(parser); 180 181 String element = parser.getName(); 182 if (element == null || !(element.equals("timezone"))) { 183 break; 184 } 185 186 String code = parser.getAttributeValue(null, "code"); 187 188 if (country.equals(code)) { 189 if (parser.next() == XmlPullParser.TEXT) { 190 String zoneIdString = parser.getText(); 191 TimeZone tz = TimeZone.getTimeZone(zoneIdString); 192 if (tz.getID().startsWith("GMT") == false) { 193 // tz.getID doesn't start not "GMT" so its valid 194 tzs.add(tz); 195 if (DBG) { 196 Log.d(TAG, "getTimeZone('" + country + "'): found tz.getID==" 197 + ((tz != null) ? tz.getID() : "<no tz>")); 198 } 199 } 200 } 201 } 202 } 203 } catch (XmlPullParserException e) { 204 Log.e(TAG, "Got xml parser exception getTimeZone('" + country + "'): e=", e); 205 } catch (IOException e) { 206 Log.e(TAG, "Got IO exception getTimeZone('" + country + "'): e=", e); 207 } finally { 208 parser.close(); 209 } 210 211 synchronized(sLastLockObj) { 212 // Cache the last result; 213 sLastZones = tzs; 214 sLastCountry = country; 215 return sLastZones; 216 } 217 } 218 219 /** 220 * Returns a String indicating the version of the time zone database currently 221 * in use. The format of the string is dependent on the underlying time zone 222 * database implementation, but will typically contain the year in which the database 223 * was updated plus a letter from a to z indicating changes made within that year. 224 * 225 * <p>Time zone database updates should be expected to occur periodically due to 226 * political and legal changes that cannot be anticipated in advance. Therefore, 227 * when computing the UTC time for a future event, applications should be aware that 228 * the results may differ following a time zone database update. This method allows 229 * applications to detect that a database change has occurred, and to recalculate any 230 * cached times accordingly. 231 * 232 * <p>The time zone database may be assumed to change only when the device runtime 233 * is restarted. Therefore, it is not necessary to re-query the database version 234 * during the lifetime of an activity. 235 */ getTimeZoneDatabaseVersion()236 public static String getTimeZoneDatabaseVersion() { 237 return ZoneInfoDB.getInstance().getVersion(); 238 } 239 240 /** @hide Field length that can hold 999 days of time */ 241 public static final int HUNDRED_DAY_FIELD_LEN = 19; 242 243 private static final int SECONDS_PER_MINUTE = 60; 244 private static final int SECONDS_PER_HOUR = 60 * 60; 245 private static final int SECONDS_PER_DAY = 24 * 60 * 60; 246 247 /** @hide */ 248 public static final long NANOS_PER_MS = 1000000; 249 250 private static final Object sFormatSync = new Object(); 251 private static char[] sFormatStr = new char[HUNDRED_DAY_FIELD_LEN+10]; 252 private static char[] sTmpFormatStr = new char[HUNDRED_DAY_FIELD_LEN+10]; 253 accumField(int amt, int suffix, boolean always, int zeropad)254 static private int accumField(int amt, int suffix, boolean always, int zeropad) { 255 if (amt > 999) { 256 int num = 0; 257 while (amt != 0) { 258 num++; 259 amt /= 10; 260 } 261 return num + suffix; 262 } else { 263 if (amt > 99 || (always && zeropad >= 3)) { 264 return 3+suffix; 265 } 266 if (amt > 9 || (always && zeropad >= 2)) { 267 return 2+suffix; 268 } 269 if (always || amt > 0) { 270 return 1+suffix; 271 } 272 } 273 return 0; 274 } 275 printFieldLocked(char[] formatStr, int amt, char suffix, int pos, boolean always, int zeropad)276 static private int printFieldLocked(char[] formatStr, int amt, char suffix, int pos, 277 boolean always, int zeropad) { 278 if (always || amt > 0) { 279 final int startPos = pos; 280 if (amt > 999) { 281 int tmp = 0; 282 while (amt != 0 && tmp < sTmpFormatStr.length) { 283 int dig = amt % 10; 284 sTmpFormatStr[tmp] = (char)(dig + '0'); 285 tmp++; 286 amt /= 10; 287 } 288 tmp--; 289 while (tmp >= 0) { 290 formatStr[pos] = sTmpFormatStr[tmp]; 291 pos++; 292 tmp--; 293 } 294 } else { 295 if ((always && zeropad >= 3) || amt > 99) { 296 int dig = amt/100; 297 formatStr[pos] = (char)(dig + '0'); 298 pos++; 299 amt -= (dig*100); 300 } 301 if ((always && zeropad >= 2) || amt > 9 || startPos != pos) { 302 int dig = amt/10; 303 formatStr[pos] = (char)(dig + '0'); 304 pos++; 305 amt -= (dig*10); 306 } 307 formatStr[pos] = (char)(amt + '0'); 308 pos++; 309 } 310 formatStr[pos] = suffix; 311 pos++; 312 } 313 return pos; 314 } 315 formatDurationLocked(long duration, int fieldLen)316 private static int formatDurationLocked(long duration, int fieldLen) { 317 if (sFormatStr.length < fieldLen) { 318 sFormatStr = new char[fieldLen]; 319 } 320 321 char[] formatStr = sFormatStr; 322 323 if (duration == 0) { 324 int pos = 0; 325 fieldLen -= 1; 326 while (pos < fieldLen) { 327 formatStr[pos++] = ' '; 328 } 329 formatStr[pos] = '0'; 330 return pos+1; 331 } 332 333 char prefix; 334 if (duration > 0) { 335 prefix = '+'; 336 } else { 337 prefix = '-'; 338 duration = -duration; 339 } 340 341 int millis = (int)(duration%1000); 342 int seconds = (int) Math.floor(duration / 1000); 343 int days = 0, hours = 0, minutes = 0; 344 345 if (seconds >= SECONDS_PER_DAY) { 346 days = seconds / SECONDS_PER_DAY; 347 seconds -= days * SECONDS_PER_DAY; 348 } 349 if (seconds >= SECONDS_PER_HOUR) { 350 hours = seconds / SECONDS_PER_HOUR; 351 seconds -= hours * SECONDS_PER_HOUR; 352 } 353 if (seconds >= SECONDS_PER_MINUTE) { 354 minutes = seconds / SECONDS_PER_MINUTE; 355 seconds -= minutes * SECONDS_PER_MINUTE; 356 } 357 358 int pos = 0; 359 360 if (fieldLen != 0) { 361 int myLen = accumField(days, 1, false, 0); 362 myLen += accumField(hours, 1, myLen > 0, 2); 363 myLen += accumField(minutes, 1, myLen > 0, 2); 364 myLen += accumField(seconds, 1, myLen > 0, 2); 365 myLen += accumField(millis, 2, true, myLen > 0 ? 3 : 0) + 1; 366 while (myLen < fieldLen) { 367 formatStr[pos] = ' '; 368 pos++; 369 myLen++; 370 } 371 } 372 373 formatStr[pos] = prefix; 374 pos++; 375 376 int start = pos; 377 boolean zeropad = fieldLen != 0; 378 pos = printFieldLocked(formatStr, days, 'd', pos, false, 0); 379 pos = printFieldLocked(formatStr, hours, 'h', pos, pos != start, zeropad ? 2 : 0); 380 pos = printFieldLocked(formatStr, minutes, 'm', pos, pos != start, zeropad ? 2 : 0); 381 pos = printFieldLocked(formatStr, seconds, 's', pos, pos != start, zeropad ? 2 : 0); 382 pos = printFieldLocked(formatStr, millis, 'm', pos, true, (zeropad && pos != start) ? 3 : 0); 383 formatStr[pos] = 's'; 384 return pos + 1; 385 } 386 387 /** @hide Just for debugging; not internationalized. */ formatDuration(long duration, StringBuilder builder)388 public static void formatDuration(long duration, StringBuilder builder) { 389 synchronized (sFormatSync) { 390 int len = formatDurationLocked(duration, 0); 391 builder.append(sFormatStr, 0, len); 392 } 393 } 394 395 /** @hide Just for debugging; not internationalized. */ formatDuration(long duration, PrintWriter pw, int fieldLen)396 public static void formatDuration(long duration, PrintWriter pw, int fieldLen) { 397 synchronized (sFormatSync) { 398 int len = formatDurationLocked(duration, fieldLen); 399 pw.print(new String(sFormatStr, 0, len)); 400 } 401 } 402 403 /** @hide Just for debugging; not internationalized. */ formatDuration(long duration, PrintWriter pw)404 public static void formatDuration(long duration, PrintWriter pw) { 405 formatDuration(duration, pw, 0); 406 } 407 408 /** @hide Just for debugging; not internationalized. */ formatDuration(long time, long now, PrintWriter pw)409 public static void formatDuration(long time, long now, PrintWriter pw) { 410 if (time == 0) { 411 pw.print("--"); 412 return; 413 } 414 formatDuration(time-now, pw, 0); 415 } 416 417 /** @hide Just for debugging; not internationalized. */ formatUptime(long time)418 public static String formatUptime(long time) { 419 final long diff = time - SystemClock.uptimeMillis(); 420 if (diff > 0) { 421 return time + " (in " + diff + " ms)"; 422 } 423 if (diff < 0) { 424 return time + " (" + -diff + " ms ago)"; 425 } 426 return time + " (now)"; 427 } 428 429 /** 430 * Convert a System.currentTimeMillis() value to a time of day value like 431 * that printed in logs. MM-DD HH:MM:SS.MMM 432 * 433 * @param millis since the epoch (1/1/1970) 434 * @return String representation of the time. 435 * @hide 436 */ logTimeOfDay(long millis)437 public static String logTimeOfDay(long millis) { 438 Calendar c = Calendar.getInstance(); 439 if (millis >= 0) { 440 c.setTimeInMillis(millis); 441 return String.format("%tm-%td %tH:%tM:%tS.%tL", c, c, c, c, c, c); 442 } else { 443 return Long.toString(millis); 444 } 445 } 446 447 /** {@hide} */ formatForLogging(long millis)448 public static String formatForLogging(long millis) { 449 if (millis <= 0) { 450 return "unknown"; 451 } else { 452 return sLoggingFormat.format(new Date(millis)); 453 } 454 } 455 } 456