make/tools/src/build/tools/tzdb/TzdbZoneRulesCompiler.java

Print this page

        

*** 56,352 **** */ package build.tools.tzdb; import static build.tools.tzdb.Utils.*; - import java.io.BufferedReader; import java.io.ByteArrayOutputStream; import java.io.DataOutputStream; ! import java.io.File; ! import java.io.FileOutputStream; ! import java.io.FileReader; import java.text.ParsePosition; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; ! import java.util.Set; import java.util.SortedMap; - import java.util.StringTokenizer; import java.util.TreeMap; - import java.util.TreeSet; - import java.util.jar.JarOutputStream; - import java.util.zip.ZipEntry; import java.util.regex.Matcher; import java.util.regex.Pattern; /** ! * A builder that can read the TZDB time-zone files and build {@code ZoneRules} instances. * * @since 1.8 */ public final class TzdbZoneRulesCompiler { - private static final Matcher YEAR = Pattern.compile("(?i)(?<min>min)|(?<max>max)|(?<only>only)|(?<year>[0-9]+)").matcher(""); - private static final Matcher MONTH = Pattern.compile("(?i)(jan)|(feb)|(mar)|(apr)|(may)|(jun)|(jul)|(aug)|(sep)|(oct)|(nov)|(dec)").matcher(""); - private static final Matcher DOW = Pattern.compile("(?i)(mon)|(tue)|(wed)|(thu)|(fri)|(sat)|(sun)").matcher(""); - private static final Matcher TIME = Pattern.compile("(?<neg>-)?+(?<hour>[0-9]{1,2})(:(?<minute>[0-5][0-9]))?+(:(?<second>[0-5][0-9]))?+").matcher(""); - - /** - * Constant for MJD 1972-01-01. - */ - private static final long MJD_1972_01_01 = 41317L; - - /** - * Reads a set of TZDB files and builds a single combined data file. - * - * @param args the arguments - */ public static void main(String[] args) { if (args.length < 2) { outputHelp(); return; } ! ! // parse args String version = null; ! File baseSrcDir = null; ! File dstDir = null; ! boolean verbose = false; ! ! // parse options int i; for (i = 0; i < args.length; i++) { String arg = args[i]; ! if (arg.startsWith("-") == false) { break; } if ("-srcdir".equals(arg)) { ! if (baseSrcDir == null && ++i < args.length) { ! baseSrcDir = new File(args[i]); continue; } ! } else if ("-dstdir".equals(arg)) { ! if (dstDir == null && ++i < args.length) { ! dstDir = new File(args[i]); ! continue; ! } ! } else if ("-version".equals(arg)) { ! if (version == null && ++i < args.length) { ! version = args[i]; continue; } } else if ("-verbose".equals(arg)) { ! if (verbose == false) { verbose = true; continue; } ! } else if ("-help".equals(arg) == false) { System.out.println("Unrecognised option: " + arg); } outputHelp(); return; } - // check source directory ! if (baseSrcDir == null) { ! System.out.println("Source directory must be specified using -srcdir: " + baseSrcDir); ! return; } ! if (baseSrcDir.isDirectory() == false) { ! System.out.println("Source does not exist or is not a directory: " + baseSrcDir); ! return; } - dstDir = (dstDir != null ? dstDir : baseSrcDir); - // parse source file names ! List<String> srcFileNames = Arrays.asList(Arrays.copyOfRange(args, i, args.length)); ! if (srcFileNames.isEmpty()) { ! System.out.println("Source filenames not specified, using default set"); ! System.out.println("(africa antarctica asia australasia backward etcetera europe northamerica southamerica)"); ! srcFileNames = Arrays.asList("africa", "antarctica", "asia", "australasia", "backward", ! "etcetera", "europe", "northamerica", "southamerica"); ! } ! ! // find source directories to process ! List<File> srcDirs = new ArrayList<>(); ! if (version != null) { ! // if the "version" specified, as in jdk repo, the "baseSrcDir" is ! // the "srcDir" that contains the tzdb data. ! srcDirs.add(baseSrcDir); ! } else { ! File[] dirs = baseSrcDir.listFiles(); ! for (File dir : dirs) { ! if (dir.isDirectory() && dir.getName().matches("[12][0-9]{3}[A-Za-z0-9._-]+")) { ! srcDirs.add(dir); } } } - if (srcDirs.isEmpty()) { - System.out.println("Source directory contains no valid source folders: " + baseSrcDir); - return; } ! // check destination directory ! if (dstDir.exists() == false && dstDir.mkdirs() == false) { ! System.out.println("Destination directory could not be created: " + dstDir); ! return; } - if (dstDir.isDirectory() == false) { - System.out.println("Destination is not a directory: " + dstDir); - return; } ! process(srcDirs, srcFileNames, dstDir, version, verbose); System.exit(0); } /** * Output usage text for the command line. */ private static void outputHelp() { System.out.println("Usage: TzdbZoneRulesCompiler <options> <tzdb source filenames>"); System.out.println("where options include:"); ! System.out.println(" -srcdir <directory> Where to find source directories (required)"); ! System.out.println(" -dstdir <directory> Where to output generated files (default srcdir)"); ! System.out.println(" -version <version> Specify the version, such as 2009a (optional)"); System.out.println(" -help Print this usage message"); System.out.println(" -verbose Output verbose information during compilation"); ! System.out.println(" There must be one directory for each version in srcdir"); ! System.out.println(" Each directory must have the name of the version, such as 2009a"); ! System.out.println(" Each directory must contain the unpacked tzdb files, such as asia or europe"); ! System.out.println(" Directories must match the regex [12][0-9][0-9][0-9][A-Za-z0-9._-]+"); ! System.out.println(" There will be one jar file for each version and one combined jar in dstdir"); ! System.out.println(" If the version is specified, only that version is processed"); ! } ! ! /** ! * Process to create the jar files. ! */ ! private static void process(List<File> srcDirs, List<String> srcFileNames, File dstDir, String version, boolean verbose) { ! // build actual jar files ! Map<String, SortedMap<String, ZoneRules>> allBuiltZones = new TreeMap<>(); ! Set<String> allRegionIds = new TreeSet<String>(); ! Set<ZoneRules> allRules = new HashSet<ZoneRules>(); ! Map<String, Map<String, String>> allLinks = new TreeMap<>(); ! ! for (File srcDir : srcDirs) { ! // source files in this directory ! List<File> srcFiles = new ArrayList<>(); ! for (String srcFileName : srcFileNames) { ! File file = new File(srcDir, srcFileName); ! if (file.exists()) { ! srcFiles.add(file); ! } ! } ! if (srcFiles.isEmpty()) { ! continue; // nothing to process ! } ! ! // compile ! String loopVersion = (srcDirs.size() == 1 && version != null) ! ? version : srcDir.getName(); ! TzdbZoneRulesCompiler compiler = new TzdbZoneRulesCompiler(loopVersion, srcFiles, verbose); ! try { ! // compile ! compiler.compile(); ! SortedMap<String, ZoneRules> builtZones = compiler.getZones(); ! ! // output version-specific file ! File dstFile = version == null ? new File(dstDir, "tzdb" + loopVersion + ".jar") ! : new File(dstDir, "tzdb.jar"); ! if (verbose) { ! System.out.println("Outputting file: " + dstFile); ! } ! outputFile(dstFile, loopVersion, builtZones, compiler.links); ! ! // create totals ! allBuiltZones.put(loopVersion, builtZones); ! allRegionIds.addAll(builtZones.keySet()); ! allRules.addAll(builtZones.values()); ! allLinks.put(loopVersion, compiler.links); ! } catch (Exception ex) { ! System.out.println("Failed: " + ex.toString()); ! ex.printStackTrace(); ! System.exit(1); ! } ! } ! ! // output merged file ! if (version == null) { ! File dstFile = new File(dstDir, "tzdb-all.jar"); ! if (verbose) { ! System.out.println("Outputting combined file: " + dstFile); ! } ! outputFile(dstFile, allBuiltZones, allRegionIds, allRules, allLinks); ! } } /** * Outputs the file. */ ! private static void outputFile(File dstFile, ! String version, SortedMap<String, ZoneRules> builtZones, Map<String, String> links) { ! Map<String, SortedMap<String, ZoneRules>> loopAllBuiltZones = new TreeMap<>(); ! loopAllBuiltZones.put(version, builtZones); ! Set<String> loopAllRegionIds = new TreeSet<String>(builtZones.keySet()); ! Set<ZoneRules> loopAllRules = new HashSet<ZoneRules>(builtZones.values()); ! Map<String, Map<String, String>> loopAllLinks = new TreeMap<>(); ! loopAllLinks.put(version, links); ! outputFile(dstFile, loopAllBuiltZones, loopAllRegionIds, loopAllRules, loopAllLinks); ! } ! ! /** ! * Outputs the file. ! */ ! private static void outputFile(File dstFile, ! Map<String, SortedMap<String, ZoneRules>> allBuiltZones, ! Set<String> allRegionIds, ! Set<ZoneRules> allRules, ! Map<String, Map<String, String>> allLinks) { ! try (JarOutputStream jos = new JarOutputStream(new FileOutputStream(dstFile))) { ! outputTZEntry(jos, allBuiltZones, allRegionIds, allRules, allLinks); ! } catch (Exception ex) { ! System.out.println("Failed: " + ex.toString()); ! ex.printStackTrace(); ! System.exit(1); ! } ! } ! ! /** ! * Outputs the timezone entry in the JAR file. ! */ ! private static void outputTZEntry(JarOutputStream jos, ! Map<String, SortedMap<String, ZoneRules>> allBuiltZones, ! Set<String> allRegionIds, ! Set<ZoneRules> allRules, ! Map<String, Map<String, String>> allLinks) { ! // this format is not publicly specified ! try { ! jos.putNextEntry(new ZipEntry("TZDB.dat")); ! DataOutputStream out = new DataOutputStream(jos); ! // file version out.writeByte(1); // group out.writeUTF("TZDB"); // versions ! String[] versionArray = allBuiltZones.keySet().toArray(new String[allBuiltZones.size()]); ! out.writeShort(versionArray.length); ! for (String version : versionArray) { out.writeUTF(version); - } // regions ! String[] regionArray = allRegionIds.toArray(new String[allRegionIds.size()]); out.writeShort(regionArray.length); for (String regionId : regionArray) { out.writeUTF(regionId); } ! // rules ! List<ZoneRules> rulesList = new ArrayList<>(allRules); out.writeShort(rulesList.size()); ByteArrayOutputStream baos = new ByteArrayOutputStream(1024); for (ZoneRules rules : rulesList) { baos.reset(); DataOutputStream dataos = new DataOutputStream(baos); --- 56,241 ---- */ package build.tools.tzdb; import static build.tools.tzdb.Utils.*; import java.io.ByteArrayOutputStream; import java.io.DataOutputStream; ! import java.nio.charset.StandardCharsets; ! import java.nio.file.Files; ! import java.nio.file.Path; ! import java.nio.file.Paths; import java.text.ParsePosition; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; ! import java.util.NoSuchElementException; ! import java.util.Scanner; import java.util.SortedMap; import java.util.TreeMap; import java.util.regex.Matcher; + import java.util.regex.MatchResult; import java.util.regex.Pattern; /** ! * A compiler that reads a set of TZDB time-zone files and builds a single ! * combined TZDB data file. * * @since 1.8 */ public final class TzdbZoneRulesCompiler { public static void main(String[] args) { + new TzdbZoneRulesCompiler().compile(args); + } + + private void compile(String[] args) { if (args.length < 2) { outputHelp(); return; } ! Path srcDir = null; ! Path dstFile = null; String version = null; ! // parse args/options int i; for (i = 0; i < args.length; i++) { String arg = args[i]; ! if (!arg.startsWith("-")) { break; } if ("-srcdir".equals(arg)) { ! if (srcDir == null && ++i < args.length) { ! srcDir = Paths.get(args[i]); continue; } ! } else if ("-dstfile".equals(arg)) { ! if (dstFile == null && ++i < args.length) { ! dstFile = Paths.get(args[i]); continue; } } else if ("-verbose".equals(arg)) { ! if (!verbose) { verbose = true; continue; } ! } else if (!"-help".equals(arg)) { System.out.println("Unrecognised option: " + arg); } outputHelp(); return; } // check source directory ! if (srcDir == null) { ! System.err.println("Source directory must be specified using -srcdir"); ! System.exit(1); } ! if (!Files.isDirectory(srcDir)) { ! System.err.println("Source does not exist or is not a directory: " + srcDir); ! System.exit(1); } // parse source file names ! if (i == args.length) { ! i = 0; ! args = new String[] {"africa", "antarctica", "asia", "australasia", "europe", ! "northamerica","southamerica", "backward", "etcetera" }; ! System.out.println("Source filenames not specified, using default set ( "); ! for (String name : args) { ! System.out.printf(name + " "); } + System.out.println(")"); } + // source files in this directory + List<Path> srcFiles = new ArrayList<>(); + for (; i < args.length; i++) { + Path file = srcDir.resolve(args[i]); + if (Files.exists(file)) { + srcFiles.add(file); + } else { + System.err.println("Source directory does not contain source file: " + args[i]); + System.exit(1); } } ! // check destination file ! if (dstFile == null) { ! dstFile = srcDir.resolve("tzdb.dat"); ! } else { ! Path parent = dstFile.getParent(); ! if (parent != null && !Files.exists(parent)) { ! System.err.println("Destination directory does not exist: " + parent); ! System.exit(1); } } ! try { ! // get tzdb source version ! Matcher m = Pattern.compile("tzdata(?<ver>[0-9]{4}[A-z])") ! .matcher(new String(Files.readAllBytes(srcDir.resolve("VERSION")), ! "ISO-8859-1")); ! if (m.find()) { ! version = m.group("ver"); ! } else { ! System.exit(1); ! System.err.println("Source directory does not contain file: VERSION"); ! } ! printVerbose("Compiling TZDB version " + version); ! // parse source files ! for (Path file : srcFiles) { ! printVerbose("Parsing file: " + file); ! parseFile(file); ! } ! // build zone rules ! printVerbose("Building rules"); ! buildZoneRules(); ! // output to file ! printVerbose("Outputting tzdb file: " + dstFile); ! outputFile(dstFile, version, builtZones, links); ! } catch (Exception ex) { ! System.out.println("Failed: " + ex.toString()); ! ex.printStackTrace(); ! System.exit(1); ! } System.exit(0); } /** * Output usage text for the command line. */ private static void outputHelp() { System.out.println("Usage: TzdbZoneRulesCompiler <options> <tzdb source filenames>"); System.out.println("where options include:"); ! System.out.println(" -srcdir <directory> Where to find tzdb source directory (required)"); ! System.out.println(" -dstfile <file> Where to output generated file (default srcdir/tzdb.dat)"); System.out.println(" -help Print this usage message"); System.out.println(" -verbose Output verbose information during compilation"); ! System.out.println(" The source directory must contain the unpacked tzdb files, such as asia or europe"); } /** * Outputs the file. */ ! private void outputFile(Path dstFile, String version, SortedMap<String, ZoneRules> builtZones, Map<String, String> links) { ! try (DataOutputStream out = new DataOutputStream(Files.newOutputStream(dstFile))) { // file version out.writeByte(1); // group out.writeUTF("TZDB"); // versions ! out.writeShort(1); out.writeUTF(version); // regions ! String[] regionArray = builtZones.keySet().toArray(new String[builtZones.size()]); out.writeShort(regionArray.length); for (String regionId : regionArray) { out.writeUTF(regionId); } ! // rules -- hashset -> remove the dup ! List<ZoneRules> rulesList = new ArrayList<>(new HashSet<>(builtZones.values())); out.writeShort(rulesList.size()); ByteArrayOutputStream baos = new ByteArrayOutputStream(1024); for (ZoneRules rules : rulesList) { baos.reset(); DataOutputStream dataos = new DataOutputStream(baos);
*** 355,575 **** byte[] bytes = baos.toByteArray(); out.writeShort(bytes.length); out.write(bytes); } // link version-region-rules ! for (String version : allBuiltZones.keySet()) { ! out.writeShort(allBuiltZones.get(version).size()); ! for (Map.Entry<String, ZoneRules> entry : allBuiltZones.get(version).entrySet()) { int regionIndex = Arrays.binarySearch(regionArray, entry.getKey()); int rulesIndex = rulesList.indexOf(entry.getValue()); out.writeShort(regionIndex); out.writeShort(rulesIndex); } - } // alias-region ! for (String version : allLinks.keySet()) { ! out.writeShort(allLinks.get(version).size()); ! for (Map.Entry<String, String> entry : allLinks.get(version).entrySet()) { int aliasIndex = Arrays.binarySearch(regionArray, entry.getKey()); int regionIndex = Arrays.binarySearch(regionArray, entry.getValue()); out.writeShort(aliasIndex); out.writeShort(regionIndex); } - } out.flush(); - jos.closeEntry(); } catch (Exception ex) { System.out.println("Failed: " + ex.toString()); ex.printStackTrace(); System.exit(1); } } ! //----------------------------------------------------------------------- /** The TZDB rules. */ private final Map<String, List<TZDBRule>> rules = new HashMap<>(); /** The TZDB zones. */ private final Map<String, List<TZDBZone>> zones = new HashMap<>(); - /** The TZDB links. */ private final Map<String, String> links = new HashMap<>(); /** The built zones. */ private final SortedMap<String, ZoneRules> builtZones = new TreeMap<>(); ! ! /** The version to produce. */ ! private final String version; ! ! /** The source files. */ ! ! private final List<File> sourceFiles; ! ! /** The version to produce. */ ! private final boolean verbose; ! ! /** ! * Creates an instance if you want to invoke the compiler manually. ! * ! * @param version the version, such as 2009a, not null ! * @param sourceFiles the list of source files, not empty, not null ! * @param verbose whether to output verbose messages ! */ ! public TzdbZoneRulesCompiler(String version, List<File> sourceFiles, boolean verbose) { ! this.version = version; ! this.sourceFiles = sourceFiles; ! this.verbose = verbose; ! } /** ! * Compile the rules file. ! * <p> ! * Use {@link #getZones()} to retrieve the parsed data. ! * ! * @throws Exception if an error occurs */ ! public void compile() throws Exception { ! printVerbose("Compiling TZDB version " + version); ! parseFiles(); ! buildZoneRules(); ! printVerbose("Compiled TZDB version " + version); ! } ! ! /** ! * Gets the parsed zone rules. ! * ! * @return the parsed zone rules, not null ! */ ! public SortedMap<String, ZoneRules> getZones() { ! return builtZones; ! } ! ! /** ! * Parses the source files. ! * ! * @throws Exception if an error occurs ! */ ! private void parseFiles() throws Exception { ! for (File file : sourceFiles) { ! printVerbose("Parsing file: " + file); ! parseFile(file); ! } } /** * Parses a source file. * * @param file the file being read, not null * @throws Exception if an error occurs */ ! private void parseFile(File file) throws Exception { int lineNumber = 1; String line = null; - BufferedReader in = null; try { ! in = new BufferedReader(new FileReader(file)); List<TZDBZone> openZone = null; ! for ( ; (line = in.readLine()) != null; lineNumber++) { int index = line.indexOf('#'); // remove comments (doesn't handle # in quotes) if (index >= 0) { line = line.substring(0, index); } if (line.trim().length() == 0) { // ignore blank lines continue; } ! StringTokenizer st = new StringTokenizer(line, " \t"); ! if (openZone != null && Character.isWhitespace(line.charAt(0)) && st.hasMoreTokens()) { ! if (parseZoneLine(st, openZone)) { openZone = null; } } else { ! if (st.hasMoreTokens()) { ! String first = st.nextToken(); if (first.equals("Zone")) { - if (st.countTokens() < 3) { - printVerbose("Invalid Zone line in file: " + file + ", line: " + line); - throw new IllegalArgumentException("Invalid Zone line"); - } openZone = new ArrayList<>(); ! zones.put(st.nextToken(), openZone); ! if (parseZoneLine(st, openZone)) { openZone = null; } } else { openZone = null; if (first.equals("Rule")) { ! if (st.countTokens() < 9) { printVerbose("Invalid Rule line in file: " + file + ", line: " + line); throw new IllegalArgumentException("Invalid Rule line"); } - parseRuleLine(st); - } else if (first.equals("Link")) { ! if (st.countTokens() < 2) { printVerbose("Invalid Link line in file: " + file + ", line: " + line); throw new IllegalArgumentException("Invalid Link line"); } - String realId = st.nextToken(); - String aliasId = st.nextToken(); - links.put(aliasId, realId); } else { throw new IllegalArgumentException("Unknown line"); } } } } } } catch (Exception ex) { ! throw new Exception("Failed while processing file '" + file + "' on line " + lineNumber + " '" + line + "'", ex); ! } finally { ! try { ! if (in != null) { ! in.close(); ! } ! } catch (Exception ex) { ! // ignore NPE and IOE ! } } } /** * Parses a Rule line. * ! * @param st the tokenizer, not null */ ! private void parseRuleLine(StringTokenizer st) { TZDBRule rule = new TZDBRule(); ! String name = st.nextToken(); if (rules.containsKey(name) == false) { rules.put(name, new ArrayList<TZDBRule>()); } rules.get(name).add(rule); ! rule.startYear = parseYear(st.nextToken(), 0); ! rule.endYear = parseYear(st.nextToken(), rule.startYear); if (rule.startYear > rule.endYear) { throw new IllegalArgumentException("Year order invalid: " + rule.startYear + " > " + rule.endYear); } ! parseOptional(st.nextToken()); // type is unused ! parseMonthDayTime(st, rule); ! rule.savingsAmount = parsePeriod(st.nextToken()); ! rule.text = parseOptional(st.nextToken()); } /** * Parses a Zone line. * ! * @param st the tokenizer, not null * @return true if the zone is complete */ ! private boolean parseZoneLine(StringTokenizer st, List<TZDBZone> zoneList) { TZDBZone zone = new TZDBZone(); zoneList.add(zone); ! zone.standardOffset = parseOffset(st.nextToken()); ! String savingsRule = parseOptional(st.nextToken()); if (savingsRule == null) { zone.fixedSavingsSecs = 0; zone.savingsRule = null; } else { try { --- 244,407 ---- byte[] bytes = baos.toByteArray(); out.writeShort(bytes.length); out.write(bytes); } // link version-region-rules ! out.writeShort(builtZones.size()); ! for (Map.Entry<String, ZoneRules> entry : builtZones.entrySet()) { int regionIndex = Arrays.binarySearch(regionArray, entry.getKey()); int rulesIndex = rulesList.indexOf(entry.getValue()); out.writeShort(regionIndex); out.writeShort(rulesIndex); } // alias-region ! out.writeShort(links.size()); ! for (Map.Entry<String, String> entry : links.entrySet()) { int aliasIndex = Arrays.binarySearch(regionArray, entry.getKey()); int regionIndex = Arrays.binarySearch(regionArray, entry.getValue()); out.writeShort(aliasIndex); out.writeShort(regionIndex); } out.flush(); } catch (Exception ex) { System.out.println("Failed: " + ex.toString()); ex.printStackTrace(); System.exit(1); } } ! private static final Pattern YEAR = Pattern.compile("(?i)(?<min>min)|(?<max>max)|(?<only>only)|(?<year>[0-9]+)"); ! private static final Pattern MONTH = Pattern.compile("(?i)(jan)|(feb)|(mar)|(apr)|(may)|(jun)|(jul)|(aug)|(sep)|(oct)|(nov)|(dec)"); ! private static final Matcher DOW = Pattern.compile("(?i)(mon)|(tue)|(wed)|(thu)|(fri)|(sat)|(sun)").matcher(""); ! private static final Matcher TIME = Pattern.compile("(?<neg>-)?+(?<hour>[0-9]{1,2})(:(?<minute>[0-5][0-9]))?+(:(?<second>[0-5][0-9]))?+").matcher(""); ! /** The TZDB rules. */ private final Map<String, List<TZDBRule>> rules = new HashMap<>(); /** The TZDB zones. */ private final Map<String, List<TZDBZone>> zones = new HashMap<>(); + /** The TZDB links. */ private final Map<String, String> links = new HashMap<>(); /** The built zones. */ private final SortedMap<String, ZoneRules> builtZones = new TreeMap<>(); ! /** Whether to output verbose messages. */ ! private boolean verbose; /** ! * private contructor */ ! private TzdbZoneRulesCompiler() { } /** * Parses a source file. * * @param file the file being read, not null * @throws Exception if an error occurs */ ! private void parseFile(Path file) throws Exception { int lineNumber = 1; String line = null; try { ! List<String> lines = Files.readAllLines(file, StandardCharsets.ISO_8859_1); List<TZDBZone> openZone = null; ! for (; lineNumber < lines.size(); lineNumber++) { ! line = lines.get(lineNumber); int index = line.indexOf('#'); // remove comments (doesn't handle # in quotes) if (index >= 0) { line = line.substring(0, index); } if (line.trim().length() == 0) { // ignore blank lines continue; } ! Scanner s = new Scanner(line); ! if (openZone != null && Character.isWhitespace(line.charAt(0)) && s.hasNext()) { ! if (parseZoneLine(s, openZone)) { openZone = null; } } else { ! if (s.hasNext()) { ! String first = s.next(); if (first.equals("Zone")) { openZone = new ArrayList<>(); ! try { ! zones.put(s.next(), openZone); ! if (parseZoneLine(s, openZone)) { openZone = null; } + } catch (NoSuchElementException x) { + printVerbose("Invalid Zone line in file: " + file + ", line: " + line); + throw new IllegalArgumentException("Invalid Zone line"); + } } else { openZone = null; if (first.equals("Rule")) { ! try { ! parseRuleLine(s); ! } catch (NoSuchElementException x) { printVerbose("Invalid Rule line in file: " + file + ", line: " + line); throw new IllegalArgumentException("Invalid Rule line"); } } else if (first.equals("Link")) { ! try { ! String realId = s.next(); ! String aliasId = s.next(); ! links.put(aliasId, realId); ! } catch (NoSuchElementException x) { printVerbose("Invalid Link line in file: " + file + ", line: " + line); throw new IllegalArgumentException("Invalid Link line"); } } else { throw new IllegalArgumentException("Unknown line"); } } } } } } catch (Exception ex) { ! throw new Exception("Failed while parsing file '" + file + "' on line " + lineNumber + " '" + line + "'", ex); } } /** * Parses a Rule line. * ! * @param s the line scanner, not null */ ! private void parseRuleLine(Scanner s) { TZDBRule rule = new TZDBRule(); ! String name = s.next(); if (rules.containsKey(name) == false) { rules.put(name, new ArrayList<TZDBRule>()); } rules.get(name).add(rule); ! rule.startYear = parseYear(s, 0); ! rule.endYear = parseYear(s, rule.startYear); if (rule.startYear > rule.endYear) { throw new IllegalArgumentException("Year order invalid: " + rule.startYear + " > " + rule.endYear); } ! parseOptional(s.next()); // type is unused ! parseMonthDayTime(s, rule); ! rule.savingsAmount = parsePeriod(s.next()); ! rule.text = parseOptional(s.next()); } /** * Parses a Zone line. * ! * @param s the line scanner, not null * @return true if the zone is complete */ ! private boolean parseZoneLine(Scanner s, List<TZDBZone> zoneList) { TZDBZone zone = new TZDBZone(); zoneList.add(zone); ! zone.standardOffset = parseOffset(s.next()); ! String savingsRule = parseOptional(s.next()); if (savingsRule == null) { zone.fixedSavingsSecs = 0; zone.savingsRule = null; } else { try {
*** 578,609 **** } catch (Exception ex) { zone.fixedSavingsSecs = null; zone.savingsRule = savingsRule; } } ! zone.text = st.nextToken(); ! if (st.hasMoreTokens()) { ! zone.year = Integer.parseInt(st.nextToken()); ! if (st.hasMoreTokens()) { ! parseMonthDayTime(st, zone); } return false; } else { return true; } } /** * Parses a Rule line. * ! * @param st the tokenizer, not null * @param mdt the object to parse into, not null */ ! private void parseMonthDayTime(StringTokenizer st, TZDBMonthDayTime mdt) { ! mdt.month = parseMonth(st.nextToken()); ! if (st.hasMoreTokens()) { ! String dayRule = st.nextToken(); if (dayRule.startsWith("last")) { mdt.dayOfMonth = -1; mdt.dayOfWeek = parseDayOfWeek(dayRule.substring(4)); mdt.adjustForwards = false; } else { --- 410,441 ---- } catch (Exception ex) { zone.fixedSavingsSecs = null; zone.savingsRule = savingsRule; } } ! zone.text = s.next(); ! if (s.hasNext()) { ! zone.year = Integer.parseInt(s.next()); ! if (s.hasNext()) { ! parseMonthDayTime(s, zone); } return false; } else { return true; } } /** * Parses a Rule line. * ! * @param s the line scanner, not null * @param mdt the object to parse into, not null */ ! private void parseMonthDayTime(Scanner s, TZDBMonthDayTime mdt) { ! mdt.month = parseMonth(s); ! if (s.hasNext()) { ! String dayRule = s.next(); if (dayRule.startsWith("last")) { mdt.dayOfMonth = -1; mdt.dayOfWeek = parseDayOfWeek(dayRule.substring(4)); mdt.adjustForwards = false; } else {
*** 619,630 **** dayRule = dayRule.substring(index + 2); } } mdt.dayOfMonth = Integer.parseInt(dayRule); } ! if (st.hasMoreTokens()) { ! String timeStr = st.nextToken(); int secsOfDay = parseSecs(timeStr); if (secsOfDay == 86400) { mdt.endOfDay = true; secsOfDay = 0; } --- 451,462 ---- dayRule = dayRule.substring(index + 2); } } mdt.dayOfMonth = Integer.parseInt(dayRule); } ! if (s.hasNext()) { ! String timeStr = s.next(); int secsOfDay = parseSecs(timeStr); if (secsOfDay == 86400) { mdt.endOfDay = true; secsOfDay = 0; }
*** 633,666 **** mdt.timeDefinition = parseTimeDefinition(timeStr.charAt(timeStr.length() - 1)); } } } ! private int parseYear(String str, int defaultYear) { ! if (YEAR.reset(str).matches()) { ! if (YEAR.group("min") != null) { //return YEAR_MIN_VALUE; return 1900; // systemv has min ! } else if (YEAR.group("max") != null) { return YEAR_MAX_VALUE; ! } else if (YEAR.group("only") != null) { return defaultYear; } ! return Integer.parseInt(YEAR.group("year")); } ! throw new IllegalArgumentException("Unknown year: " + str); } ! private int parseMonth(String str) { ! if (MONTH.reset(str).matches()) { for (int moy = 1; moy < 13; moy++) { ! if (MONTH.group(moy) != null) { return moy; } } } ! throw new IllegalArgumentException("Unknown month: " + str); } private int parseDayOfWeek(String str) { if (DOW.reset(str).matches()) { for (int dow = 1; dow < 8; dow++) { --- 465,511 ---- mdt.timeDefinition = parseTimeDefinition(timeStr.charAt(timeStr.length() - 1)); } } } ! private int parseYear(Scanner s, int defaultYear) { ! if (s.hasNext(YEAR)) { ! s.next(YEAR); ! MatchResult mr = s.match(); ! if (mr.group(1) != null) { ! return 1900; // systemv has min ! } else if (mr.group(2) != null) { ! return YEAR_MAX_VALUE; ! } else if (mr.group(3) != null) { ! return defaultYear; ! } ! return Integer.parseInt(mr.group(4)); ! /* ! if (mr.group("min") != null) { //return YEAR_MIN_VALUE; return 1900; // systemv has min ! } else if (mr.group("max") != null) { return YEAR_MAX_VALUE; ! } else if (mr.group("only") != null) { return defaultYear; } ! return Integer.parseInt(mr.group("year")); ! */ } ! throw new IllegalArgumentException("Unknown year: " + s.next()); } ! private int parseMonth(Scanner s) { ! if (s.hasNext(MONTH)) { ! s.next(MONTH); for (int moy = 1; moy < 13; moy++) { ! if (s.match().group(moy) != null) { return moy; } } } ! throw new IllegalArgumentException("Unknown month: " + s.next()); } private int parseDayOfWeek(String str) { if (DOW.reset(str).matches()) { for (int dow = 1; dow < 8; dow++) {
*** 727,737 **** // wall time return TimeDefinition.WALL; } } - //----------------------------------------------------------------------- /** * Build the rules, zones and links into real zones. * * @throws Exception if an error occurs */ --- 572,581 ----
*** 742,753 **** List<TZDBZone> tzdbZones = zones.get(zoneId); ZoneRulesBuilder bld = new ZoneRulesBuilder(); for (TZDBZone tzdbZone : tzdbZones) { bld = tzdbZone.addToBuilder(bld, rules); } ! ZoneRules buildRules = bld.toRules(zoneId); ! builtZones.put(zoneId, buildRules); } // build aliases for (String aliasId : links.keySet()) { String realId = links.get(aliasId); --- 586,596 ---- List<TZDBZone> tzdbZones = zones.get(zoneId); ZoneRulesBuilder bld = new ZoneRulesBuilder(); for (TZDBZone tzdbZone : tzdbZones) { bld = tzdbZone.addToBuilder(bld, rules); } ! builtZones.put(zoneId, bld.toRules(zoneId)); } // build aliases for (String aliasId : links.keySet()) { String realId = links.get(aliasId);
*** 756,784 **** if (realRules == null) { realId = links.get(realId); // try again (handle alias liked to alias) printVerbose("Relinking alias " + aliasId + " to " + realId); realRules = builtZones.get(realId); if (realRules == null) { ! throw new IllegalArgumentException("Alias '" + aliasId + "' links to invalid zone '" + realId + "' for '" + version + "'"); } links.put(aliasId, realId); - } builtZones.put(aliasId, realRules); } - // remove UTC and GMT ! //builtZones.remove("UTC"); ! //builtZones.remove("GMT"); ! //builtZones.remove("GMT0"); builtZones.remove("GMT+0"); builtZones.remove("GMT-0"); links.remove("GMT+0"); links.remove("GMT-0"); } - //----------------------------------------------------------------------- /** * Prints a verbose message. * * @param message the message, not null */ --- 599,627 ---- if (realRules == null) { realId = links.get(realId); // try again (handle alias liked to alias) printVerbose("Relinking alias " + aliasId + " to " + realId); realRules = builtZones.get(realId); if (realRules == null) { ! throw new IllegalArgumentException("Alias '" + aliasId + "' links to invalid zone '" + realId); } links.put(aliasId, realId); } builtZones.put(aliasId, realRules); } // remove UTC and GMT ! // builtZones.remove("UTC"); ! // builtZones.remove("GMT"); ! // builtZones.remove("GMT0"); builtZones.remove("GMT+0"); builtZones.remove("GMT-0"); links.remove("GMT+0"); links.remove("GMT-0"); + // remove ROC, which is not supported in j.u.tz + builtZones.remove("ROC"); + links.remove("ROC"); } /** * Prints a verbose message. * * @param message the message, not null */
*** 786,796 **** if (verbose) { System.out.println(message); } } - //----------------------------------------------------------------------- /** * Class representing a month-day-time in the TZDB file. */ abstract class TZDBMonthDayTime { /** The month of the cutover. */ --- 629,638 ----
*** 891,897 **** ldt = ldt.plusDays(1); } return ldt; } } - } --- 733,738 ----