1 /* 2 * Copyright (c) 2025 Huawei Device Co., Ltd. 3 * Licensed under the Apache License, Version 2.0 (the "License"); 4 * you may not use this file except in compliance with the License. 5 * You may obtain a copy of the License at 6 * 7 * http://www.apache.org/licenses/LICENSE-2.0 8 * 9 * Unless required by applicable law or agreed to in writing, software 10 * distributed under the License is distributed on an "AS IS" BASIS, 11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 * See the License for the specific language governing permissions and 13 * limitations under the License. 14 */ 15 16 import java.io.*; 17 import java.util.*; 18 19 /** 20 * usage: java ZoneCompactor <setup file> <data directory> <output directory> <tzdata version> 21 * 22 * Compile a set of tzfile-formatted files into a single file containing an index. 23 * 24 * The compilation is controlled by a setup file, which is provided as a 25 * command-line argument. The setup file has the form: 26 * 27 * Link <toName> <fromName> 28 * 29 * <zone filename> 30 * 31 * 32 * Note that the links must be declared prior to the zone names. 33 * A zone name is a filename relative to the source directory such as 34 * 'GMT', 'Africa/Dakar', or 'America/Argentina/Jujuy'. 35 * 36 * Use the 'zic' command-line tool to convert from flat files 37 * (such as 'africa' or 'northamerica') to a directory 38 * hierarchy suitable for this tool (containing files such as 'data/Africa/Abidjan'). 39 * 40 * @since 2025/02/08 41 */ 42 43 public class ZoneCompactor { 44 // Maximum number of characters in a zone name, including '\0' terminator. 45 private static final int MAXNAME = 40; 46 47 // Zone name synonyms. 48 private Map<String,String> links = new HashMap<>(); 49 50 // File offsets by zone name. 51 private Map<String,Integer> offsets = new HashMap<>(); 52 53 // File lengths by zone name. 54 private Map<String,Integer> lengths = new HashMap<>(); 55 56 // Concatenate the contents of 'inFile' onto 'out'. copyFile(File inFile, OutputStream out)57 private static void copyFile(File inFile, OutputStream out) throws Exception { 58 InputStream in = new FileInputStream(inFile); 59 byte[] buf = new byte[8192]; 60 while (true) { 61 int nbytes = in.read(buf); 62 if (nbytes == -1) { 63 break; 64 } 65 out.write(buf, 0, nbytes); 66 } 67 out.flush(); 68 } 69 ZoneCompactor(String setupFile, String dataDirectory, String outputDirectory, String version)70 public ZoneCompactor(String setupFile, String dataDirectory, String outputDirectory, 71 String version) throws Exception { 72 // Read the setup file and concatenate all the data. 73 Set<String> zoneIds = new LinkedHashSet<>(); 74 try (BufferedReader reader = new BufferedReader(new FileReader(setupFile))) { 75 String s; 76 while ((s = reader.readLine()) != null) { 77 s = s.trim(); 78 zoneIds.add(s); 79 } 80 } 81 82 ByteArrayOutputStream allData = new ByteArrayOutputStream(); 83 int offset = 0; 84 for (String zoneId : zoneIds) { 85 File sourceFile = new File(dataDirectory, zoneId); 86 long length = sourceFile.length(); 87 offsets.put(zoneId, offset); 88 lengths.put(zoneId, (int) length); 89 offset += length; 90 copyFile(sourceFile, allData); 91 } 92 93 // Create/truncate the destination file. 94 RandomAccessFile f = new RandomAccessFile(new File(outputDirectory, "tzdata"), "rw"); 95 f.setLength(0); 96 // tzdata_version 97 f.write(toAscii(new byte[12], version)); 98 // Write placeholder values for the offsets, and remember where we need to seek back to later 99 // when we have the real values. 100 int index_offset_offset = (int) f.getFilePointer(); 101 f.writeInt(0); 102 int data_offset_offset = (int) f.getFilePointer(); 103 f.writeInt(0); 104 // The final offset serves as a placeholder for sections that might be added in future and 105 // ensures we know the size of the final "real" section. Relying on the last section ending at 106 // EOF would make it harder to append sections to the end of the file in a backward compatible 107 // way. 108 int final_offset_offset = (int) f.getFilePointer(); 109 f.writeInt(0); 110 int index_offset = (int) f.getFilePointer(); 111 // Write the index. 112 ArrayList<String> sortedOlsonIds = new ArrayList<String>(); 113 sortedOlsonIds.addAll(offsets.keySet()); 114 Collections.sort(sortedOlsonIds); 115 for (String zoneName : sortedOlsonIds) { 116 if (zoneName.length() >= MAXNAME) { 117 throw new RuntimeException("zone filename too long: " + zoneName.length()); 118 } 119 f.write(toAscii(new byte[MAXNAME], zoneName)); 120 f.writeInt(offsets.get(zoneName)); 121 f.writeInt(lengths.get(zoneName)); 122 } 123 int data_offset = (int) f.getFilePointer(); 124 // Write the data. 125 f.write(allData.toByteArray()); 126 int final_offset = (int) f.getFilePointer(); 127 // Go back and fix up the offsets in the header. 128 f.seek(index_offset_offset); 129 f.writeInt(index_offset); 130 f.seek(data_offset_offset); 131 f.writeInt(data_offset); 132 f.seek(final_offset_offset); 133 f.writeInt(final_offset); 134 f.close(); 135 } 136 toAscii(byte[] dst, String src)137 private static byte[] toAscii(byte[] dst, String src) { 138 for (int i = 0; i < src.length(); ++i) { 139 if (src.charAt(i) > '~') { 140 throw new RuntimeException("non-ASCII string: " + src); 141 } 142 dst[i] = (byte) src.charAt(i); 143 } 144 return dst; 145 } 146 main(String[] args)147 public static void main(String[] args) throws Exception { 148 String setupFilePath = "C:\\Users\\Administrator\\Desktop\\iana\\setup"; 149 String dataDir = "C:\\Users\\Administrator\\Desktop\\iana\\tzdata2025a\\posix"; 150 String outputDir = "C:\\Users\\Administrator\\Desktop\\iana\\tzdata2025a\\output"; 151 String tzdataVersion = "tzdata2025a"; 152 new ZoneCompactor(setupFilePath, dataDir, outputDir, tzdataVersion); 153 } 154 }