1 2 import java.io.*; 3 import java.util.*; 4 5 // usage: java ZoneCompiler <setup file> <top-level directory> 6 // 7 // Compile a set of tzfile-formatted files into a single file plus 8 // an index file. 9 // 10 // The compilation is controlled by a setup file, which is provided as a 11 // command-line argument. The setup file has the form: 12 // 13 // Link <toName> <fromName> 14 // ... 15 // <zone filename> 16 // ... 17 // 18 // Note that the links must be declared prior to the zone names. A 19 // zone name is a filename relative to the source directory such as 20 // 'GMT', 'Africa/Dakar', or 'America/Argentina/Jujuy'. 21 // 22 // Use the 'zic' command-line tool to convert from flat files 23 // (e.g., 'africa', 'northamerica') into a suitable source directory 24 // hierarchy for this tool (e.g., 'data/Africa/Abidjan'). 25 // 26 // Example: 27 // zic -d data tz2007h 28 // javac ZoneCompactor.java 29 // java ZoneCompactor setup data 30 // <produces zoneinfo.dat and zoneinfo.idx> 31 32 public class ZoneCompactor { 33 34 // Zone name synonyms 35 Map<String,String> links = new HashMap<String,String>(); 36 37 // File starting bytes by zone name 38 Map<String,Integer> starts = new HashMap<String,Integer>(); 39 40 // File lengths by zone name 41 Map<String,Integer> lengths = new HashMap<String,Integer>(); 42 43 // Raw GMT offsets by zone name 44 Map<String,Integer> offsets = new HashMap<String,Integer>(); 45 int start = 0; 46 47 // Maximum number of characters in a zone name, including '\0' terminator 48 private static final int MAXNAME = 40; 49 50 // Concatenate the contents of 'inFile' onto 'out' 51 // and return the contents as a byte array. 52 private static byte[] copyFile(File inFile, OutputStream out) 53 throws Exception { 54 byte[] ret = new byte[0]; 55 56 InputStream in = new FileInputStream(inFile); 57 byte[] buf = new byte[8192]; 58 while (true) { 59 int nbytes = in.read(buf); 60 if (nbytes == -1) { 61 break; 62 } 63 out.write(buf, 0, nbytes); 64 65 byte[] nret = new byte[ret.length + nbytes]; 66 System.arraycopy(ret, 0, nret, 0, ret.length); 67 System.arraycopy(buf, 0, nret, ret.length, nbytes); 68 ret = nret; 69 } 70 out.flush(); 71 return ret; 72 } 73 74 // Write a 32-bit integer in network byte order 75 private void writeInt(OutputStream os, int x) throws IOException { 76 os.write((x >> 24) & 0xff); 77 os.write((x >> 16) & 0xff); 78 os.write((x >> 8) & 0xff); 79 os.write( x & 0xff); 80 } 81 82 public ZoneCompactor(String setupFilename, String dirName) 83 throws Exception { 84 File zoneInfoFile = new File("zoneinfo.dat"); 85 zoneInfoFile.delete(); 86 OutputStream zoneInfo = new FileOutputStream(zoneInfoFile); 87 88 BufferedReader rdr = new BufferedReader(new FileReader(setupFilename)); 89 90 String s; 91 while ((s = rdr.readLine()) != null) { 92 s = s.trim(); 93 if (s.startsWith("Link")) { 94 StringTokenizer st = new StringTokenizer(s); 95 st.nextToken(); 96 String to = st.nextToken(); 97 String from = st.nextToken(); 98 links.put(from, to); 99 } else { 100 String link = links.get(s); 101 if (link == null) { 102 File f = new File(dirName, s); 103 long length = f.length(); 104 starts.put(s, new Integer(start)); 105 lengths.put(s, new Integer((int)length)); 106 107 start += length; 108 byte[] data = copyFile(f, zoneInfo); 109 110 TimeZone tz = ZoneInfo.make(s, data); 111 int gmtOffset = tz.getRawOffset(); 112 offsets.put(s, new Integer(gmtOffset)); 113 } 114 } 115 } 116 zoneInfo.close(); 117 118 // Fill in fields for links 119 Iterator<String> iter = links.keySet().iterator(); 120 while (iter.hasNext()) { 121 String from = iter.next(); 122 String to = links.get(from); 123 124 starts.put(from, starts.get(to)); 125 lengths.put(from, lengths.get(to)); 126 offsets.put(from, offsets.get(to)); 127 } 128 129 File idxFile = new File("zoneinfo.idx"); 130 idxFile.delete(); 131 FileOutputStream idx = new FileOutputStream(idxFile); 132 133 ArrayList<String> l = new ArrayList<String>(); 134 l.addAll(starts.keySet()); 135 Collections.sort(l); 136 Iterator<String> ziter = l.iterator(); 137 while (ziter.hasNext()) { 138 String zname = ziter.next(); 139 if (zname.length() >= MAXNAME) { 140 System.err.println("Error - zone filename exceeds " + 141 (MAXNAME - 1) + " characters!"); 142 } 143 144 byte[] znameBuf = new byte[MAXNAME]; 145 for (int i = 0; i < zname.length(); i++) { 146 znameBuf[i] = (byte)zname.charAt(i); 147 } 148 idx.write(znameBuf); 149 writeInt(idx, starts.get(zname).intValue()); 150 writeInt(idx, lengths.get(zname).intValue()); 151 writeInt(idx, offsets.get(zname).intValue()); 152 } 153 idx.close(); 154 155 // System.out.println("maxLength = " + maxLength); 156 } 157 158 public static void main(String[] args) throws Exception { 159 if (args.length != 2) { 160 System.err.println("usage: java ZoneCompactor <setup> <data dir>"); 161 System.exit(0); 162 } 163 new ZoneCompactor(args[0], args[1]); 164 } 165 166 } 167