diff options
author | David Turner <digit@google.com> | 2011-02-18 14:53:03 -0800 |
---|---|---|
committer | Android (Google) Code Review <android-gerrit@google.com> | 2011-02-18 14:53:03 -0800 |
commit | 0999f8dcf22cd2ca541314a348720aedcf02ae48 (patch) | |
tree | c24bf408b2c069cabbba1fa0df45e483a34be39f | |
parent | 0233509c16046766bea9af6c7053cc6ceecef7a2 (diff) | |
parent | d40e63ee47e4a7f072a9d9a20e09c26f0090b02c (diff) | |
download | bionic-0999f8dcf22cd2ca541314a348720aedcf02ae48.zip bionic-0999f8dcf22cd2ca541314a348720aedcf02ae48.tar.gz bionic-0999f8dcf22cd2ca541314a348720aedcf02ae48.tar.bz2 |
Merge "Move the zoneinfo generation tool into bionic."
-rw-r--r-- | libc/tools/zoneinfo/ZoneCompactor.java | 166 | ||||
-rw-r--r-- | libc/tools/zoneinfo/ZoneInfo.java | 272 | ||||
-rwxr-xr-x | libc/tools/zoneinfo/generate | 79 |
3 files changed, 517 insertions, 0 deletions
diff --git a/libc/tools/zoneinfo/ZoneCompactor.java b/libc/tools/zoneinfo/ZoneCompactor.java new file mode 100644 index 0000000..b657748 --- /dev/null +++ b/libc/tools/zoneinfo/ZoneCompactor.java @@ -0,0 +1,166 @@ + +import java.io.*; +import java.util.*; + +// usage: java ZoneCompiler <setup file> <top-level directory> +// +// Compile a set of tzfile-formatted files into a single file plus +// an index file. +// +// The compilation is controlled by a setup file, which is provided as a +// command-line argument. The setup file has the form: +// +// Link <toName> <fromName> +// ... +// <zone filename> +// ... +// +// Note that the links must be declared prior to the zone names. A +// zone name is a filename relative to the source directory such as +// 'GMT', 'Africa/Dakar', or 'America/Argentina/Jujuy'. +// +// Use the 'zic' command-line tool to convert from flat files +// (e.g., 'africa', 'northamerica') into a suitable source directory +// hierarchy for this tool (e.g., 'data/Africa/Abidjan'). +// +// Example: +// zic -d data tz2007h +// javac ZoneCompactor.java +// java ZoneCompactor setup data +// <produces zoneinfo.dat and zoneinfo.idx> + +public class ZoneCompactor { + + // Zone name synonyms + Map<String,String> links = new HashMap<String,String>(); + + // File starting bytes by zone name + Map<String,Integer> starts = new HashMap<String,Integer>(); + + // File lengths by zone name + Map<String,Integer> lengths = new HashMap<String,Integer>(); + + // Raw GMT offsets by zone name + Map<String,Integer> offsets = new HashMap<String,Integer>(); + int start = 0; + + // Maximum number of characters in a zone name, including '\0' terminator + private static final int MAXNAME = 40; + + // Concatenate the contents of 'inFile' onto 'out' + // and return the contents as a byte array. + private static byte[] copyFile(File inFile, OutputStream out) + throws Exception { + byte[] ret = new byte[0]; + + InputStream in = new FileInputStream(inFile); + byte[] buf = new byte[8192]; + while (true) { + int nbytes = in.read(buf); + if (nbytes == -1) { + break; + } + out.write(buf, 0, nbytes); + + byte[] nret = new byte[ret.length + nbytes]; + System.arraycopy(ret, 0, nret, 0, ret.length); + System.arraycopy(buf, 0, nret, ret.length, nbytes); + ret = nret; + } + out.flush(); + return ret; + } + + // Write a 32-bit integer in network byte order + private void writeInt(OutputStream os, int x) throws IOException { + os.write((x >> 24) & 0xff); + os.write((x >> 16) & 0xff); + os.write((x >> 8) & 0xff); + os.write( x & 0xff); + } + + public ZoneCompactor(String setupFilename, String dirName) + throws Exception { + File zoneInfoFile = new File("zoneinfo.dat"); + zoneInfoFile.delete(); + OutputStream zoneInfo = new FileOutputStream(zoneInfoFile); + + BufferedReader rdr = new BufferedReader(new FileReader(setupFilename)); + + String s; + while ((s = rdr.readLine()) != null) { + s = s.trim(); + if (s.startsWith("Link")) { + StringTokenizer st = new StringTokenizer(s); + st.nextToken(); + String to = st.nextToken(); + String from = st.nextToken(); + links.put(from, to); + } else { + String link = links.get(s); + if (link == null) { + File f = new File(dirName, s); + long length = f.length(); + starts.put(s, new Integer(start)); + lengths.put(s, new Integer((int)length)); + + start += length; + byte[] data = copyFile(f, zoneInfo); + + TimeZone tz = ZoneInfo.make(s, data); + int gmtOffset = tz.getRawOffset(); + offsets.put(s, new Integer(gmtOffset)); + } + } + } + zoneInfo.close(); + + // Fill in fields for links + Iterator<String> iter = links.keySet().iterator(); + while (iter.hasNext()) { + String from = iter.next(); + String to = links.get(from); + + starts.put(from, starts.get(to)); + lengths.put(from, lengths.get(to)); + offsets.put(from, offsets.get(to)); + } + + File idxFile = new File("zoneinfo.idx"); + idxFile.delete(); + FileOutputStream idx = new FileOutputStream(idxFile); + + ArrayList<String> l = new ArrayList<String>(); + l.addAll(starts.keySet()); + Collections.sort(l); + Iterator<String> ziter = l.iterator(); + while (ziter.hasNext()) { + String zname = ziter.next(); + if (zname.length() >= MAXNAME) { + System.err.println("Error - zone filename exceeds " + + (MAXNAME - 1) + " characters!"); + } + + byte[] znameBuf = new byte[MAXNAME]; + for (int i = 0; i < zname.length(); i++) { + znameBuf[i] = (byte)zname.charAt(i); + } + idx.write(znameBuf); + writeInt(idx, starts.get(zname).intValue()); + writeInt(idx, lengths.get(zname).intValue()); + writeInt(idx, offsets.get(zname).intValue()); + } + idx.close(); + + // System.out.println("maxLength = " + maxLength); + } + + public static void main(String[] args) throws Exception { + if (args.length != 2) { + System.err.println("usage: java ZoneCompactor <setup> <data dir>"); + System.exit(0); + } + new ZoneCompactor(args[0], args[1]); + } + +} diff --git a/libc/tools/zoneinfo/ZoneInfo.java b/libc/tools/zoneinfo/ZoneInfo.java new file mode 100644 index 0000000..99507ae --- /dev/null +++ b/libc/tools/zoneinfo/ZoneInfo.java @@ -0,0 +1,272 @@ +/* + * 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. + */ + +import java.io.IOException; +import java.util.Arrays; +import java.util.Date; +import java.util.TimeZone; + +/** + * Copied from ZoneInfo and ZoneInfoDB in dalvik. + * {@hide} + */ +public class ZoneInfo extends TimeZone { + + private static final long MILLISECONDS_PER_DAY = 24 * 60 * 60 * 1000; + private static final long MILLISECONDS_PER_400_YEARS = + MILLISECONDS_PER_DAY * (400 * 365 + 100 - 3); + + private static final long UNIX_OFFSET = 62167219200000L; + + private static final int[] NORMAL = new int[] { + 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, + }; + + private static final int[] LEAP = new int[] { + 0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, + }; + + private static String nullName(byte[] data, int where, int off) { + if (off < 0) + return null; + + int end = where + off; + while (end < data.length && data[end] != '\0') + end++; + + return new String(data, where + off, end - (where + off)); + } + + public static ZoneInfo make(String name, byte[] data) { + int ntransition = read4(data, 32); + int ngmtoff = read4(data, 36); + int base = 44; + + int[] transitions = new int[ntransition]; + for (int i = 0; i < ntransition; i++) + transitions[i] = read4(data, base + 4 * i); + base += 4 * ntransition; + + byte[] type = new byte[ntransition]; + for (int i = 0; i < ntransition; i++) + type[i] = data[base + i]; + base += ntransition; + + int[] gmtoff = new int[ngmtoff]; + byte[] isdst = new byte[ngmtoff]; + byte[] abbrev = new byte[ngmtoff]; + for (int i = 0; i < ngmtoff; i++) { + gmtoff[i] = read4(data, base + 6 * i); + isdst[i] = data[base + 6 * i + 4]; + abbrev[i] = data[base + 6 * i + 5]; + } + + base += 6 * ngmtoff; + + return new ZoneInfo(name, transitions, type, gmtoff, isdst, abbrev, data, base); + } + + private static int read4(byte[] data, int off) { + return ((data[off ] & 0xFF) << 24) | + ((data[off + 1] & 0xFF) << 16) | + ((data[off + 2] & 0xFF) << 8) | + ((data[off + 3] & 0xFF) << 0); + } + + /*package*/ ZoneInfo(String name, int[] transitions, byte[] type, + int[] gmtoff, byte[] isdst, byte[] abbrev, + byte[] data, int abbrevoff) { + mTransitions = transitions; + mTypes = type; + mGmtOffs = gmtoff; + mIsDsts = isdst; + mUseDst = false; + setID(name); + + // Find the latest GMT and non-GMT offsets for their abbreviations + + int lastdst; + for (lastdst = mTransitions.length - 1; lastdst >= 0; lastdst--) { + if (mIsDsts[mTypes[lastdst] & 0xFF] != 0) + break; + } + + int laststd; + for (laststd = mTransitions.length - 1; laststd >= 0; laststd--) { + if (mIsDsts[mTypes[laststd] & 0xFF] == 0) + break; + } + + if (lastdst >= 0) { + mDaylightName = nullName(data, abbrevoff, + abbrev[mTypes[lastdst] & 0xFF]); + } + if (laststd >= 0) { + mStandardName = nullName(data, abbrevoff, + abbrev[mTypes[laststd] & 0xFF]); + } + + // Use the latest non-DST offset if any as the raw offset + + if (laststd < 0) { + laststd = 0; + } + + if (laststd >= mTypes.length) { + mRawOffset = mGmtOffs[0]; + } else { + mRawOffset = mGmtOffs[mTypes[laststd] & 0xFF]; + } + + // Subtract the raw offset from all offsets so it can be changed + // and affect them too. + // Find whether there exist any observances of DST. + + for (int i = 0; i < mGmtOffs.length; i++) { + mGmtOffs[i] -= mRawOffset; + + if (mIsDsts[i] != 0) { + mUseDst = true; + } + } + + mRawOffset *= 1000; + } + + @Override + public int getOffset(@SuppressWarnings("unused") int era, + int year, int month, int day, + @SuppressWarnings("unused") int dayOfWeek, + int millis) { + // XXX This assumes Gregorian always; Calendar switches from + // Julian to Gregorian in 1582. What calendar system are the + // arguments supposed to come from? + + long calc = (year / 400) * MILLISECONDS_PER_400_YEARS; + year %= 400; + + calc += year * (365 * MILLISECONDS_PER_DAY); + calc += ((year + 3) / 4) * MILLISECONDS_PER_DAY; + + if (year > 0) + calc -= ((year - 1) / 100) * MILLISECONDS_PER_DAY; + + boolean isLeap = (year == 0 || (year % 4 == 0 && year % 100 != 0)); + int[] mlen = isLeap ? LEAP : NORMAL; + + calc += mlen[month] * MILLISECONDS_PER_DAY; + calc += (day - 1) * MILLISECONDS_PER_DAY; + calc += millis; + + calc -= mRawOffset; + calc -= UNIX_OFFSET; + + return getOffset(calc); + } + + @Override + public int getOffset(long when) { + int unix = (int) (when / 1000); + int trans = Arrays.binarySearch(mTransitions, unix); + + if (trans == ~0) { + return mGmtOffs[0] * 1000 + mRawOffset; + } + if (trans < 0) { + trans = ~trans - 1; + } + + return mGmtOffs[mTypes[trans] & 0xFF] * 1000 + mRawOffset; + } + + @Override + public int getRawOffset() { + return mRawOffset; + } + + @Override + public void setRawOffset(int off) { + mRawOffset = off; + } + + @Override + public boolean inDaylightTime(Date when) { + int unix = (int) (when.getTime() / 1000); + int trans = Arrays.binarySearch(mTransitions, unix); + + if (trans == ~0) { + return mIsDsts[0] != 0; + } + if (trans < 0) { + trans = ~trans - 1; + } + + return mIsDsts[mTypes[trans] & 0xFF] != 0; + } + + @Override + public boolean useDaylightTime() { + return mUseDst; + } + + private int mRawOffset; + private int[] mTransitions; + private int[] mGmtOffs; + private byte[] mTypes; + private byte[] mIsDsts; + private boolean mUseDst; + private String mDaylightName; + private String mStandardName; + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof ZoneInfo)) { + return false; + } + ZoneInfo other = (ZoneInfo) obj; + return mUseDst == other.mUseDst + && (mDaylightName == null ? other.mDaylightName == null : + mDaylightName.equals(other.mDaylightName)) + && (mStandardName == null ? other.mStandardName == null : + mStandardName.equals(other.mStandardName)) + && mRawOffset == other.mRawOffset + // Arrays.equals returns true if both arrays are null + && Arrays.equals(mGmtOffs, other.mGmtOffs) + && Arrays.equals(mIsDsts, other.mIsDsts) + && Arrays.equals(mTypes, other.mTypes) + && Arrays.equals(mTransitions, other.mTransitions); + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((mDaylightName == null) ? 0 : + mDaylightName.hashCode()); + result = prime * result + Arrays.hashCode(mGmtOffs); + result = prime * result + Arrays.hashCode(mIsDsts); + result = prime * result + mRawOffset; + result = prime * result + ((mStandardName == null) ? 0 : + mStandardName.hashCode()); + result = prime * result + Arrays.hashCode(mTransitions); + result = prime * result + Arrays.hashCode(mTypes); + result = prime * result + (mUseDst ? 1231 : 1237); + return result; + } +} diff --git a/libc/tools/zoneinfo/generate b/libc/tools/zoneinfo/generate new file mode 100755 index 0000000..5755e26 --- /dev/null +++ b/libc/tools/zoneinfo/generate @@ -0,0 +1,79 @@ +#!/bin/bash +# Run with no arguments from any directory, with no special setup required. + +# Abort if any command returns an error exit status, or if an undefined +# variable is used. +set -e +set -u + +echo "Looking for bionic..." +bionic_dir=$(cd $(dirname $0)/../../.. && pwd) +bionic_zoneinfo_dir=$bionic_dir/libc/zoneinfo +bionic_zoneinfo_tools_dir=$bionic_dir/libc/tools/zoneinfo +if [[ ! -d "$bionic_zoneinfo_dir" || ! -d "$bionic_zoneinfo_tools_dir" ]]; then + echo "Can't find bionic's zoneinfo directories!" + exit 1 +fi + +echo "Switching to temporary directory..." +temp_dir=`mktemp -d` +cd $temp_dir +trap "rm -rf $temp_dir; exit" INT TERM EXIT + +# URL from "Sources for Time Zone and Daylight Saving Time Data" +# http://www.twinsun.com/tz/tz-link.htm +echo "Looking for new tzdata..." +wget -N --no-verbose 'ftp://elsie.nci.nih.gov/pub/tzdata*.tar.gz' +zoneinfo_version_file=$bionic_zoneinfo_dir/zoneinfo.version +if [ -f "$zoneinfo_version_file" ]; then + current_version=tzdata`sed s/\n// < $zoneinfo_version_file` +else + current_version=missing +fi +latest_archive=`ls -r -v tzdata*.tar.gz | head -n1` +latest_version=`basename $latest_archive .tar.gz` +if [ "$current_version" == "$latest_version" ]; then + echo "You already have the latest tzdata ($latest_version)!" + exit 1 +fi + +echo "Extracting $latest_version..." +mkdir $latest_version +tar -C $latest_version -zxf $latest_archive + +echo "Compiling $latest_version..." +mkdir data +for i in \ + africa \ + antarctica \ + asia \ + australasia \ + etcetera \ + europe \ + factory \ + northamerica \ + solar87 \ + solar88 \ + solar89 \ + southamerica +do + zic -d data $latest_version/$i +done + +echo "Compacting $latest_version..." +( + cat $latest_version/* | grep '^Link' | awk '{print $1, $2, $3}' + ( + cat $latest_version/* | grep '^Zone' | awk '{print $2}' + cat $latest_version/* | grep '^Link' | awk '{print $3}' + ) | LC_ALL="C" sort +) | grep -v Riyadh8 > setup + +javac -d . \ + $bionic_zoneinfo_tools_dir/ZoneCompactor.java \ + $bionic_zoneinfo_tools_dir/ZoneInfo.java +java ZoneCompactor setup data + +echo "Updating bionic to $latest_version..." +mv zoneinfo.dat zoneinfo.idx $bionic_zoneinfo_dir +echo $latest_version | sed 's/tzdata//' > $bionic_zoneinfo_dir/zoneinfo.version |