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  * 'zoneinfo' database as the source of time zone information.  However, to conserve
     40  * disk space the data for all time zones are concatenated into a single file, and a
     41  * second file is used to indicate the starting position of each time zone record.  A
     42  * third file indicates the version of the zoneinfo database used to generate the data.
     43  *
     44  * @hide - used to implement TimeZone
     45  */
     46 public final class ZoneInfoDB {
     47     /**
     48      * The directory containing the time zone database files.
     49      */
     50     private static final String ZONE_DIRECTORY_NAME =
     51             System.getenv("ANDROID_ROOT") + "/usr/share/zoneinfo/";
     52 
     53     /**
     54      * The name of the file containing the concatenated time zone records.
     55      */
     56     private static final String ZONE_FILE_NAME = ZONE_DIRECTORY_NAME + "zoneinfo.dat";
     57 
     58     /**
     59      * The name of the file containing the index to each time zone record within
     60      * the zoneinfo.dat file.
     61      */
     62     private static final String INDEX_FILE_NAME = ZONE_DIRECTORY_NAME + "zoneinfo.idx";
     63 
     64     private static final Object LOCK = new Object();
     65 
     66     private static final String VERSION = readVersion();
     67 
     68     /**
     69      * Rather than open, read, and close the big data file each time we look up a time zone,
     70      * we map the big data file during startup, and then just use the MemoryMappedFile.
     71      *
     72      * At the moment, this "big" data file is about 500 KiB. At some point, that will be small
     73      * enough that we'll just keep the byte[] in memory.
     74      */
     75     private static final MemoryMappedFile ALL_ZONE_DATA = mapData();
     76 
     77     /**
     78      * The 'ids' array contains time zone ids sorted alphabetically, for binary searching.
     79      * The other two arrays are in the same order. 'byteOffsets' gives the byte offset
     80      * into "zoneinfo.dat" of each time zone, and 'rawUtcOffsets' gives the time zone's
     81      * raw UTC offset.
     82      */
     83     private static String[] ids;
     84     private static int[] byteOffsets;
     85     private static int[] rawUtcOffsets;
     86     static {
     87         readIndex();
     88     }
     89 
     90     private ZoneInfoDB() {
     91     }
     92 
     93     /**
     94      * Reads the file indicating the database version in use.
     95      */
     96     private static String readVersion() {
     97         try {
     98             byte[] bytes = IoUtils.readFileAsByteArray(ZONE_DIRECTORY_NAME + "zoneinfo.version");
     99             return new String(bytes, 0, bytes.length, Charsets.ISO_8859_1).trim();
    100         } catch (IOException ex) {
    101             throw new RuntimeException(ex);
    102         }
    103     }
    104 
    105     private static MemoryMappedFile mapData() {
    106         try {
    107             return MemoryMappedFile.mmapRO(ZONE_FILE_NAME);
    108         } catch (ErrnoException errnoException) {
    109             throw new AssertionError(errnoException);
    110         }
    111     }
    112 
    113     /**
    114      * Traditionally, Unix systems have one file per time zone. We have one big data file, which
    115      * is just a concatenation of regular time zone files. To allow random access into this big
    116      * data file, we also have an index. We read the index at startup, and keep it in memory so
    117      * we can binary search by id when we need time zone data.
    118      *
    119      * The format of this file is, I believe, Android's own, and undocumented.
    120      *
    121      * All this code assumes strings are US-ASCII.
    122      */
    123     private static void readIndex() {
    124         MemoryMappedFile mappedFile = null;
    125         try {
    126             mappedFile = MemoryMappedFile.mmapRO(INDEX_FILE_NAME);
    127             readIndex(mappedFile);
    128         } catch (Exception ex) {
    129             throw new AssertionError(ex);
    130         } finally {
    131             IoUtils.closeQuietly(mappedFile);
    132         }
    133     }
    134 
    135     private static void readIndex(MemoryMappedFile mappedFile) throws ErrnoException, IOException {
    136         BufferIterator it = mappedFile.bigEndianIterator();
    137 
    138         // The database reserves 40 bytes for each id.
    139         final int SIZEOF_TZNAME = 40;
    140         // The database uses 32-bit (4 byte) integers.
    141         final int SIZEOF_TZINT = 4;
    142 
    143         byte[] idBytes = new byte[SIZEOF_TZNAME];
    144         int numEntries = (int) mappedFile.size() / (SIZEOF_TZNAME + 3*SIZEOF_TZINT);
    145 
    146         char[] idChars = new char[numEntries * SIZEOF_TZNAME];
    147         int[] idEnd = new int[numEntries];
    148         int idOffset = 0;
    149 
    150         byteOffsets = new int[numEntries];
    151         rawUtcOffsets = new int[numEntries];
    152 
    153         for (int i = 0; i < numEntries; i++) {
    154             it.readByteArray(idBytes, 0, idBytes.length);
    155             byteOffsets[i] = it.readInt();
    156             int length = it.readInt();
    157             if (length < 44) {
    158                 throw new AssertionError("length in index file < sizeof(tzhead)");
    159             }
    160             rawUtcOffsets[i] = it.readInt();
    161 
    162             // Don't include null chars in the String
    163             int len = idBytes.length;
    164             for (int j = 0; j < len; j++) {
    165                 if (idBytes[j] == 0) {
    166                     break;
    167                 }
    168                 idChars[idOffset++] = (char) (idBytes[j] & 0xFF);
    169             }
    170 
    171             idEnd[i] = idOffset;
    172         }
    173 
    174         // We create one string containing all the ids, and then break that into substrings.
    175         // This way, all ids share a single char[] on the heap.
    176         String allIds = new String(idChars, 0, idOffset);
    177         ids = new String[numEntries];
    178         for (int i = 0; i < numEntries; i++) {
    179             ids[i] = allIds.substring(i == 0 ? 0 : idEnd[i - 1], idEnd[i]);
    180         }
    181     }
    182 
    183     private static TimeZone makeTimeZone(String id) throws IOException {
    184         // Work out where in the big data file this time zone is.
    185         int index = Arrays.binarySearch(ids, id);
    186         if (index < 0) {
    187             return null;
    188         }
    189 
    190         BufferIterator data = ALL_ZONE_DATA.bigEndianIterator();
    191         data.skip(byteOffsets[index]);
    192 
    193         // Variable names beginning tzh_ correspond to those in "tzfile.h".
    194         // Check tzh_magic.
    195         if (data.readInt() != 0x545a6966) { // "TZif"
    196             return null;
    197         }
    198 
    199         // Skip the uninteresting part of the header.
    200         data.skip(28);
    201 
    202         // Read the sizes of the arrays we're about to read.
    203         int tzh_timecnt = data.readInt();
    204         int tzh_typecnt = data.readInt();
    205 
    206         data.skip(4); // Skip tzh_charcnt.
    207 
    208         int[] transitions = new int[tzh_timecnt];
    209         data.readIntArray(transitions, 0, transitions.length);
    210 
    211         byte[] type = new byte[tzh_timecnt];
    212         data.readByteArray(type, 0, type.length);
    213 
    214         int[] gmtOffsets = new int[tzh_typecnt];
    215         byte[] isDsts = new byte[tzh_typecnt];
    216         for (int i = 0; i < tzh_typecnt; ++i) {
    217             gmtOffsets[i] = data.readInt();
    218             isDsts[i] = data.readByte();
    219             // We skip the abbreviation index. This would let us provide historically-accurate
    220             // time zone abbreviations (such as "AHST", "YST", and "AKST" for standard time in
    221             // America/Anchorage in 1982, 1983, and 1984 respectively). ICU only knows the current
    222             // names, though, so even if we did use this data to provide the correct abbreviations
    223             // for en_US, we wouldn't be able to provide correct abbreviations for other locales,
    224             // nor would we be able to provide correct long forms (such as "Yukon Standard Time")
    225             // for any locale. (The RI doesn't do any better than us here either.)
    226             data.skip(1);
    227         }
    228 
    229         return new ZoneInfo(id, transitions, type, gmtOffsets, isDsts);
    230     }
    231 
    232     public static String[] getAvailableIDs() {
    233         return ids.clone();
    234     }
    235 
    236     public static String[] getAvailableIDs(int rawOffset) {
    237         List<String> matches = new ArrayList<String>();
    238         for (int i = 0, end = rawUtcOffsets.length; i < end; i++) {
    239             if (rawUtcOffsets[i] == rawOffset) {
    240                 matches.add(ids[i]);
    241             }
    242         }
    243         return matches.toArray(new String[matches.size()]);
    244     }
    245 
    246     public static TimeZone getSystemDefault() {
    247         synchronized (LOCK) {
    248             TimezoneGetter tzGetter = TimezoneGetter.getInstance();
    249             String zoneName = tzGetter != null ? tzGetter.getId() : null;
    250             if (zoneName != null) {
    251                 zoneName = zoneName.trim();
    252             }
    253             if (zoneName == null || zoneName.isEmpty()) {
    254                 // use localtime for the simulator
    255                 // TODO: what does that correspond to?
    256                 zoneName = "localtime";
    257             }
    258             return TimeZone.getTimeZone(zoneName);
    259         }
    260     }
    261 
    262     public static TimeZone getTimeZone(String id) {
    263         if (id == null) {
    264             return null;
    265         }
    266         try {
    267             return makeTimeZone(id);
    268         } catch (IOException ignored) {
    269             return null;
    270         }
    271     }
    272 
    273     public static String getVersion() {
    274         return VERSION;
    275     }
    276 }
    277