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