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 { 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 */ 63 public static TimeZone getTimeZone(int offset, boolean dst, long when, String country) { 64 TimeZone best = null; 65 66 Resources r = Resources.getSystem(); 67 XmlResourceParser parser = r.getXml(com.android.internal.R.xml.time_zones_by_country); 68 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 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 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 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 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. */ 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. */ 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. */ 382 public static void formatDuration(long duration, PrintWriter pw) { 383 formatDuration(duration, pw, 0); 384 } 385 386 /** @hide Just for debugging; not internationalized. */ 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. */ 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 */ 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