1/* 2 * Copyright (C) 2007 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 org.apache.harmony.luni.internal.util; 18 19import java.io.File; 20import java.io.FileInputStream; 21import java.io.IOException; 22import java.io.RandomAccessFile; 23import java.util.ArrayList; 24import java.util.Arrays; 25import java.util.Date; 26import java.util.List; 27import java.util.TimeZone; 28import java.util.logging.Logger; 29 30/** 31 * A class used to initialize the time zone database. This implementation uses the 32 * 'zoneinfo' database as the source of time zone information. However, to conserve 33 * disk space the data for all time zones are concatenated into a single file, and a 34 * second file is used to indicate the starting position of each time zone record. A 35 * third file indicates the version of the zoneinfo databse used to generate the data. 36 * 37 * {@hide} 38 */ 39public final class ZoneInfoDB { 40 private static final int TZNAME_LENGTH = 40; 41 private static final int TZINT_LENGTH = 4; 42 43 /** 44 * The directory contining the time zone database files. 45 */ 46 private static final String ZONE_DIRECTORY_NAME = 47 System.getenv("ANDROID_ROOT") + "/usr/share/zoneinfo/"; 48 49 /** 50 * The name of the file containing the concatenated time zone records. 51 */ 52 private static final String ZONE_FILE_NAME = 53 ZONE_DIRECTORY_NAME + "zoneinfo.dat"; 54 55 /** 56 * The name of the file containing the index to each time zone record within 57 * the zoneinfo.dat file. 58 */ 59 private static final String INDEX_FILE_NAME = 60 ZONE_DIRECTORY_NAME + "zoneinfo.idx"; 61 62 /** 63 * Zoneinfo version used prior to creation of the zoneinfo.version file, 64 * equal to "2007h". 65 */ 66 private static final String DEFAULT_VERSION = "2007h"; 67 68 /** 69 * The name of the file indicating the database version in use. If the file is not 70 * present or is unreadable, we assume a version of DEFAULT_VERSION. 71 */ 72 private static final String VERSION_FILE_NAME = 73 ZONE_DIRECTORY_NAME + "zoneinfo.version"; 74 75 private static Object lock = new Object(); 76 private static TimeZone defaultZone = null; 77 78 private static String version; 79 private static String[] names; 80 private static int[] starts; 81 private static int[] lengths; 82 private static int[] offsets; 83 84 /** 85 * This class is uninstantiable. 86 */ 87 private ZoneInfoDB() { 88 // This space intentionally left blank. 89 } 90 91 private static void readVersion() throws IOException { 92 RandomAccessFile versionFile = new RandomAccessFile(VERSION_FILE_NAME, "r"); 93 int len = (int) versionFile.length(); 94 byte[] vbuf = new byte[len]; 95 versionFile.readFully(vbuf); 96 version = new String(vbuf, 0, len, "ISO-8859-1").trim(); 97 versionFile.close(); 98 } 99 100 private static void readDatabase() throws IOException { 101 if (starts != null) { 102 return; 103 } 104 105 RandomAccessFile indexFile = new RandomAccessFile(INDEX_FILE_NAME, "r"); 106 byte[] nbuf = new byte[TZNAME_LENGTH]; 107 108 int numEntries = 109 (int) (indexFile.length() / (TZNAME_LENGTH + 3*TZINT_LENGTH)); 110 111 char[] namebuf = new char[numEntries * TZNAME_LENGTH]; 112 int[] nameend = new int[numEntries]; 113 int nameoff = 0; 114 115 starts = new int[numEntries]; 116 lengths = new int[numEntries]; 117 offsets = new int[numEntries]; 118 119 for (int i = 0; i < numEntries; i++) { 120 indexFile.readFully(nbuf); 121 starts[i] = indexFile.readInt(); 122 lengths[i] = indexFile.readInt(); 123 offsets[i] = indexFile.readInt(); 124 125 // Don't include null chars in the String 126 int len = nbuf.length; 127 for (int j = 0; j < len; j++) { 128 if (nbuf[j] == 0) { 129 break; 130 } 131 namebuf[nameoff++] = (char) (nbuf[j] & 0xFF); 132 } 133 134 nameend[i] = nameoff; 135 } 136 137 String name = new String(namebuf, 0, nameoff); 138 139 // Assumes the namebuf is all ASCII (so byte offsets == char offsets). 140 names = new String[numEntries]; 141 for (int i = 0; i < numEntries; i++) { 142 names[i] = name.substring(i == 0 ? 0 : nameend[i - 1], 143 nameend[i]); 144 } 145 146 indexFile.close(); 147 } 148 149 static { 150 // Don't attempt to log here because the logger requires this class to be initialized 151 try { 152 readVersion(); 153 } catch (IOException e) { 154 // The version can't be read, we can continue without it 155 version = DEFAULT_VERSION; 156 } 157 158 try { 159 readDatabase(); 160 } catch (IOException e) { 161 // The database can't be read, try to continue without it 162 names = new String[0]; 163 starts = new int[0]; 164 lengths = new int[0]; 165 offsets = new int[0]; 166 } 167 } 168 169 public static String getVersion() { 170 return version; 171 } 172 173 public static String[] getAvailableIDs() { 174 return _getAvailableIDs(0, false); 175 } 176 177 public static String[] getAvailableIDs(int rawOffset) { 178 return _getAvailableIDs(rawOffset, true); 179 } 180 181 private static String[] _getAvailableIDs(int rawOffset, 182 boolean checkOffset) { 183 List<String> matches = new ArrayList<String>(); 184 185 int[] _offsets = ZoneInfoDB.offsets; 186 String[] _names = ZoneInfoDB.names; 187 int len = _offsets.length; 188 for (int i = 0; i < len; i++) { 189 if (!checkOffset || _offsets[i] == rawOffset) { 190 matches.add(_names[i]); 191 } 192 } 193 194 return matches.toArray(new String[matches.size()]); 195 } 196 197 /*package*/ static TimeZone _getTimeZone(String name) 198 throws IOException { 199 FileInputStream fis = null; 200 int length = 0; 201 202 File f = new File(ZONE_DIRECTORY_NAME + name); 203 if (!f.exists()) { 204 fis = new FileInputStream(ZONE_FILE_NAME); 205 int i = Arrays.binarySearch(ZoneInfoDB.names, name); 206 207 if (i < 0) { 208 return null; 209 } 210 211 int start = ZoneInfoDB.starts[i]; 212 length = ZoneInfoDB.lengths[i]; 213 214 fis.skip(start); 215 } 216 217 if (fis == null) { 218 fis = new FileInputStream(f); 219 length = (int)f.length(); // data won't exceed 2G! 220 } 221 222 byte[] data = new byte[length]; 223 int nread = 0; 224 while (nread < length) { 225 int size = fis.read(data, nread, length - nread); 226 if (size > 0) { 227 nread += size; 228 } 229 } 230 231 try { 232 fis.close(); 233 } catch (IOException e3) { 234 // probably better to continue than to fail here 235 java.util.logging.Logger.global.warning("IOException " + e3 + 236 " retrieving time zone data"); 237 e3.printStackTrace(); 238 } 239 240 if (data.length < 36) { 241 return null; 242 } 243 if (data[0] != 'T' || data[1] != 'Z' || 244 data[2] != 'i' || data[3] != 'f') { 245 return null; 246 } 247 248 int ntransition = read4(data, 32); 249 int ngmtoff = read4(data, 36); 250 int base = 44; 251 252 int[] transitions = new int[ntransition]; 253 for (int i = 0; i < ntransition; i++) 254 transitions[i] = read4(data, base + 4 * i); 255 base += 4 * ntransition; 256 257 byte[] type = new byte[ntransition]; 258 for (int i = 0; i < ntransition; i++) 259 type[i] = data[base + i]; 260 base += ntransition; 261 262 int[] gmtoff = new int[ngmtoff]; 263 byte[] isdst = new byte[ngmtoff]; 264 byte[] abbrev = new byte[ngmtoff]; 265 for (int i = 0; i < ngmtoff; i++) { 266 gmtoff[i] = read4(data, base + 6 * i); 267 isdst[i] = data[base + 6 * i + 4]; 268 abbrev[i] = data[base + 6 * i + 5]; 269 } 270 271 base += 6 * ngmtoff; 272 273 return new ZoneInfo(name, transitions, type, gmtoff, isdst, abbrev, data, base); 274 } 275 276 private static int read4(byte[] data, int off) { 277 return ((data[off ] & 0xFF) << 24) | 278 ((data[off + 1] & 0xFF) << 16) | 279 ((data[off + 2] & 0xFF) << 8) | 280 ((data[off + 3] & 0xFF) << 0); 281 } 282 283 public static TimeZone getTimeZone(String id) { 284 if (id != null) { 285 if (id.equals("GMT") || id.equals("UTC")) { 286 TimeZone tz = new MinimalTimeZone(0); 287 tz.setID(id); 288 return tz; 289 } 290 291 if (id.startsWith("GMT")) { 292 return new MinimalTimeZone(parseNumericZone(id) * 1000); 293 } 294 } 295 296 TimeZone tz = ZoneInfo.getTimeZone(id); 297 298 if (tz != null) 299 return tz; 300 301 /* 302 * It isn't GMT+anything, and it also isn't something we have 303 * in the database. Give up and return GMT. 304 */ 305 tz = new MinimalTimeZone(0); 306 tz.setID("GMT"); 307 return tz; 308 } 309 310 public static TimeZone getDefault() { 311 TimeZone zone; 312 313 synchronized (lock) { 314 if (defaultZone != null) { 315 return defaultZone; 316 } 317 318 String zoneName = null; 319 TimezoneGetter tzGetter = TimezoneGetter.getInstance(); 320 if (tzGetter != null) { 321 zoneName = tzGetter.getId(); 322 } 323 if (zoneName != null && zoneName.length() > 0) { 324 zone = TimeZone.getTimeZone(zoneName.trim()); 325 } else { 326 // use localtime here so that the simulator works 327 zone = TimeZone.getTimeZone("localtime"); 328 } 329 330 defaultZone = zone; 331 } 332 return zone; 333 } 334 335 // TODO - why does this ignore the 'zone' parameter? 336 public static void setDefault(@SuppressWarnings("unused") TimeZone zone) { 337 /* 338 * if (zone == null), the next call to getDefault will set it to the 339 * the system's default time zone. 340 */ 341 synchronized (lock) { 342 defaultZone = null; 343 } 344 } 345 346 private static int parseNumericZone(String name) { 347 if (name == null) 348 return 0; 349 350 if (!name.startsWith("GMT")) 351 return 0; 352 353 if (name.length() == 3) 354 return 0; 355 356 int sign; 357 if (name.charAt(3) == '+') 358 sign = 1; 359 else if (name.charAt(3) == '-') 360 sign = -1; 361 else 362 return 0; 363 364 int where; 365 int hour = 0; 366 boolean colon = false; 367 for (where = 4; where < name.length(); where++) { 368 char c = name.charAt(where); 369 370 if (c == ':') { 371 where++; 372 colon = true; 373 break; 374 } 375 376 if (c >= '0' && c <= '9') 377 hour = hour * 10 + c - '0'; 378 else 379 return 0; 380 } 381 382 int min = 0; 383 for (; where < name.length(); where++) { 384 char c = name.charAt(where); 385 386 if (c >= '0' && c <= '9') 387 min = min * 10 + c - '0'; 388 else 389 return 0; 390 } 391 392 if (colon) 393 return sign * (hour * 60 + min) * 60; 394 else if (hour >= 100) 395 return sign * ((hour / 100) * 60 + (hour % 100)) * 60; 396 else 397 return sign * (hour * 60) * 60; 398 } 399 400 /*package*/ static class MinimalTimeZone extends TimeZone { 401 private int rawOffset; 402 403 public MinimalTimeZone(int offset) { 404 rawOffset = offset; 405 setID(getDisplayName()); 406 } 407 408 @SuppressWarnings("unused") 409 @Override 410 public int getOffset(int era, int year, int month, int day, int dayOfWeek, int millis) { 411 return getRawOffset(); 412 } 413 414 @Override 415 public int getRawOffset() { 416 return rawOffset; 417 } 418 419 @Override 420 public void setRawOffset(int off) { 421 rawOffset = off; 422 } 423 424 @SuppressWarnings("unused") 425 @Override 426 public boolean inDaylightTime(Date when) { 427 return false; 428 } 429 430 @Override 431 public boolean useDaylightTime() { 432 return false; 433 } 434 } 435} 436