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

Print this page

        

@@ -54,12 +54,10 @@
  * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
 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;

@@ -177,19 +175,45 @@
                 version = m.group("ver");
             } else {
                 System.exit(1);
                 System.err.println("Source directory does not contain file: VERSION");
             }
+
+            // load source files
             printVerbose("Compiling TZDB version " + version);
-            // parse source files
-            for (Path file : srcFiles) {
-                printVerbose("Parsing file: " + file);
-                parseFile(file);
-            }
+            TzdbZoneRulesProvider provider = new TzdbZoneRulesProvider(srcFiles);
+
             // build zone rules
             printVerbose("Building rules");
-            buildZoneRules();
+
+            // Build the rules, zones and links into real zones.
+            SortedMap<String, ZoneRules> builtZones = new TreeMap<>();
+    
+            // build zones
+            for (String zoneId : provider.getZoneIds()) {
+                printVerbose("Building zone " + zoneId);
+                builtZones.put(zoneId, provider.getZoneRules(zoneId));
+            }
+
+            // build aliases
+            Map<String, String> links = provider.getAliasMap();
+            for (String aliasId : links.keySet()) {
+                String realId = links.get(aliasId);
+                printVerbose("Linking alias " + aliasId + " to " + realId);
+                ZoneRules realRules = builtZones.get(realId);
+                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);
+            }
+
             // output to file
             printVerbose("Outputting tzdb file: " + dstFile);
             outputFile(dstFile, version, builtZones, links);
         } catch (Exception ex) {
             System.out.println("Failed: " + ex.toString());

@@ -267,365 +291,17 @@
             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 {
-                zone.fixedSavingsSecs = parsePeriod(savingsRule);
-                zone.savingsRule = null;
-            } 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 {
-                int index = dayRule.indexOf(">=");
-                if (index > 0) {
-                    mdt.dayOfWeek = parseDayOfWeek(dayRule.substring(0, index));
-                    dayRule = dayRule.substring(index + 2);
-                } else {
-                    index = dayRule.indexOf("<=");
-                    if (index > 0) {
-                        mdt.dayOfWeek = parseDayOfWeek(dayRule.substring(0, index));
-                        mdt.adjustForwards = false;
-                        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;
-                }
-                LocalTime time = LocalTime.ofSecondOfDay(secsOfDay);
-                mdt.time = time;
-                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++) {
-                if (DOW.group(dow) != null) {
-                    return dow;
-                }
-            }
-        }
-        throw new IllegalArgumentException("Unknown day-of-week: " + str);
-    }
-
-    private String parseOptional(String str) {
-        return str.equals("-") ? null : str;
-    }
-
-    private int parseSecs(String str) {
-        if (str.equals("-")) {
-            return 0;
-        }
-        try {
-            if (TIME.reset(str).find()) {
-                int secs = Integer.parseInt(TIME.group("hour")) * 60 * 60;
-                if (TIME.group("minute") != null) {
-                    secs += Integer.parseInt(TIME.group("minute")) * 60;
-                }
-                if (TIME.group("second") != null) {
-                    secs += Integer.parseInt(TIME.group("second"));
-                }
-                if (TIME.group("neg") != null) {
-                    secs = -secs;
-                }
-                return secs;
-            }
-        } catch (NumberFormatException x) {}
-        throw new IllegalArgumentException(str);
-    }
-
-    private ZoneOffset parseOffset(String str) {
-        int secs = parseSecs(str);
-        return ZoneOffset.ofTotalSeconds(secs);
-    }
-
-    private int parsePeriod(String str) {
-        return parseSecs(str);
-    }
-
-    private TimeDefinition parseTimeDefinition(char c) {
-        switch (c) {
-            case 's':
-            case 'S':
-                // standard time
-                return TimeDefinition.STANDARD;
-            case 'u':
-            case 'U':
-            case 'g':
-            case 'G':
-            case 'z':
-            case 'Z':
-                // UTC
-                return TimeDefinition.UTC;
-            case 'w':
-            case 'W':
-            default:
-                // wall time
-                return TimeDefinition.WALL;
-        }
-    }
-
-    /**
-     * Build the rules, zones and links into real zones.
-     *
-     * @throws Exception if an error occurs
-     */
-    private void buildZoneRules() throws Exception {
-        // build zones
-        for (String zoneId : zones.keySet()) {
-            printVerbose("Building zone " + zoneId);
-            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);
-            printVerbose("Linking alias " + aliasId + " to " + realId);
-            ZoneRules realRules = builtZones.get(realId);
-            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");
-        // remove EST, HST and MST. They are supported via
-        // the short-id mapping
-        builtZones.remove("EST");
-        builtZones.remove("HST");
-        builtZones.remove("MST");
-    }
+    private TzdbZoneRulesCompiler() {}
 
     /**
      * Prints a verbose message.
      *
      * @param message  the message, not null

@@ -633,111 +309,6 @@
     private void printVerbose(String message) {
         if (verbose) {
             System.out.println(message);
         }
     }
-
-    /**
-     * Class representing a month-day-time in the TZDB file.
-     */
-    abstract class TZDBMonthDayTime {
-        /** The month of the cutover. */
-        int month = 1;
-        /** The day-of-month of the cutover. */
-        int dayOfMonth = 1;
-        /** Whether to adjust forwards. */
-        boolean adjustForwards = true;
-        /** The day-of-week of the cutover. */
-        int dayOfWeek = -1;
-        /** The time of the cutover. */
-        LocalTime time = LocalTime.MIDNIGHT;
-        /** Whether this is midnight end of day. */
-        boolean endOfDay;
-        /** The time of the cutover. */
-        TimeDefinition timeDefinition = TimeDefinition.WALL;
-        void adjustToFowards(int year) {
-            if (adjustForwards == false && dayOfMonth > 0) {
-                LocalDate adjustedDate = LocalDate.of(year, month, dayOfMonth).minusDays(6);
-                dayOfMonth = adjustedDate.getDayOfMonth();
-                month = adjustedDate.getMonth();
-                adjustForwards = true;
-            }
-        }
-    }
-
-    /**
-     * Class representing a rule line in the TZDB file.
-     */
-    final class TZDBRule extends TZDBMonthDayTime {
-        /** The start year. */
-        int startYear;
-        /** The end year. */
-        int endYear;
-        /** The amount of savings. */
-        int savingsAmount;
-        /** The text name of the zone. */
-        String text;
-
-        void addToBuilder(ZoneRulesBuilder bld) {
-            adjustToFowards(2004);  // irrelevant, treat as leap year
-            bld.addRuleToWindow(startYear, endYear, month, dayOfMonth, dayOfWeek, time, endOfDay, timeDefinition, savingsAmount);
-        }
-    }
-
-    /**
-     * Class representing a linked set of zone lines in the TZDB file.
-     */
-    final class TZDBZone extends TZDBMonthDayTime {
-        /** The standard offset. */
-        ZoneOffset standardOffset;
-        /** The fixed savings amount. */
-        Integer fixedSavingsSecs;
-        /** The savings rule. */
-        String savingsRule;
-        /** The text name of the zone. */
-        String text;
-        /** The year of the cutover. */
-        int year = YEAR_MAX_VALUE;
-
-        ZoneRulesBuilder addToBuilder(ZoneRulesBuilder bld, Map<String, List<TZDBRule>> rules) {
-            if (year != YEAR_MAX_VALUE) {
-                bld.addWindow(standardOffset, toDateTime(year), timeDefinition);
-            } else {
-                bld.addWindowForever(standardOffset);
-            }
-            if (fixedSavingsSecs != null) {
-                bld.setFixedSavingsToWindow(fixedSavingsSecs);
-            } else {
-                List<TZDBRule> tzdbRules = rules.get(savingsRule);
-                if (tzdbRules == null) {
-                    throw new IllegalArgumentException("Rule not found: " + savingsRule);
-                }
-                for (TZDBRule tzdbRule : tzdbRules) {
-                    tzdbRule.addToBuilder(bld);
-                }
-            }
-            return bld;
-        }
-
-        private LocalDateTime toDateTime(int year) {
-            adjustToFowards(year);
-            LocalDate date;
-            if (dayOfMonth == -1) {
-                dayOfMonth = lengthOfMonth(month, isLeapYear(year));
-                date = LocalDate.of(year, month, dayOfMonth);
-                if (dayOfWeek != -1) {
-                    date = previousOrSame(date, dayOfWeek);
-                }
-            } else {
-                date = LocalDate.of(year, month, dayOfMonth);
-                if (dayOfWeek != -1) {
-                    date = nextOrSame(date, dayOfWeek);
-                }
-            }
-            LocalDateTime ldt = LocalDateTime.of(date, time);
-            if (endOfDay) {
-                ldt = ldt.plusDays(1);
-            }
-            return ldt;
-        }
-    }
 }