ZoneInfoDB.java revision a6e8689807f5a8bb9470ce7c26a47455d2d0608d
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 android.system.ErrnoException; 20import java.io.IOException; 21import java.io.RandomAccessFile; 22import java.nio.ByteBuffer; 23import java.nio.ByteOrder; 24import java.nio.channels.FileChannel.MapMode; 25import java.nio.charset.StandardCharsets; 26import java.util.ArrayList; 27import java.util.Arrays; 28import java.util.List; 29import java.util.TimeZone; 30import libcore.io.BufferIterator; 31import libcore.io.IoUtils; 32import libcore.io.MemoryMappedFile; 33 34/** 35 * A class used to initialize the time zone database. This implementation uses the 36 * Olson tzdata as the source of time zone information. However, to conserve 37 * disk space (inodes) and reduce I/O, all the data is concatenated into a single file, 38 * with an index to indicate the starting position of each time zone record. 39 * 40 * @hide - used to implement TimeZone 41 */ 42public final class ZoneInfoDB { 43 private static final TzData DATA = 44 new TzData(System.getenv("ANDROID_DATA") + "/misc/zoneinfo/tzdata", 45 System.getenv("ANDROID_ROOT") + "/usr/share/zoneinfo/tzdata"); 46 47 public static class TzData { 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 could just keep the byte[] in memory, but using mmap(2) like this has the 54 * nice property that even if someone replaces the file under us (because multiple gservices 55 * updates have gone out, say), we still get a consistent (if outdated) view of the world. 56 */ 57 private MemoryMappedFile mappedFile; 58 59 private String version; 60 private String zoneTab; 61 62 /** 63 * The 'ids' array contains time zone ids sorted alphabetically, for binary searching. 64 * The other two arrays are in the same order. 'byteOffsets' gives the byte offset 65 * of each time zone, and 'rawUtcOffsets' gives the time zone's raw UTC offset. 66 */ 67 private String[] ids; 68 private int[] byteOffsets; 69 private int[] rawUtcOffsets; 70 71 /** 72 * ZoneInfo objects are worth caching because they are expensive to create. 73 * See http://b/8270865 for context. 74 */ 75 private final static int CACHE_SIZE = 1; 76 private final BasicLruCache<String, ZoneInfo> cache = 77 new BasicLruCache<String, ZoneInfo>(CACHE_SIZE) { 78 @Override 79 protected ZoneInfo create(String id) { 80 // Work out where in the big data file this time zone is. 81 int index = Arrays.binarySearch(ids, id); 82 if (index < 0) { 83 return null; 84 } 85 86 BufferIterator it = mappedFile.bigEndianIterator(); 87 it.skip(byteOffsets[index]); 88 89 return ZoneInfo.makeTimeZone(id, it); 90 } 91 }; 92 93 public TzData(String... paths) { 94 for (String path : paths) { 95 if (loadData(path)) { 96 return; 97 } 98 } 99 100 // We didn't find any usable tzdata on disk, so let's just hard-code knowledge of "GMT". 101 // This is actually implemented in TimeZone itself, so if this is the only time zone 102 // we report, we won't be asked any more questions. 103 System.logE("Couldn't find any tzdata!"); 104 version = "missing"; 105 zoneTab = "# Emergency fallback data.\n"; 106 ids = new String[] { "GMT" }; 107 byteOffsets = rawUtcOffsets = new int[1]; 108 } 109 110 private boolean loadData(String path) { 111 try { 112 mappedFile = MemoryMappedFile.mmapRO(path); 113 } catch (ErrnoException errnoException) { 114 return false; 115 } 116 try { 117 readHeader(); 118 return true; 119 } catch (Exception ex) { 120 // Something's wrong with the file. 121 // Log the problem and return false so we try the next choice. 122 System.logE("tzdata file \"" + path + "\" was present but invalid!", ex); 123 return false; 124 } 125 } 126 127 private void readHeader() { 128 // byte[12] tzdata_version -- "tzdata2012f\0" 129 // int index_offset 130 // int data_offset 131 // int zonetab_offset 132 BufferIterator it = mappedFile.bigEndianIterator(); 133 134 byte[] tzdata_version = new byte[12]; 135 it.readByteArray(tzdata_version, 0, tzdata_version.length); 136 String magic = new String(tzdata_version, 0, 6, StandardCharsets.US_ASCII); 137 if (!magic.equals("tzdata") || tzdata_version[11] != 0) { 138 throw new RuntimeException("bad tzdata magic: " + Arrays.toString(tzdata_version)); 139 } 140 version = new String(tzdata_version, 6, 5, StandardCharsets.US_ASCII); 141 142 int index_offset = it.readInt(); 143 int data_offset = it.readInt(); 144 int zonetab_offset = it.readInt(); 145 146 readIndex(it, index_offset, data_offset); 147 readZoneTab(it, zonetab_offset, (int) mappedFile.size() - zonetab_offset); 148 } 149 150 private void readZoneTab(BufferIterator it, int zoneTabOffset, int zoneTabSize) { 151 byte[] bytes = new byte[zoneTabSize]; 152 it.seek(zoneTabOffset); 153 it.readByteArray(bytes, 0, bytes.length); 154 zoneTab = new String(bytes, 0, bytes.length, StandardCharsets.US_ASCII); 155 } 156 157 private void readIndex(BufferIterator it, int indexOffset, int dataOffset) { 158 it.seek(indexOffset); 159 160 // The database reserves 40 bytes for each id. 161 final int SIZEOF_TZNAME = 40; 162 // The database uses 32-bit (4 byte) integers. 163 final int SIZEOF_TZINT = 4; 164 165 byte[] idBytes = new byte[SIZEOF_TZNAME]; 166 int indexSize = (dataOffset - indexOffset); 167 int entryCount = indexSize / (SIZEOF_TZNAME + 3*SIZEOF_TZINT); 168 169 char[] idChars = new char[entryCount * SIZEOF_TZNAME]; 170 int[] idEnd = new int[entryCount]; 171 int idOffset = 0; 172 173 byteOffsets = new int[entryCount]; 174 rawUtcOffsets = new int[entryCount]; 175 176 for (int i = 0; i < entryCount; i++) { 177 it.readByteArray(idBytes, 0, idBytes.length); 178 179 byteOffsets[i] = it.readInt(); 180 byteOffsets[i] += dataOffset; // TODO: change the file format so this is included. 181 182 int length = it.readInt(); 183 if (length < 44) { 184 throw new AssertionError("length in index file < sizeof(tzhead)"); 185 } 186 rawUtcOffsets[i] = it.readInt(); 187 188 // Don't include null chars in the String 189 int len = idBytes.length; 190 for (int j = 0; j < len; j++) { 191 if (idBytes[j] == 0) { 192 break; 193 } 194 idChars[idOffset++] = (char) (idBytes[j] & 0xFF); 195 } 196 197 idEnd[i] = idOffset; 198 } 199 200 // We create one string containing all the ids, and then break that into substrings. 201 // This way, all ids share a single char[] on the heap. 202 String allIds = new String(idChars, 0, idOffset); 203 ids = new String[entryCount]; 204 for (int i = 0; i < entryCount; i++) { 205 ids[i] = allIds.substring(i == 0 ? 0 : idEnd[i - 1], idEnd[i]); 206 } 207 } 208 209 public String[] getAvailableIDs() { 210 return ids.clone(); 211 } 212 213 public String[] getAvailableIDs(int rawOffset) { 214 List<String> matches = new ArrayList<String>(); 215 for (int i = 0, end = rawUtcOffsets.length; i < end; ++i) { 216 if (rawUtcOffsets[i] == rawOffset) { 217 matches.add(ids[i]); 218 } 219 } 220 return matches.toArray(new String[matches.size()]); 221 } 222 223 public String getVersion() { 224 return version; 225 } 226 227 public String getZoneTab() { 228 return zoneTab; 229 } 230 231 public ZoneInfo makeTimeZone(String id) throws IOException { 232 // The object from the cache is cloned because TimeZone / ZoneInfo are mutable. 233 return (ZoneInfo) cache.get(id).clone(); 234 } 235 } 236 237 private ZoneInfoDB() { 238 } 239 240 public static TzData getInstance() { 241 return DATA; 242 } 243} 244