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 17 package libcore.util; 18 19 import java.io.IOException; 20 import java.io.RandomAccessFile; 21 import java.nio.ByteBuffer; 22 import java.nio.ByteOrder; 23 import java.nio.channels.FileChannel.MapMode; 24 import java.nio.charset.Charsets; 25 import java.util.ArrayList; 26 import java.util.Arrays; 27 import java.util.List; 28 import java.util.TimeZone; 29 import libcore.io.BufferIterator; 30 import libcore.io.ErrnoException; 31 import libcore.io.IoUtils; 32 import libcore.io.MemoryMappedFile; 33 34 // TODO: repackage this class, used by frameworks/base. 35 import 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 */ 45 public 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 private static String zoneTab; 59 60 /** 61 * The 'ids' array contains time zone ids sorted alphabetically, for binary searching. 62 * The other two arrays are in the same order. 'byteOffsets' gives the byte offset 63 * of each time zone, and 'rawUtcOffsets' gives the time zone's raw UTC offset. 64 */ 65 private static String[] ids; 66 private static int[] byteOffsets; 67 private static int[] rawUtcOffsets; 68 69 static { 70 readHeader(); 71 } 72 73 private ZoneInfoDB() { 74 } 75 76 private static void readHeader() { 77 // byte[12] tzdata_version -- "tzdata2012f\0" 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 index_offset = it.readInt(); 92 int data_offset = it.readInt(); 93 int zonetab_offset = it.readInt(); 94 95 readIndex(it, index_offset, data_offset); 96 readZoneTab(it, zonetab_offset); 97 } 98 99 private static MemoryMappedFile mapData() { 100 MemoryMappedFile result = mapData(System.getenv("ANDROID_DATA") + "/misc/zoneinfo/"); 101 if (result == null) { 102 result = mapData(System.getenv("ANDROID_ROOT") + "/usr/share/zoneinfo/"); 103 if (result == null) { 104 throw new AssertionError("Couldn't find any tzdata!"); 105 } 106 } 107 return result; 108 } 109 110 private static MemoryMappedFile mapData(String directory) { 111 try { 112 return MemoryMappedFile.mmapRO(directory + "tzdata"); 113 } catch (ErrnoException errnoException) { 114 return null; 115 } 116 } 117 118 private static void readZoneTab(BufferIterator it, int zoneTabOffset) { 119 byte[] bytes = new byte[(int) TZDATA.size() - zoneTabOffset]; 120 it.seek(zoneTabOffset); 121 it.readByteArray(bytes, 0, bytes.length); 122 zoneTab = new String(bytes, 0, bytes.length, Charsets.US_ASCII); 123 } 124 125 private static void readIndex(BufferIterator it, int indexOffset, int dataOffset) { 126 it.seek(indexOffset); 127 128 // The database reserves 40 bytes for each id. 129 final int SIZEOF_TZNAME = 40; 130 // The database uses 32-bit (4 byte) integers. 131 final int SIZEOF_TZINT = 4; 132 133 byte[] idBytes = new byte[SIZEOF_TZNAME]; 134 int indexSize = (dataOffset - indexOffset); 135 int entryCount = indexSize / (SIZEOF_TZNAME + 3*SIZEOF_TZINT); 136 137 char[] idChars = new char[entryCount * SIZEOF_TZNAME]; 138 int[] idEnd = new int[entryCount]; 139 int idOffset = 0; 140 141 byteOffsets = new int[entryCount]; 142 rawUtcOffsets = new int[entryCount]; 143 144 for (int i = 0; i < entryCount; i++) { 145 it.readByteArray(idBytes, 0, idBytes.length); 146 147 byteOffsets[i] = it.readInt(); 148 byteOffsets[i] += dataOffset; // TODO: change the file format so this is included. 149 150 int length = it.readInt(); 151 if (length < 44) { 152 throw new AssertionError("length in index file < sizeof(tzhead)"); 153 } 154 rawUtcOffsets[i] = it.readInt(); 155 156 // Don't include null chars in the String 157 int len = idBytes.length; 158 for (int j = 0; j < len; j++) { 159 if (idBytes[j] == 0) { 160 break; 161 } 162 idChars[idOffset++] = (char) (idBytes[j] & 0xFF); 163 } 164 165 idEnd[i] = idOffset; 166 } 167 168 // We create one string containing all the ids, and then break that into substrings. 169 // This way, all ids share a single char[] on the heap. 170 String allIds = new String(idChars, 0, idOffset); 171 ids = new String[entryCount]; 172 for (int i = 0; i < entryCount; i++) { 173 ids[i] = allIds.substring(i == 0 ? 0 : idEnd[i - 1], idEnd[i]); 174 } 175 } 176 177 public static TimeZone makeTimeZone(String id) throws IOException { 178 // Work out where in the big data file this time zone is. 179 int index = Arrays.binarySearch(ids, id); 180 if (index < 0) { 181 return null; 182 } 183 184 BufferIterator it = TZDATA.bigEndianIterator(); 185 it.skip(byteOffsets[index]); 186 187 return ZoneInfo.makeTimeZone(id, it); 188 } 189 190 public static String[] getAvailableIDs() { 191 return ids.clone(); 192 } 193 194 public static String[] getAvailableIDs(int rawOffset) { 195 List<String> matches = new ArrayList<String>(); 196 for (int i = 0, end = rawUtcOffsets.length; i < end; ++i) { 197 if (rawUtcOffsets[i] == rawOffset) { 198 matches.add(ids[i]); 199 } 200 } 201 return matches.toArray(new String[matches.size()]); 202 } 203 204 public static TimeZone getSystemDefault() { 205 synchronized (LOCK) { 206 TimezoneGetter tzGetter = TimezoneGetter.getInstance(); 207 String zoneName = tzGetter != null ? tzGetter.getId() : null; 208 if (zoneName != null) { 209 zoneName = zoneName.trim(); 210 } 211 if (zoneName == null || zoneName.isEmpty()) { 212 // use localtime for the simulator 213 // TODO: what does that correspond to? 214 zoneName = "localtime"; 215 } 216 return TimeZone.getTimeZone(zoneName); 217 } 218 } 219 220 public static String getVersion() { 221 return version; 222 } 223 224 public static String getZoneTab() { 225 return zoneTab; 226 } 227 } 228