210 System.out.println(" -dstdir <directory> Where to output generated files (default srcdir)");
211 System.out.println(" -version <version> Specify the version, such as 2009a (optional)");
212 System.out.println(" -help Print this usage message");
213 System.out.println(" -verbose Output verbose information during compilation");
214 System.out.println(" There must be one directory for each version in srcdir");
215 System.out.println(" Each directory must have the name of the version, such as 2009a");
216 System.out.println(" Each directory must contain the unpacked tzdb files, such as asia or europe");
217 System.out.println(" Directories must match the regex [12][0-9][0-9][0-9][A-Za-z0-9._-]+");
218 System.out.println(" There will be one jar file for each version and one combined jar in dstdir");
219 System.out.println(" If the version is specified, only that version is processed");
220 }
221
222 /**
223 * Process to create the jar files.
224 */
225 private static void process(List<File> srcDirs, List<String> srcFileNames, File dstDir, String version, boolean verbose) {
226 // build actual jar files
227 Map<String, SortedMap<String, ZoneRules>> allBuiltZones = new TreeMap<>();
228 Set<String> allRegionIds = new TreeSet<String>();
229 Set<ZoneRules> allRules = new HashSet<ZoneRules>();
230
231 for (File srcDir : srcDirs) {
232 // source files in this directory
233 List<File> srcFiles = new ArrayList<>();
234 for (String srcFileName : srcFileNames) {
235 File file = new File(srcDir, srcFileName);
236 if (file.exists()) {
237 srcFiles.add(file);
238 }
239 }
240 if (srcFiles.isEmpty()) {
241 continue; // nothing to process
242 }
243
244 // compile
245 String loopVersion = srcDir.getName();
246 TzdbZoneRulesCompiler compiler = new TzdbZoneRulesCompiler(loopVersion, srcFiles, verbose);
247 try {
248 // compile
249 compiler.compile();
250 SortedMap<String, ZoneRules> builtZones = compiler.getZones();
251
252 // output version-specific file
253 File dstFile = version == null ? new File(dstDir, "tzdb" + loopVersion + ".jar")
254 : new File(dstDir, "tzdb.jar");
255 if (verbose) {
256 System.out.println("Outputting file: " + dstFile);
257 }
258 outputFile(dstFile, loopVersion, builtZones);
259
260 // create totals
261 allBuiltZones.put(loopVersion, builtZones);
262 allRegionIds.addAll(builtZones.keySet());
263 allRules.addAll(builtZones.values());
264 } catch (Exception ex) {
265 System.out.println("Failed: " + ex.toString());
266 ex.printStackTrace();
267 System.exit(1);
268 }
269 }
270
271 // output merged file
272 if (version == null) {
273 File dstFile = new File(dstDir, "tzdb-all.jar");
274 if (verbose) {
275 System.out.println("Outputting combined file: " + dstFile);
276 }
277 outputFile(dstFile, allBuiltZones, allRegionIds, allRules);
278 }
279 }
280
281 /**
282 * Outputs the file.
283 */
284 private static void outputFile(File dstFile,
285 String version,
286 SortedMap<String, ZoneRules> builtZones) {
287 Map<String, SortedMap<String, ZoneRules>> loopAllBuiltZones = new TreeMap<>();
288 loopAllBuiltZones.put(version, builtZones);
289 Set<String> loopAllRegionIds = new TreeSet<String>(builtZones.keySet());
290 Set<ZoneRules> loopAllRules = new HashSet<ZoneRules>(builtZones.values());
291 outputFile(dstFile, loopAllBuiltZones, loopAllRegionIds, loopAllRules);
292 }
293
294 /**
295 * Outputs the file.
296 */
297 private static void outputFile(File dstFile,
298 Map<String, SortedMap<String, ZoneRules>> allBuiltZones,
299 Set<String> allRegionIds,
300 Set<ZoneRules> allRules)
301 {
302 try (JarOutputStream jos = new JarOutputStream(new FileOutputStream(dstFile))) {
303 outputTZEntry(jos, allBuiltZones, allRegionIds, allRules);
304 } catch (Exception ex) {
305 System.out.println("Failed: " + ex.toString());
306 ex.printStackTrace();
307 System.exit(1);
308 }
309 }
310
311 /**
312 * Outputs the timezone entry in the JAR file.
313 */
314 private static void outputTZEntry(JarOutputStream jos,
315 Map<String, SortedMap<String, ZoneRules>> allBuiltZones,
316 Set<String> allRegionIds,
317 Set<ZoneRules> allRules) {
318 // this format is not publicly specified
319 try {
320 jos.putNextEntry(new ZipEntry("TZDB.dat"));
321 DataOutputStream out = new DataOutputStream(jos);
322
323 // file version
324 out.writeByte(1);
325 // group
326 out.writeUTF("TZDB");
327 // versions
328 String[] versionArray = allBuiltZones.keySet().toArray(new String[allBuiltZones.size()]);
329 out.writeShort(versionArray.length);
330 for (String version : versionArray) {
331 out.writeUTF(version);
332 }
333 // regions
334 String[] regionArray = allRegionIds.toArray(new String[allRegionIds.size()]);
335 out.writeShort(regionArray.length);
336 for (String regionId : regionArray) {
337 out.writeUTF(regionId);
342 ByteArrayOutputStream baos = new ByteArrayOutputStream(1024);
343 for (ZoneRules rules : rulesList) {
344 baos.reset();
345 DataOutputStream dataos = new DataOutputStream(baos);
346 rules.writeExternal(dataos);
347 dataos.close();
348 byte[] bytes = baos.toByteArray();
349 out.writeShort(bytes.length);
350 out.write(bytes);
351 }
352 // link version-region-rules
353 for (String version : allBuiltZones.keySet()) {
354 out.writeShort(allBuiltZones.get(version).size());
355 for (Map.Entry<String, ZoneRules> entry : allBuiltZones.get(version).entrySet()) {
356 int regionIndex = Arrays.binarySearch(regionArray, entry.getKey());
357 int rulesIndex = rulesList.indexOf(entry.getValue());
358 out.writeShort(regionIndex);
359 out.writeShort(rulesIndex);
360 }
361 }
362 out.flush();
363 jos.closeEntry();
364 } catch (Exception ex) {
365 System.out.println("Failed: " + ex.toString());
366 ex.printStackTrace();
367 System.exit(1);
368 }
369 }
370
371 //-----------------------------------------------------------------------
372 /** The TZDB rules. */
373 private final Map<String, List<TZDBRule>> rules = new HashMap<>();
374
375 /** The TZDB zones. */
376 private final Map<String, List<TZDBZone>> zones = new HashMap<>();
377 /** The TZDB links. */
378
379 private final Map<String, String> links = new HashMap<>();
380
381 /** The built zones. */
604 }
605 mdt.dayOfMonth = Integer.parseInt(dayRule);
606 }
607 if (st.hasMoreTokens()) {
608 String timeStr = st.nextToken();
609 int secsOfDay = parseSecs(timeStr);
610 if (secsOfDay == 86400) {
611 mdt.endOfDay = true;
612 secsOfDay = 0;
613 }
614 LocalTime time = LocalTime.ofSecondOfDay(secsOfDay);
615 mdt.time = time;
616 mdt.timeDefinition = parseTimeDefinition(timeStr.charAt(timeStr.length() - 1));
617 }
618 }
619 }
620
621 private int parseYear(String str, int defaultYear) {
622 if (YEAR.reset(str).matches()) {
623 if (YEAR.group("min") != null) {
624 return YEAR_MIN_VALUE;
625 } else if (YEAR.group("max") != null) {
626 return YEAR_MAX_VALUE;
627 } else if (YEAR.group("only") != null) {
628 return defaultYear;
629 }
630 return Integer.parseInt(YEAR.group("year"));
631 }
632 throw new IllegalArgumentException("Unknown year: " + str);
633 }
634
635 private int parseMonth(String str) {
636 if (MONTH.reset(str).matches()) {
637 for (int moy = 1; moy < 13; moy++) {
638 if (MONTH.group(moy) != null) {
639 return moy;
640 }
641 }
642 }
643 throw new IllegalArgumentException("Unknown month: " + str);
644 }
725 ZoneRulesBuilder bld = new ZoneRulesBuilder();
726 for (TZDBZone tzdbZone : tzdbZones) {
727 bld = tzdbZone.addToBuilder(bld, rules);
728 }
729 ZoneRules buildRules = bld.toRules(zoneId);
730 builtZones.put(zoneId, buildRules);
731 }
732
733 // build aliases
734 for (String aliasId : links.keySet()) {
735 String realId = links.get(aliasId);
736 printVerbose("Linking alias " + aliasId + " to " + realId);
737 ZoneRules realRules = builtZones.get(realId);
738 if (realRules == null) {
739 realId = links.get(realId); // try again (handle alias liked to alias)
740 printVerbose("Relinking alias " + aliasId + " to " + realId);
741 realRules = builtZones.get(realId);
742 if (realRules == null) {
743 throw new IllegalArgumentException("Alias '" + aliasId + "' links to invalid zone '" + realId + "' for '" + version + "'");
744 }
745 }
746 builtZones.put(aliasId, realRules);
747 }
748
749 // remove UTC and GMT
750 builtZones.remove("UTC");
751 builtZones.remove("GMT");
752 builtZones.remove("GMT0");
753 builtZones.remove("GMT+0");
754 builtZones.remove("GMT-0");
755 }
756
757 //-----------------------------------------------------------------------
758 /**
759 * Prints a verbose message.
760 *
761 * @param message the message, not null
762 */
763 private void printVerbose(String message) {
764 if (verbose) {
765 System.out.println(message);
766 }
767 }
768
769 //-----------------------------------------------------------------------
770 /**
771 * Class representing a month-day-time in the TZDB file.
772 */
773 abstract class TZDBMonthDayTime {
774 /** The month of the cutover. */
775 int month = 1;
776 /** The day-of-month of the cutover. */
777 int dayOfMonth = 1;
778 /** Whether to adjust forwards. */
779 boolean adjustForwards = true;
780 /** The day-of-week of the cutover. */
781 int dayOfWeek = -1;
782 /** The time of the cutover. */
783 LocalTime time = LocalTime.MIDNIGHT;
784 /** Whether this is midnight end of day. */
785 boolean endOfDay;
786 /** The time of the cutover. */
787 TimeDefinition timeDefinition = TimeDefinition.WALL;
788
789 void adjustToFowards(int year) {
790 if (adjustForwards == false && dayOfMonth > 0) {
791 LocalDate adjustedDate = LocalDate.of(year, month, dayOfMonth).minusDays(6);
792 dayOfMonth = adjustedDate.getDayOfMonth();
793 month = adjustedDate.getMonth();
794 adjustForwards = true;
795 }
796 }
797 }
798
799 /**
800 * Class representing a rule line in the TZDB file.
801 */
802 final class TZDBRule extends TZDBMonthDayTime {
803 /** The start year. */
804 int startYear;
805 /** The end year. */
806 int endYear;
807 /** The amount of savings. */
808 int savingsAmount;
|
210 System.out.println(" -dstdir <directory> Where to output generated files (default srcdir)");
211 System.out.println(" -version <version> Specify the version, such as 2009a (optional)");
212 System.out.println(" -help Print this usage message");
213 System.out.println(" -verbose Output verbose information during compilation");
214 System.out.println(" There must be one directory for each version in srcdir");
215 System.out.println(" Each directory must have the name of the version, such as 2009a");
216 System.out.println(" Each directory must contain the unpacked tzdb files, such as asia or europe");
217 System.out.println(" Directories must match the regex [12][0-9][0-9][0-9][A-Za-z0-9._-]+");
218 System.out.println(" There will be one jar file for each version and one combined jar in dstdir");
219 System.out.println(" If the version is specified, only that version is processed");
220 }
221
222 /**
223 * Process to create the jar files.
224 */
225 private static void process(List<File> srcDirs, List<String> srcFileNames, File dstDir, String version, boolean verbose) {
226 // build actual jar files
227 Map<String, SortedMap<String, ZoneRules>> allBuiltZones = new TreeMap<>();
228 Set<String> allRegionIds = new TreeSet<String>();
229 Set<ZoneRules> allRules = new HashSet<ZoneRules>();
230 Map<String, Map<String, String>> allLinks = new TreeMap<>();
231
232 for (File srcDir : srcDirs) {
233 // source files in this directory
234 List<File> srcFiles = new ArrayList<>();
235 for (String srcFileName : srcFileNames) {
236 File file = new File(srcDir, srcFileName);
237 if (file.exists()) {
238 srcFiles.add(file);
239 }
240 }
241 if (srcFiles.isEmpty()) {
242 continue; // nothing to process
243 }
244
245 // compile
246 String loopVersion = (srcDirs.size() == 1 && version != null)
247 ? version : srcDir.getName();
248 TzdbZoneRulesCompiler compiler = new TzdbZoneRulesCompiler(loopVersion, srcFiles, verbose);
249 try {
250 // compile
251 compiler.compile();
252 SortedMap<String, ZoneRules> builtZones = compiler.getZones();
253
254 // output version-specific file
255 File dstFile = version == null ? new File(dstDir, "tzdb" + loopVersion + ".jar")
256 : new File(dstDir, "tzdb.jar");
257 if (verbose) {
258 System.out.println("Outputting file: " + dstFile);
259 }
260 outputFile(dstFile, loopVersion, builtZones, compiler.links);
261
262 // create totals
263 allBuiltZones.put(loopVersion, builtZones);
264 allRegionIds.addAll(builtZones.keySet());
265 allRules.addAll(builtZones.values());
266 allLinks.put(loopVersion, compiler.links);
267 } catch (Exception ex) {
268 System.out.println("Failed: " + ex.toString());
269 ex.printStackTrace();
270 System.exit(1);
271 }
272 }
273
274 // output merged file
275 if (version == null) {
276 File dstFile = new File(dstDir, "tzdb-all.jar");
277 if (verbose) {
278 System.out.println("Outputting combined file: " + dstFile);
279 }
280 outputFile(dstFile, allBuiltZones, allRegionIds, allRules, allLinks);
281 }
282 }
283
284 /**
285 * Outputs the file.
286 */
287 private static void outputFile(File dstFile,
288 String version,
289 SortedMap<String, ZoneRules> builtZones,
290 Map<String, String> links) {
291 Map<String, SortedMap<String, ZoneRules>> loopAllBuiltZones = new TreeMap<>();
292 loopAllBuiltZones.put(version, builtZones);
293 Set<String> loopAllRegionIds = new TreeSet<String>(builtZones.keySet());
294 Set<ZoneRules> loopAllRules = new HashSet<ZoneRules>(builtZones.values());
295 Map<String, Map<String, String>> loopAllLinks = new TreeMap<>();
296 loopAllLinks.put(version, links);
297 outputFile(dstFile, loopAllBuiltZones, loopAllRegionIds, loopAllRules, loopAllLinks);
298 }
299
300 /**
301 * Outputs the file.
302 */
303 private static void outputFile(File dstFile,
304 Map<String, SortedMap<String, ZoneRules>> allBuiltZones,
305 Set<String> allRegionIds,
306 Set<ZoneRules> allRules,
307 Map<String, Map<String, String>> allLinks) {
308 try (JarOutputStream jos = new JarOutputStream(new FileOutputStream(dstFile))) {
309 outputTZEntry(jos, allBuiltZones, allRegionIds, allRules, allLinks);
310 } catch (Exception ex) {
311 System.out.println("Failed: " + ex.toString());
312 ex.printStackTrace();
313 System.exit(1);
314 }
315 }
316
317 /**
318 * Outputs the timezone entry in the JAR file.
319 */
320 private static void outputTZEntry(JarOutputStream jos,
321 Map<String, SortedMap<String, ZoneRules>> allBuiltZones,
322 Set<String> allRegionIds,
323 Set<ZoneRules> allRules,
324 Map<String, Map<String, String>> allLinks) {
325 // this format is not publicly specified
326 try {
327 jos.putNextEntry(new ZipEntry("TZDB.dat"));
328 DataOutputStream out = new DataOutputStream(jos);
329
330 // file version
331 out.writeByte(1);
332 // group
333 out.writeUTF("TZDB");
334 // versions
335 String[] versionArray = allBuiltZones.keySet().toArray(new String[allBuiltZones.size()]);
336 out.writeShort(versionArray.length);
337 for (String version : versionArray) {
338 out.writeUTF(version);
339 }
340 // regions
341 String[] regionArray = allRegionIds.toArray(new String[allRegionIds.size()]);
342 out.writeShort(regionArray.length);
343 for (String regionId : regionArray) {
344 out.writeUTF(regionId);
349 ByteArrayOutputStream baos = new ByteArrayOutputStream(1024);
350 for (ZoneRules rules : rulesList) {
351 baos.reset();
352 DataOutputStream dataos = new DataOutputStream(baos);
353 rules.writeExternal(dataos);
354 dataos.close();
355 byte[] bytes = baos.toByteArray();
356 out.writeShort(bytes.length);
357 out.write(bytes);
358 }
359 // link version-region-rules
360 for (String version : allBuiltZones.keySet()) {
361 out.writeShort(allBuiltZones.get(version).size());
362 for (Map.Entry<String, ZoneRules> entry : allBuiltZones.get(version).entrySet()) {
363 int regionIndex = Arrays.binarySearch(regionArray, entry.getKey());
364 int rulesIndex = rulesList.indexOf(entry.getValue());
365 out.writeShort(regionIndex);
366 out.writeShort(rulesIndex);
367 }
368 }
369 // alias-region
370 for (String version : allLinks.keySet()) {
371 out.writeShort(allLinks.get(version).size());
372 for (Map.Entry<String, String> entry : allLinks.get(version).entrySet()) {
373 int aliasIndex = Arrays.binarySearch(regionArray, entry.getKey());
374 int regionIndex = Arrays.binarySearch(regionArray, entry.getValue());
375 out.writeShort(aliasIndex);
376 out.writeShort(regionIndex);
377 }
378 }
379 out.flush();
380 jos.closeEntry();
381 } catch (Exception ex) {
382 System.out.println("Failed: " + ex.toString());
383 ex.printStackTrace();
384 System.exit(1);
385 }
386 }
387
388 //-----------------------------------------------------------------------
389 /** The TZDB rules. */
390 private final Map<String, List<TZDBRule>> rules = new HashMap<>();
391
392 /** The TZDB zones. */
393 private final Map<String, List<TZDBZone>> zones = new HashMap<>();
394 /** The TZDB links. */
395
396 private final Map<String, String> links = new HashMap<>();
397
398 /** The built zones. */
621 }
622 mdt.dayOfMonth = Integer.parseInt(dayRule);
623 }
624 if (st.hasMoreTokens()) {
625 String timeStr = st.nextToken();
626 int secsOfDay = parseSecs(timeStr);
627 if (secsOfDay == 86400) {
628 mdt.endOfDay = true;
629 secsOfDay = 0;
630 }
631 LocalTime time = LocalTime.ofSecondOfDay(secsOfDay);
632 mdt.time = time;
633 mdt.timeDefinition = parseTimeDefinition(timeStr.charAt(timeStr.length() - 1));
634 }
635 }
636 }
637
638 private int parseYear(String str, int defaultYear) {
639 if (YEAR.reset(str).matches()) {
640 if (YEAR.group("min") != null) {
641 //return YEAR_MIN_VALUE;
642 return 1900; // systemv has min
643 } else if (YEAR.group("max") != null) {
644 return YEAR_MAX_VALUE;
645 } else if (YEAR.group("only") != null) {
646 return defaultYear;
647 }
648 return Integer.parseInt(YEAR.group("year"));
649 }
650 throw new IllegalArgumentException("Unknown year: " + str);
651 }
652
653 private int parseMonth(String str) {
654 if (MONTH.reset(str).matches()) {
655 for (int moy = 1; moy < 13; moy++) {
656 if (MONTH.group(moy) != null) {
657 return moy;
658 }
659 }
660 }
661 throw new IllegalArgumentException("Unknown month: " + str);
662 }
743 ZoneRulesBuilder bld = new ZoneRulesBuilder();
744 for (TZDBZone tzdbZone : tzdbZones) {
745 bld = tzdbZone.addToBuilder(bld, rules);
746 }
747 ZoneRules buildRules = bld.toRules(zoneId);
748 builtZones.put(zoneId, buildRules);
749 }
750
751 // build aliases
752 for (String aliasId : links.keySet()) {
753 String realId = links.get(aliasId);
754 printVerbose("Linking alias " + aliasId + " to " + realId);
755 ZoneRules realRules = builtZones.get(realId);
756 if (realRules == null) {
757 realId = links.get(realId); // try again (handle alias liked to alias)
758 printVerbose("Relinking alias " + aliasId + " to " + realId);
759 realRules = builtZones.get(realId);
760 if (realRules == null) {
761 throw new IllegalArgumentException("Alias '" + aliasId + "' links to invalid zone '" + realId + "' for '" + version + "'");
762 }
763 links.put(aliasId, realId);
764
765 }
766 builtZones.put(aliasId, realRules);
767 }
768
769 // remove UTC and GMT
770 //builtZones.remove("UTC");
771 //builtZones.remove("GMT");
772 //builtZones.remove("GMT0");
773 builtZones.remove("GMT+0");
774 builtZones.remove("GMT-0");
775 links.remove("GMT+0");
776 links.remove("GMT-0");
777 }
778
779 //-----------------------------------------------------------------------
780 /**
781 * Prints a verbose message.
782 *
783 * @param message the message, not null
784 */
785 private void printVerbose(String message) {
786 if (verbose) {
787 System.out.println(message);
788 }
789 }
790
791 //-----------------------------------------------------------------------
792 /**
793 * Class representing a month-day-time in the TZDB file.
794 */
795 abstract class TZDBMonthDayTime {
796 /** The month of the cutover. */
797 int month = 1;
798 /** The day-of-month of the cutover. */
799 int dayOfMonth = 1;
800 /** Whether to adjust forwards. */
801 boolean adjustForwards = true;
802 /** The day-of-week of the cutover. */
803 int dayOfWeek = -1;
804 /** The time of the cutover. */
805 LocalTime time = LocalTime.MIDNIGHT;
806 /** Whether this is midnight end of day. */
807 boolean endOfDay;
808 /** The time of the cutover. */
809 TimeDefinition timeDefinition = TimeDefinition.WALL;
810 void adjustToFowards(int year) {
811 if (adjustForwards == false && dayOfMonth > 0) {
812 LocalDate adjustedDate = LocalDate.of(year, month, dayOfMonth).minusDays(6);
813 dayOfMonth = adjustedDate.getDayOfMonth();
814 month = adjustedDate.getMonth();
815 adjustForwards = true;
816 }
817 }
818 }
819
820 /**
821 * Class representing a rule line in the TZDB file.
822 */
823 final class TZDBRule extends TZDBMonthDayTime {
824 /** The start year. */
825 int startYear;
826 /** The end year. */
827 int endYear;
828 /** The amount of savings. */
829 int savingsAmount;
|