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