blob: f47afd13ebf3b2d89e0c35e37de070a6b8b87388 [file] [log] [blame]
Elliott Hughesd40e63e2011-02-17 16:20:07 -08001
2import java.io.*;
Elliott Hugheseb061292012-10-19 12:05:24 -07003import java.nio.ByteOrder;
Elliott Hughesd40e63e2011-02-17 16:20:07 -08004import java.util.*;
Elliott Hugheseb061292012-10-19 12:05:24 -07005import libcore.io.BufferIterator;
6import libcore.util.ZoneInfo;
Elliott Hughesd40e63e2011-02-17 16:20:07 -08007
Elliott Hughes328a4842012-10-19 13:03:52 -07008// usage: java ZoneCompiler <setup file> <data directory> <output directory> <tzdata version>
Elliott Hughesd40e63e2011-02-17 16:20:07 -08009//
Elliott Hughes5b1497a2012-10-19 14:47:37 -070010// Compile a set of tzfile-formatted files into a single file containing an index.
Elliott Hughesd40e63e2011-02-17 16:20:07 -080011//
12// The compilation is controlled by a setup file, which is provided as a
13// command-line argument. The setup file has the form:
14//
15// Link <toName> <fromName>
16// ...
17// <zone filename>
18// ...
19//
Elliott Hughes5b1497a2012-10-19 14:47:37 -070020// Note that the links must be declared prior to the zone names.
21// A zone name is a filename relative to the source directory such as
Elliott Hughesd40e63e2011-02-17 16:20:07 -080022// 'GMT', 'Africa/Dakar', or 'America/Argentina/Jujuy'.
23//
24// Use the 'zic' command-line tool to convert from flat files
Elliott Hughes5b1497a2012-10-19 14:47:37 -070025// (such as 'africa' or 'northamerica') to a directory
26// hierarchy suitable for this tool (containing files such as 'data/Africa/Abidjan').
Elliott Hughesd40e63e2011-02-17 16:20:07 -080027//
Elliott Hughesd40e63e2011-02-17 16:20:07 -080028
29public class ZoneCompactor {
Elliott Hughes5b1497a2012-10-19 14:47:37 -070030 public static class ByteArrayBufferIteratorBE extends BufferIterator {
31 private final byte[] bytes;
32 private int offset = 0;
Elliott Hughesd40e63e2011-02-17 16:20:07 -080033
Elliott Hughes5b1497a2012-10-19 14:47:37 -070034 public ByteArrayBufferIteratorBE(byte[] bytes) {
35 this.bytes = bytes;
36 this.offset = 0;
Elliott Hugheseb061292012-10-19 12:05:24 -070037 }
Elliott Hughesd40e63e2011-02-17 16:20:07 -080038
Elliott Hughes5b1497a2012-10-19 14:47:37 -070039 public void seek(int offset) {
40 this.offset = offset;
Elliott Hughesd40e63e2011-02-17 16:20:07 -080041 }
Elliott Hugheseb061292012-10-19 12:05:24 -070042
Elliott Hughes5b1497a2012-10-19 14:47:37 -070043 public void skip(int byteCount) {
44 this.offset += byteCount;
Elliott Hughesd40e63e2011-02-17 16:20:07 -080045 }
46
Elliott Hughes5b1497a2012-10-19 14:47:37 -070047 public void readByteArray(byte[] dst, int dstOffset, int byteCount) {
48 System.arraycopy(bytes, offset, dst, dstOffset, byteCount);
49 offset += byteCount;
Elliott Hughesd40e63e2011-02-17 16:20:07 -080050 }
51
Elliott Hughes5b1497a2012-10-19 14:47:37 -070052 public byte readByte() {
53 return bytes[offset++];
Elliott Hughesd40e63e2011-02-17 16:20:07 -080054 }
Elliott Hughes5b1497a2012-10-19 14:47:37 -070055
56 public int readInt() {
57 return ((readByte() & 0xff) << 24) | ((readByte() & 0xff) << 16) | ((readByte() & 0xff) << 8) | (readByte() & 0xff);
58 }
59
60 public void readIntArray(int[] dst, int dstOffset, int intCount) {
61 for (int i = 0; i < intCount; ++i) {
62 dst[dstOffset++] = readInt();
63 }
64 }
65
66 public short readShort() {
67 throw new UnsupportedOperationException();
68 }
69 }
70
71 // Maximum number of characters in a zone name, including '\0' terminator
72 private static final int MAXNAME = 40;
73
74 // Zone name synonyms
75 private Map<String,String> links = new HashMap<String,String>();
76
77 // File starting bytes by zone name
78 private Map<String,Integer> starts = new HashMap<String,Integer>();
79
80 // File lengths by zone name
81 private Map<String,Integer> lengths = new HashMap<String,Integer>();
82
83 // Raw GMT offsets by zone name
84 private Map<String,Integer> offsets = new HashMap<String,Integer>();
85 private int start = 0;
86
87 // Concatenate the contents of 'inFile' onto 'out'
88 // and return the contents as a byte array.
89 private static byte[] copyFile(File inFile, OutputStream out) throws Exception {
90 byte[] ret = new byte[0];
91
92 InputStream in = new FileInputStream(inFile);
93 byte[] buf = new byte[8192];
94 while (true) {
95 int nbytes = in.read(buf);
96 if (nbytes == -1) {
97 break;
98 }
99 out.write(buf, 0, nbytes);
100
101 byte[] nret = new byte[ret.length + nbytes];
102 System.arraycopy(ret, 0, nret, 0, ret.length);
103 System.arraycopy(buf, 0, nret, ret.length, nbytes);
104 ret = nret;
105 }
106 out.flush();
107 return ret;
108 }
109
Elliott Hughes23935352012-10-22 14:47:58 -0700110 public ZoneCompactor(String setupFile, String dataDirectory, String zoneTabFile, String outputDirectory, String version) throws Exception {
Elliott Hughes5b1497a2012-10-19 14:47:37 -0700111 // Read the setup file, and concatenate all the data.
112 ByteArrayOutputStream allData = new ByteArrayOutputStream();
113 BufferedReader reader = new BufferedReader(new FileReader(setupFile));
114 String s;
115 while ((s = reader.readLine()) != null) {
116 s = s.trim();
117 if (s.startsWith("Link")) {
118 StringTokenizer st = new StringTokenizer(s);
119 st.nextToken();
120 String to = st.nextToken();
121 String from = st.nextToken();
122 links.put(from, to);
123 } else {
124 String link = links.get(s);
125 if (link == null) {
126 File sourceFile = new File(dataDirectory, s);
127 long length = sourceFile.length();
128 starts.put(s, start);
129 lengths.put(s, (int) length);
130
131 start += length;
132 byte[] data = copyFile(sourceFile, allData);
133
134 BufferIterator it = new ByteArrayBufferIteratorBE(data);
135 TimeZone tz = ZoneInfo.makeTimeZone(s, it);
136 int gmtOffset = tz.getRawOffset();
137 offsets.put(s, gmtOffset);
138 }
139 }
140 }
Elliott Hughes23935352012-10-22 14:47:58 -0700141 reader.close();
Elliott Hughes5b1497a2012-10-19 14:47:37 -0700142
143 // Fill in fields for links.
144 Iterator<String> it = links.keySet().iterator();
145 while (it.hasNext()) {
146 String from = it.next();
147 String to = links.get(from);
148
149 starts.put(from, starts.get(to));
150 lengths.put(from, lengths.get(to));
151 offsets.put(from, offsets.get(to));
152 }
153
154 // Create/truncate the destination file.
155 RandomAccessFile f = new RandomAccessFile(new File(outputDirectory, "tzdata"), "rw");
156 f.setLength(0);
157
158 // Write the header.
159
Elliott Hughes23935352012-10-22 14:47:58 -0700160 // byte[12] tzdata_version -- 'tzdata2012f\0'
161 // int index_offset -- so we can slip in extra header fields in a backwards-compatible way
Elliott Hughes5b1497a2012-10-19 14:47:37 -0700162 // int data_offset
163 // int zonetab_offset
164
165 // tzdata_version
166 f.write(toAscii(new byte[12], version));
167
Elliott Hughes5b1497a2012-10-19 14:47:37 -0700168 // Write dummy values for the three offsets, and remember where we need to seek back to later
169 // when we have the real values.
170 int index_offset_offset = (int) f.getFilePointer();
171 f.writeInt(0);
172 int data_offset_offset = (int) f.getFilePointer();
173 f.writeInt(0);
174 int zonetab_offset_offset = (int) f.getFilePointer();
175 f.writeInt(0);
176
177 int index_offset = (int) f.getFilePointer();
178
179 // Write the index.
180 ArrayList<String> sortedOlsonIds = new ArrayList<String>();
181 sortedOlsonIds.addAll(starts.keySet());
182 Collections.sort(sortedOlsonIds);
183 it = sortedOlsonIds.iterator();
184 while (it.hasNext()) {
185 String zoneName = it.next();
186 if (zoneName.length() >= MAXNAME) {
187 throw new RuntimeException("zone filename too long: " + zoneName.length());
188 }
189
190 f.write(toAscii(new byte[MAXNAME], zoneName));
191 f.writeInt(starts.get(zoneName));
192 f.writeInt(lengths.get(zoneName));
193 f.writeInt(offsets.get(zoneName));
194 }
195
196 int data_offset = (int) f.getFilePointer();
197
198 // Write the data.
199 f.write(allData.toByteArray());
200
Elliott Hughesaf7f2f22013-03-14 17:10:24 -0700201 int zonetab_offset = (int) f.getFilePointer();
202
Elliott Hughes23935352012-10-22 14:47:58 -0700203 // Copy the zone.tab.
204 reader = new BufferedReader(new FileReader(zoneTabFile));
205 while ((s = reader.readLine()) != null) {
206 if (!s.startsWith("#")) {
207 f.writeBytes(s);
208 f.write('\n');
209 }
210 }
211 reader.close();
212
Elliott Hughes5b1497a2012-10-19 14:47:37 -0700213 // Go back and fix up the offsets in the header.
214 f.seek(index_offset_offset);
215 f.writeInt(index_offset);
216 f.seek(data_offset_offset);
217 f.writeInt(data_offset);
218 f.seek(zonetab_offset_offset);
219 f.writeInt(zonetab_offset);
220
221 f.close();
222 }
223
224 private static byte[] toAscii(byte[] dst, String src) {
225 for (int i = 0; i < src.length(); ++i) {
226 if (src.charAt(i) > '~') {
227 throw new RuntimeException("non-ASCII string: " + src);
228 }
229 dst[i] = (byte) src.charAt(i);
230 }
231 return dst;
232 }
233
234 public static void main(String[] args) throws Exception {
Elliott Hughes23935352012-10-22 14:47:58 -0700235 if (args.length != 5) {
236 System.err.println("usage: java ZoneCompactor <setup file> <data directory> <zone.tab file> <output directory> <tzdata version>");
Elliott Hughes5b1497a2012-10-19 14:47:37 -0700237 System.exit(0);
238 }
Elliott Hughes23935352012-10-22 14:47:58 -0700239 new ZoneCompactor(args[0], args[1], args[2], args[3], args[4]);
Elliott Hughes5b1497a2012-10-19 14:47:37 -0700240 }
Elliott Hughesd40e63e2011-02-17 16:20:07 -0800241}