All Downloads are FREE. Search and download functionalities are using the official Maven repository.

libcore.util.TzDataRoboVM Maven / Gradle / Ivy

/*
 * Copyright (C) 2007 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
/*
 * Copyright (C) 2012 Trillian Mobile AB
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package libcore.util;

import java.io.File;
import java.io.IOException;
import java.nio.ByteOrder;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.TimeZone;
import java.util.TreeSet;

import libcore.io.BufferIterator;
import libcore.io.HeapBufferIterator;
import libcore.io.IoUtils;

/**
 * RoboVM implementation of {@link ZoneInfoDB.TzData} which loads the zoneinfo
 * from a standard Olson tzdata folder structure as used in Mac OS X, iOS and
 * most Linux distributions.
 */
final class TzDataRoboVM extends ZoneInfoDB.TzData {

    private static final String ZONE_DIRECTORY_NAME = "/usr/share/zoneinfo/";

    private static final String VERSION = readVersion();

    /**
     * Deprecated TimeZone aliases for JDK 1.1.x compatibility. We use the same mappings as used by GCJ.
     */
    private static final Map deprecatedAliases;
    
    /**
     * Cached {@link ZoneInfo}s. The first time a ZoneInfo is loaded we store it in this array at the same
     * index as the index of the timezone's id in ids. Subsequent calls for the same TimeZone will return a clone
     * of the cached ZoneInfo.
     */
    private static ZoneInfo[] zoneInfos;
    
    /**
     * The contents of the zone.tab file. Lazily loaded.
     */
    private String zoneTab;
    
    /**
     * The 'ids' array contains time zone ids sorted alphabetically, for binary searching.
     */
    private static String[] ids;

    static {
        deprecatedAliases = new HashMap();
        deprecatedAliases.put("ACT", "Australia/Darwin");
        deprecatedAliases.put("AET", "Australia/Sydney");
        deprecatedAliases.put("AGT", "America/Argentina/Buenos_Aires");
        deprecatedAliases.put("ART", "Africa/Cairo");
        deprecatedAliases.put("AST", "America/Juneau");
        deprecatedAliases.put("BET", "Etc/GMT-11");
        deprecatedAliases.put("BST", "Asia/Colombo");
        deprecatedAliases.put("CAT", "Africa/Gaborone");
        deprecatedAliases.put("CNT", "America/St_Johns");
        deprecatedAliases.put("CST", "CST6CDT");
        deprecatedAliases.put("CTT", "Asia/Brunei");
        deprecatedAliases.put("EAT", "Indian/Comoro");
        deprecatedAliases.put("ECT", "CET");
        deprecatedAliases.put("EST", "EST5EDT");
        deprecatedAliases.put("EST5", "EST5EDT");
        deprecatedAliases.put("IET", "EST5EDT");
        deprecatedAliases.put("IST", "Asia/Calcutta");
        deprecatedAliases.put("JST", "Asia/Seoul");
        deprecatedAliases.put("MIT", "Pacific/Niue");
        deprecatedAliases.put("MST", "MST7MDT");
        deprecatedAliases.put("MST7", "MST7MDT");
        deprecatedAliases.put("NET", "Indian/Mauritius");
        deprecatedAliases.put("NST", "Pacific/Auckland");
        deprecatedAliases.put("PLT", "Indian/Kerguelen");
        deprecatedAliases.put("PNT", "MST7MDT");
        deprecatedAliases.put("PRT", "America/Anguilla");
        deprecatedAliases.put("PST", "PST8PDT");
        deprecatedAliases.put("SST", "Pacific/Ponape");
        deprecatedAliases.put("VST", "Asia/Bangkok");        
        
        readIndex();
    }

    /**
     * Reads the file indicating the database version in use.
     */
    private static String readVersion() {
        // Mac OS X / iOS have a +VERSION file in /usr/share/zoneinfo containing the version number
        try {
            byte[] bytes = IoUtils.readFileAsByteArray(ZONE_DIRECTORY_NAME + "+VERSION");
            return new String(bytes, 0, bytes.length, StandardCharsets.ISO_8859_1).trim();
        } catch (IOException ex) {
            // Linux (at least Ubuntu) doesn't have a +VERSION file. Just return unknown.
            return "unknown";
        }            
    }

    /**
     * Traditionally, Unix systems have one file per time zone. We have one big data file, which
     * is just a concatenation of regular time zone files. To allow random access into this big
     * data file, we also have an index. We read the index at startup, and keep it in memory so
     * we can binary search by id when we need time zone data.
     *
     * The format of this file is, I believe, Android's own, and undocumented.
     *
     * All this code assumes strings are US-ASCII.
     */
    private static void readIndex() {
        try {
            readIndexMulti();
        } catch (Exception ex) {
            throw new AssertionError(ex);
        }
        
        zoneInfos = new ZoneInfo[ids.length];
    }

    /**
     * Populates the ids array when using a multi file timezone database. Reads the contents of 
     * {@link #ZONE_DIRECTORY_NAME}. We assume that all files beginning with an upper case letter at depth 0 to 2 are
     * proper TZ files. Fragile but known to work on Ubuntu 12.04, Mac OS X 10.7 and iOS 6.0.
     * This was added in RoboVM.
     */
    private static void readIndexMulti() throws IOException {
        TreeSet set = new TreeSet();
        
        for (File f1 : new File(ZONE_DIRECTORY_NAME).listFiles()) {
            String name1 = f1.getName();
            if ("Factory".equals(name1)) {
                // Skip
                continue;
            }
            if (name1.charAt(0) < 'A' || name1.charAt(0) > 'Z') {
                // Must start with upper case character. Skip.
                continue;
            }
            if (f1.isDirectory()) {
                for (File f2 : f1.listFiles()) {
                    String name2 = f2.getName();
                    if (name2.charAt(0) < 'A' || name2.charAt(0) > 'Z') {
                        // Must start with upper case character. Skip.
                        continue;
                    }
                    if (f2.isDirectory()) {
                        for (File f3 : f2.listFiles()) {
                            String name3 = f3.getName();
                            if (name3.charAt(0) < 'A' || name3.charAt(0) > 'Z') {
                                // Must start with upper case character. Skip.
                                continue;
                            }
                            if (!f3.isDirectory()) {
                                set.add(name1 + "/" + name2 + "/" + name3);
                            }
                        }
                    } else {
                        set.add(name1 + "/" + name2);                                
                    }
                }
            } else {
                set.add(name1);
            }
        }
        
        for (Entry alias : deprecatedAliases.entrySet()) {
            if (set.contains(alias.getValue())) {
                set.add(alias.getKey());
            }
        }

        ids = set.toArray(new String[set.size()]);
    }
    
    public TimeZone makeTimeZone(String id) throws IOException {
        return makeTimeZone(id, true);
    }
    
    private TimeZone makeTimeZone(String id, boolean clone) throws IOException {
        // Check the aliases first
        String realId = deprecatedAliases.get(id);
        if (realId != null) {
            return makeTimeZone(realId, clone);
        }
        
        // Work out where in the big data file this time zone is.
        int index = Arrays.binarySearch(ids, id);
        if (index < 0) {
            return null;
        }

        ZoneInfo zoneInfo = zoneInfos[index];
        if (zoneInfo != null) {
            return clone ? (TimeZone) zoneInfo.clone() : zoneInfo;
        }
        
        byte[] bytes = IoUtils.readFileAsByteArray(ZONE_DIRECTORY_NAME + id);
        BufferIterator it = HeapBufferIterator.iterator(bytes, 0, bytes.length, ByteOrder.BIG_ENDIAN);

        zoneInfo = (ZoneInfo) ZoneInfo.makeTimeZone(id, it);
        zoneInfos[index] = zoneInfo;
        return clone ? (TimeZone) zoneInfo.clone() : zoneInfo;
    }

    public String[] getAvailableIDs() {
        return ids.clone();
    }

    public String[] getAvailableIDs(int rawOffset) {
        List matches = new ArrayList();
        // Unfortunately we need load each ZoneInfo to determine the offset
        for (String id : ids) {
            try {
                TimeZone timeZone = makeTimeZone(id, false);
                if (timeZone.getRawOffset() == rawOffset) {
                    matches.add(id);
                }
            } catch (IOException e) {
            }
        }
        return matches.toArray(new String[matches.size()]);
    }

    public String getZoneTab() {
        if (zoneTab == null) {
            try {
                zoneTab = IoUtils.readFileAsString(ZONE_DIRECTORY_NAME + "zone.tab");
            } catch (IOException e) {
                throw new Error(e);
            }
        }
        return zoneTab;
    }

    @Override
    public String getDefaultID() {
        // In RoboVM we check the files /etc/timezone, /etc/localtime and /private/var/db/timezone/localtime.
        // The first file is a text file containing the id of the current time zone, e.g. 'Europe/Stockholm'
        // The two other files should be symlinks into the /usr/share/zoneinfo dir pointing out the current
        // time zone.
        String zoneName = null;
        try {
            // Ubuntu places the time zone in /etc/timezone
            zoneName = IoUtils.readFileAsString("/etc/timezone").trim();
        } catch (IOException e) {
        }
        if (zoneName == null || zoneName.isEmpty()) {
            // Mac OS X symlinks /etc/localtime to the current time zone file in /usr/share/zoneinfo
            File link = new File("/etc/localtime");
            if (!link.exists()) {
                // iOS uses the link /private/var/db/timezone/localtime instead
                link = new File("/private/var/db/timezone/localtime");
            }
            try {
                String path = link.getCanonicalPath(); // Resolve the link
                if (path.startsWith(ZONE_DIRECTORY_NAME)) {
                    zoneName = path.substring(ZONE_DIRECTORY_NAME.length());
                }
            } catch (IOException e) {
            }
        }
        return zoneName;
    }
    
    public String getVersion() {
        return VERSION;
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy