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