make/src/classes/build/tools/tzdb/TzdbZoneRulesCompiler.java
Print this page
*** 54,65 ****
* 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;
--- 54,63 ----
*** 177,195 ****
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());
--- 175,219 ----
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);
! TzdbZoneRulesProvider provider = new TzdbZoneRulesProvider(srcFiles);
!
// build zone rules
printVerbose("Building rules");
!
! // 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,631 ****
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");
! }
/**
* Prints a verbose message.
*
* @param message the message, not null
--- 291,307 ----
ex.printStackTrace();
System.exit(1);
}
}
/** Whether to output verbose messages. */
private boolean verbose;
/**
* private contructor
*/
! private TzdbZoneRulesCompiler() {}
/**
* Prints a verbose message.
*
* @param message the message, not null
*** 633,743 ****
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;
- }
- }
}
--- 309,314 ----