ZoneInfoDB.java revision 6cbefca623f55004ba65f11577fc25f92f6297dc
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 libcore.util; 18 19import java.io.IOException; 20import java.io.RandomAccessFile; 21import java.nio.ByteBuffer; 22import java.nio.ByteOrder; 23import java.nio.channels.FileChannel.MapMode; 24import java.nio.charset.Charsets; 25import java.util.ArrayList; 26import java.util.Arrays; 27import java.util.List; 28import java.util.TimeZone; 29import libcore.io.BufferIterator; 30import libcore.io.ErrnoException; 31import libcore.io.IoUtils; 32import libcore.io.MemoryMappedFile; 33 34// TODO: repackage this class, used by frameworks/base. 35import org.apache.harmony.luni.internal.util.TimezoneGetter; 36 37/** 38 * A class used to initialize the time zone database. This implementation uses the 39 * Olson tzdata as the source of time zone information. However, to conserve 40 * disk space (inodes) and reduce I/O, all the data is concatenated into a single file, 41 * with an index to indicate the starting position of each time zone record. 42 * 43 * @hide - used to implement TimeZone 44 */ 45public final class ZoneInfoDB { 46 private static final Object LOCK = new Object(); 47 48 /** 49 * Rather than open, read, and close the big data file each time we look up a time zone, 50 * we map the big data file during startup, and then just use the MemoryMappedFile. 51 * 52 * At the moment, this "big" data file is about 500 KiB. At some point, that will be small 53 * enough that we'll just keep the byte[] in memory. 54 */ 55 private static final MemoryMappedFile TZDATA = mapData(); 56 57 private static String version; 58 59 /** 60 * The 'ids' array contains time zone ids sorted alphabetically, for binary searching. 61 * The other two arrays are in the same order. 'byteOffsets' gives the byte offset 62 * of each time zone, and 'rawUtcOffsets' gives the time zone's raw UTC offset. 63 */ 64 private static String[] ids; 65 private static int[] byteOffsets; 66 private static int[] rawUtcOffsets; 67 68 static { 69 readHeader(); 70 } 71 72 private ZoneInfoDB() { 73 } 74 75 private static void readHeader() { 76 // byte[12] tzdata_version -- "tzdata2012f\0" 77 // int file_format_version -- 1 78 // int index_offset 79 // int data_offset 80 // int zonetab_offset 81 BufferIterator it = TZDATA.bigEndianIterator(); 82 83 byte[] tzdata_version = new byte[12]; 84 it.readByteArray(tzdata_version, 0, tzdata_version.length); 85 String magic = new String(tzdata_version, 0, 6, Charsets.US_ASCII); 86 if (!magic.equals("tzdata") || tzdata_version[11] != 0) { 87 throw new RuntimeException("bad tzdata magic: " + Arrays.toString(tzdata_version)); 88 } 89 version = new String(tzdata_version, 6, 5, Charsets.US_ASCII); 90 91 int file_format_version = it.readInt(); 92 if (file_format_version != 1) { 93 throw new RuntimeException("unknown tzdata file format version: " + file_format_version); 94 } 95 96 int index_offset = it.readInt(); 97 int data_offset = it.readInt(); 98 int zonetab_offset = it.readInt(); 99 if (zonetab_offset != 0) { 100 throw new RuntimeException("non-zero zonetab offset: " + zonetab_offset); 101 } 102 103 readIndex(it, index_offset, data_offset); 104 } 105 106 private static MemoryMappedFile mapData() { 107 try { 108 String path = System.getenv("ANDROID_ROOT") + "/usr/share/zoneinfo/tzdata"; 109 return MemoryMappedFile.mmapRO(path); 110 } catch (ErrnoException errnoException) { 111 throw new AssertionError(errnoException); 112 } 113 } 114 115 private static void readIndex(BufferIterator it, int indexOffset, int dataOffset) { 116 it.seek(indexOffset); 117 118 // The database reserves 40 bytes for each id. 119 final int SIZEOF_TZNAME = 40; 120 // The database uses 32-bit (4 byte) integers. 121 final int SIZEOF_TZINT = 4; 122 123 byte[] idBytes = new byte[SIZEOF_TZNAME]; 124 int indexSize = (dataOffset - indexOffset); 125 int entryCount = indexSize / (SIZEOF_TZNAME + 3*SIZEOF_TZINT); 126 127 char[] idChars = new char[entryCount * SIZEOF_TZNAME]; 128 int[] idEnd = new int[entryCount]; 129 int idOffset = 0; 130 131 byteOffsets = new int[entryCount]; 132 rawUtcOffsets = new int[entryCount]; 133 134 for (int i = 0; i < entryCount; i++) { 135 it.readByteArray(idBytes, 0, idBytes.length); 136 137 byteOffsets[i] = it.readInt(); 138 byteOffsets[i] += dataOffset; // TODO: change the file format so this is included. 139 140 int length = it.readInt(); 141 if (length < 44) { 142 throw new AssertionError("length in index file < sizeof(tzhead)"); 143 } 144 rawUtcOffsets[i] = it.readInt(); 145 146 // Don't include null chars in the String 147 int len = idBytes.length; 148 for (int j = 0; j < len; j++) { 149 if (idBytes[j] == 0) { 150 break; 151 } 152 idChars[idOffset++] = (char) (idBytes[j] & 0xFF); 153 } 154 155 idEnd[i] = idOffset; 156 } 157 158 // We create one string containing all the ids, and then break that into substrings. 159 // This way, all ids share a single char[] on the heap. 160 String allIds = new String(idChars, 0, idOffset); 161 ids = new String[entryCount]; 162 for (int i = 0; i < entryCount; i++) { 163 ids[i] = allIds.substring(i == 0 ? 0 : idEnd[i - 1], idEnd[i]); 164 } 165 } 166 167 public static TimeZone makeTimeZone(String id) throws IOException { 168 // Work out where in the big data file this time zone is. 169 int index = Arrays.binarySearch(ids, id); 170 if (index < 0) { 171 return null; 172 } 173 174 BufferIterator data = TZDATA.bigEndianIterator(); 175 data.skip(byteOffsets[index]); 176 177 // Variable names beginning tzh_ correspond to those in "tzfile.h". 178 // Check tzh_magic. 179 if (data.readInt() != 0x545a6966) { // "TZif" 180 return null; 181 } 182 183 // Skip the uninteresting part of the header. 184 data.skip(28); 185 186 // Read the sizes of the arrays we're about to read. 187 int tzh_timecnt = data.readInt(); 188 int tzh_typecnt = data.readInt(); 189 190 data.skip(4); // Skip tzh_charcnt. 191 192 int[] transitions = new int[tzh_timecnt]; 193 data.readIntArray(transitions, 0, transitions.length); 194 195 byte[] type = new byte[tzh_timecnt]; 196 data.readByteArray(type, 0, type.length); 197 198 int[] gmtOffsets = new int[tzh_typecnt]; 199 byte[] isDsts = new byte[tzh_typecnt]; 200 for (int i = 0; i < tzh_typecnt; ++i) { 201 gmtOffsets[i] = data.readInt(); 202 isDsts[i] = data.readByte(); 203 // We skip the abbreviation index. This would let us provide historically-accurate 204 // time zone abbreviations (such as "AHST", "YST", and "AKST" for standard time in 205 // America/Anchorage in 1982, 1983, and 1984 respectively). ICU only knows the current 206 // names, though, so even if we did use this data to provide the correct abbreviations 207 // for en_US, we wouldn't be able to provide correct abbreviations for other locales, 208 // nor would we be able to provide correct long forms (such as "Yukon Standard Time") 209 // for any locale. (The RI doesn't do any better than us here either.) 210 data.skip(1); 211 } 212 213 return new ZoneInfo(id, transitions, type, gmtOffsets, isDsts); 214 } 215 216 public static String[] getAvailableIDs() { 217 return ids.clone(); 218 } 219 220 public static String[] getAvailableIDs(int rawOffset) { 221 List<String> matches = new ArrayList<String>(); 222 for (int i = 0, end = rawUtcOffsets.length; i < end; ++i) { 223 if (rawUtcOffsets[i] == rawOffset) { 224 matches.add(ids[i]); 225 } 226 } 227 return matches.toArray(new String[matches.size()]); 228 } 229 230 public static TimeZone getSystemDefault() { 231 synchronized (LOCK) { 232 TimezoneGetter tzGetter = TimezoneGetter.getInstance(); 233 String zoneName = tzGetter != null ? tzGetter.getId() : null; 234 if (zoneName != null) { 235 zoneName = zoneName.trim(); 236 } 237 if (zoneName == null || zoneName.isEmpty()) { 238 // use localtime for the simulator 239 // TODO: what does that correspond to? 240 zoneName = "localtime"; 241 } 242 return TimeZone.getTimeZone(zoneName); 243 } 244 } 245 246 public static String getVersion() { 247 return version; 248 } 249} 250