Home | History | Annotate | Download | only in util
      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