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