• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 }