blob: e75e8aadeb30af0ba26195d603522c1624c475db [file] [log] [blame]
package org.unicode.cldr.util;
import java.io.BufferedReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import java.util.TreeSet;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import com.ibm.icu.util.ICUUncheckedIOException;
public class ZoneParser {
static final boolean DEBUG = false;
private String version;
private Map<String, String> zone_to_country;
private Map<String, Set<String>> country_to_zoneSet;
/**
* @return mapping from zone id to country. If a zone has no country, then XX
* is used.
*/
public Map<String, String> getZoneToCounty() {
if (zone_to_country == null)
make_zone_to_country();
return zone_to_country;
}
/**
* @return mapping from country to zoneid. If a zone has no country, then XX
* is used.
*/
public Map<String, Set<String>> getCountryToZoneSet() {
if (country_to_zoneSet == null)
make_zone_to_country();
return country_to_zoneSet;
}
/**
* @return map from tzids to a list: latitude, longitude, country, comment?. + =
* N or E
*/
public Map<String, List<String>> getZoneData() {
if (zoneData == null)
makeZoneData();
return zoneData;
}
public List<String> getDeprecatedZoneIDs() {
return Arrays.asList(FIX_DEPRECATED_ZONE_DATA);
}
/**
*
*/
private void make_zone_to_country() {
zone_to_country = new TreeMap<String, String>(TZIDComparator);
country_to_zoneSet = new TreeMap<String, Set<String>>();
// Map aliasMap = getAliasMap();
Map<String, List<String>> zoneData = getZoneData();
for (String zone : zoneData.keySet()) {
String country = (String) zoneData.get(zone).get(2);
zone_to_country.put(zone, country);
Set<String> s = country_to_zoneSet.get(country);
if (s == null)
country_to_zoneSet.put(country, s = new TreeSet<String>());
s.add(zone);
}
/*
* Set territories = getAvailableCodes("territory"); for (Iterator it =
* territories.iterator(); it.hasNext();) { String code = (String)
* it.next(); String[] zones = TimeZone.getAvailableIDs(code); for (int i =
* 0; i < zones.length; ++i) { if (aliasMap.get(zones[i]) != null) continue;
* zone_to_country.put(zones[i], code); } } String[] zones =
* TimeZone.getAvailableIDs(); for (int i = 0; i < zones.length; ++i) { if
* (aliasMap.get(zones[i]) != null) continue; if
* (zone_to_country.get(zones[i]) == null) { zone_to_country.put(zones[i],
* NO_COUNTRY); } } for (Iterator it = zone_to_country.keySet().iterator();
* it.hasNext();) { String tzid = (String) it.next(); String country =
* (String) zone_to_country.get(tzid); Set s = (Set)
* country_to_zoneSet.get(country); if (s == null)
* country_to_zoneSet.put(country, s = new TreeSet()); s.add(tzid); }
*/
// protect
zone_to_country = Collections.unmodifiableMap(zone_to_country);
country_to_zoneSet = CldrUtility.protectCollection(country_to_zoneSet);
}
/**
*
*
* private Map bogusZones = null;
*
* private Map getAliasMap() { if (bogusZones == null) { try { bogusZones =
* new TreeMap(); BufferedReader in =
* Utility.getUTF8Data"TimeZoneAliases.txt"); while (true) { String line =
* in.readLine(); if (line == null) break; line = line.trim(); int pos =
* line.indexOf('#'); if (pos >= 0) { skippedAliases.add(line); line =
* line.substring(0,pos).trim(); } if (line.length() == 0) continue; List
* pieces = Utility.splitList(line,';', true); bogusZones.put(pieces.get(0),
* pieces.get(1)); } in.close(); } catch (IOException e) { throw new
* IllegalArgumentException("Can't find timezone aliases"); } } return
* bogusZones; }
*/
Map<String, List<String>> zoneData;
Set<String> skippedAliases = new TreeSet<String>();
/*
* # This file contains a table with the following columns: # 1. ISO 3166
* 2-character country code. See the file `iso3166.tab'. # 2. Latitude and
* longitude of the zone's principal location # in ISO 6709
* sign-degrees-minutes-seconds format, # either +-DDMM+-DDDMM or
* +-DDMMSS+-DDDMMSS, # first latitude (+ is north), then longitude (+ is
* east). # 3. Zone name used in value of TZ environment variable. # 4.
* Comments; present if and only if the country has multiple rows. # # Columns
* are separated by a single tab.
*/
static int parseYear(String year, int defaultValue) {
if ("only".startsWith(year))
return defaultValue;
if ("minimum".startsWith(year))
return Integer.MIN_VALUE;
if ("maximum".startsWith(year))
return Integer.MAX_VALUE;
return Integer.parseInt(year);
}
public static class Time {
public int seconds;
public byte type;
static final byte WALL = 0, STANDARD = 1, UNIVERSAL = 2;
Time(String in) {
if (in.equals("-")) return; // zero/WALL is the default
char suffix = in.charAt(in.length() - 1);
switch (suffix) {
case 'w':
in = in.substring(0, in.length() - 1);
break;
case 's':
in = in.substring(0, in.length() - 1);
type = STANDARD;
break;
case 'u':
case 'g':
case 'z':
in = in.substring(0, in.length() - 1);
type = UNIVERSAL;
break;
}
seconds = parseSeconds(in, false);
}
public static int parseSeconds(String in, boolean allowNegative) {
boolean negative = false;
if (in.startsWith("-")) {
assert (allowNegative);
negative = true;
in = in.substring(1);
}
String[] pieces = in.split(":");
int multiplier = 3600;
int result = 0;
for (int i = 0; i < pieces.length; ++i) {
result += multiplier * Integer.parseInt(pieces[i]);
multiplier /= 60;
assert (multiplier >= 0);
}
if (negative) result = -result;
return result;
}
public String toString() {
return BoilerplateUtilities.toStringHelper(this);
}
}
static final String[] months = { "january", "february", "march", "april", "may", "june", "july", "august",
"september", "october", "november", "december" };
static final String[] weekdays = { "sunday", "monday", "tuesday", "wednesday", "thursday", "friday", "saturday" };
static int findStartsWith(String value, String[] array, boolean exact) {
value = value.toLowerCase(Locale.ENGLISH);
for (int i = 0; i < array.length; ++i) {
if (array[i].startsWith(value)) return i;
}
throw new IllegalArgumentException("Can't find " + value + " in " + Arrays.asList(months));
}
static Pattern dayPattern = PatternCache.get("([0-9]+)|(last)([a-z]+)|([a-z]+)([<=>]+)([0-9]+)");
static final String[] relations = { "<=", ">=" };
public static class Day implements Comparable<Object> {
public int number;
public byte relation;
public int weekDay;
static final byte NONE = 0, LEQ = 2, GEQ = 4;
Day(String value) {
value = value.toLowerCase();
Matcher matcher = dayPattern.matcher(value);
if (!matcher.matches()) {
throw new IllegalArgumentException();
}
if (matcher.group(1) != null) {
number = Integer.parseInt(matcher.group(1));
return;
}
if (matcher.group(2) != null) {
weekDay = findStartsWith(matcher.group(3), weekdays, false);
number = 31;
relation = LEQ;
return;
}
if (matcher.group(4) != null) {
weekDay = findStartsWith(matcher.group(4), weekdays, false);
relation = (byte) findStartsWith(matcher.group(5), relations, false);
number = Integer.parseInt(matcher.group(6));
return;
}
throw new IllegalArgumentException();
}
public String toString() {
return BoilerplateUtilities.toStringHelper(this);
}
public int compareTo(Object other) {
return toString().compareTo(other.toString());
}
}
/**
*
A rule line has the form
*
* Rule NAME FROM TO TYPE IN ON AT SAVE LETTER/S
*
* For example:
*
* Rule US 1967 1973 - Apr lastSun 2:00 1:00 D
*
* The fields that make up a rule line are:
*
* NAME Gives the (arbitrary) name of the set of rules this
* rule is part of.
*
* FROM Gives the first year in which the rule applies. Any
* integer year can be supplied; the Gregorian calendar
* is assumed. The word minimum (or an abbreviation)
* means the minimum year representable as an integer.
* The word maximum (or an abbreviation) means the
* maximum year representable as an integer. Rules can
* describe times that are not representable as time
* values, with the unrepresentable times ignored; this
* allows rules to be portable among hosts with
* differing time value types.
*
* TO Gives the final year in which the rule applies. In
* addition to minimum and maximum (as above), the word
* only (or an abbreviation) may be used to repeat the
* value of the FROM field.
*
* TYPE Gives the type of year in which the rule applies.
* If TYPE is - then the rule applies in all years
* between FROM and TO inclusive. If TYPE is something
* else, then zic executes the command
* yearistype year type
* to check the type of a year: an exit status of zero
* is taken to mean that the year is of the given type;
* an exit status of one is taken to mean that the year
* is not of the given type.
*
* IN Names the month in which the rule takes effect.
* Month names may be abbreviated.
*
* ON Gives the day on which the rule takes effect.
* Recognized forms include:
*
* 5 the fifth of the month
* lastSun the last Sunday in the month
* lastMon the last Monday in the month
* Sun>=8 first Sunday on or after the eighth
* Sun<=25 last Sunday on or before the 25th
*
* Names of days of the week may be abbreviated or
* spelled out in full. Note that there must be no
* spaces within the ON field.
*
* AT Gives the time of day at which the rule takes
* effect. Recognized forms include:
*
* 2 time in hours
* 2:00 time in hours and minutes
* 15:00 24-hour format time (for times after noon)
* 1:28:14 time in hours, minutes, and seconds
* - equivalent to 0
*
* where hour 0 is midnight at the start of the day,
* and hour 24 is midnight at the end of the day. Any
* of these forms may be followed by the letter w if
* the given time is local "wall clock" time, s if the
* given time is local "standard" time, or u (or g or
* z) if the given time is universal time; in the
* absence of an indicator, wall clock time is assumed.
*** cannot be negative
*
* SAVE Gives the amount of time to be added to local
* standard time when the rule is in effect. This
* field has the same format as the AT field (although,
* of course, the w and s suffixes are not used).
*** can be positive or negative
*
* LETTER/S
* Gives the "variable part" (for example, the "S" or
* "D" in "EST" or "EDT") of time zone abbreviations to
* be used when this rule is in effect. If this field
* is -, the variable part is null.
*
*
*
*/
public static class RuleLine {
public static Set<String> types = new TreeSet<String>();
public static Set<Day> days = new TreeSet<Day>();
static Set<Integer> saves = new TreeSet<Integer>();
RuleLine(List<String> l) {
fromYear = parseYear(l.get(0), 0);
toYear = parseYear(l.get(1), fromYear);
type = l.get(2);
if (type.equals("-")) type = null;
month = 1 + findStartsWith((String) l.get(3), months, false);
day = new Day(l.get(4));
time = new Time(l.get(5));
save = Time.parseSeconds(l.get(6), true);
letter = l.get(7);
if (letter.equals("-")) letter = null;
if (type != null) types.add(type);
days.add(day);
}
public String toString() {
return BoilerplateUtilities.toStringHelper(this);
}
public int fromYear;
public int toYear;
public String type;
public int month;
public Day day;
public Time time;
public int save;
public String letter;
public static final int FIELD_COUNT = 8; // excluding Rule, Name
}
/**
* A zone line has the form
*
* Zone NAME GMTOFF RULES/SAVE FORMAT [UNTIL]
*
* For example:
*
* Zone Australia/Adelaide 9:30 Aus CST 1971 Oct 31 2:00
*
* The fields that make up a zone line are:
*
* NAME The name of the time zone. This is the name used in
* creating the time conversion information file for the
* zone.
*
* GMTOFF
* The amount of time to add to UTC to get standard time
* in this zone. This field has the same format as the
* AT and SAVE fields of rule lines; begin the field with
* a minus sign if time must be subtracted from UTC.
*
* RULES/SAVE
* The name of the rule(s) that apply in the time zone
* or, alternately, an amount of time to add to local
* standard time. If this field is - then standard time
* always applies in the time zone.
*
* FORMAT
* The format for time zone abbreviations in this time
* zone. The pair of characters %s is used to show where
* the "variable part" of the time zone abbreviation
* goes. Alternately, a slash (/) separates standard and
* daylight abbreviations.
*
* UNTIL The time at which the UTC offset or the rule(s) change
* for a location. It is specified as a year, a month, a
* day, and a time of day. If this is specified, the
* time zone information is generated from the given UTC
* offset and rule change until the time specified. The
* month, day, and time of day have the same format as
* the IN, ON, and AT columns of a rule; trailing columns
* can be omitted, and default to the earliest possible
* value for the missing columns.
*
* The next line must be a "continuation" line; this has
* the same form as a zone line except that the string
* "Zone" and the name are omitted, as the continuation
* line will place information starting at the time
* specified as the UNTIL field in the previous line in
* the file used by the previous line. Continuation
* lines may contain an UNTIL field, just as zone lines
* do, indicating that the next line is a further
* continuation.
*/
public static class ZoneLine {
public static Set<Day> untilDays = new TreeSet<Day>();
public static Set<String> rulesSaves = new TreeSet<String>();
ZoneLine(List<String> l) {
gmtOff = Time.parseSeconds(l.get(0), true);
rulesSave = (String) l.get(1);
if (rulesSave.equals("-"))
rulesSave = "0";
else if (rulesSave.charAt(0) < 'A') rulesSave = "" + Time.parseSeconds(rulesSave, false);
format = (String) l.get(2);
switch (l.size()) {
case 7:
untilTime = new Time(l.get(6)); // fall through
case 6:
untilDay = new Day(l.get(5)); // fall through
untilDays.add(untilDay);
case 5:
untilMonth = 1 + findStartsWith((String) l.get(4), months, false); // fall through
case 4:
untilYear = parseYear(l.get(3), Integer.MAX_VALUE); // fall through
case 3:
break; // ok
default:
throw new IllegalArgumentException("Wrong field count: " + l);
}
rulesSaves.add(rulesSave);
}
public String toString() {
return BoilerplateUtilities.toStringHelper(this);
}
public int gmtOff;
public String rulesSave;
public String format;
public int untilYear = Integer.MAX_VALUE; // indicating continuation
public int untilMonth;
public Day untilDay;
public Time untilTime;
public String comment;
public static final int FIELD_COUNT = 3; // excluding Zone, Name
public static final int FIELD_COUNT_UNTIL = 7; // excluding Zone, Name
}
Map<String, List<RuleLine>> ruleID_rules = new TreeMap<String, List<RuleLine>>();
Map<String, List<ZoneLine>> zone_rules = new TreeMap<String, List<ZoneLine>>();
Map<String, String> linkold_new = new TreeMap<String, String>();
Map<String, Set<String>> linkNew_oldSet = new TreeMap<String, Set<String>>();
public class Transition {
public long date;
public long offset;
public String abbreviation;
}
public class TransitionList {
void addTransitions(ZoneLine lastZoneLine, ZoneLine zoneLine, int startYear, int endYear) {
// add everything between the zonelines
if (lastZoneLine == null) {
return;
}
startYear = Math.max(startYear, lastZoneLine.untilYear);
endYear = Math.min(endYear, zoneLine.untilYear);
int gmtOffset = lastZoneLine.gmtOff;
for (int year = startYear; year <= endYear; ++year) {
resolveTime(gmtOffset, lastZoneLine.untilYear, lastZoneLine.untilMonth,
lastZoneLine.untilDay, lastZoneLine.untilTime);
}
}
private long resolveTime(int gmtOffset, int untilYear, int untilMonth, Day untilDay, Time untilTime) {
return 0;
}
}
public TransitionList getTransitions(String zoneID, int startYear, int endYear) {
TransitionList results = new TransitionList();
List<ZoneLine> rules = zone_rules.get(zoneID);
ZoneLine lastZoneLine = null;
for (ZoneLine zoneLine : rules) {
results.addTransitions(lastZoneLine, zoneLine, startYear, endYear);
lastZoneLine = zoneLine;
}
return results;
}
public Comparator<String> getTZIDComparator() {
return TZIDComparator;
}
private static List<String> errorData = Arrays.asList(new String[] {
new Double(Double.MIN_VALUE).toString(), new Double(Double.MIN_VALUE).toString(), "" });
private Comparator<String> TZIDComparator = new Comparator<String>() {
Map<String, List<String>> data = getZoneData();
public int compare(String s1, String s2) {
List<String> data1 = data.get(s1);
if (data1 == null)
data1 = errorData;
List<String> data2 = data.get(s2);
if (data2 == null)
data2 = errorData;
int result;
// country
String country1 = (String) data1.get(2);
String country2 = (String) data2.get(2);
if ((result = country1.compareTo(country2)) != 0)
return result;
// longitude
Double d1 = Double.valueOf(data1.get(1));
Double d2 = Double.valueOf(data2.get(1));
if ((result = d1.compareTo(d2)) != 0)
return result;
// latitude
d1 = Double.valueOf(data1.get(0));
d2 = Double.valueOf(data2.get(0));
if ((result = d1.compareTo(d2)) != 0)
return result;
// name
return s1.compareTo(s2);
}
};
public static MapComparator<String> regionalCompare = new MapComparator<String>();
static {
regionalCompare.add("America");
regionalCompare.add("Atlantic");
regionalCompare.add("Europe");
regionalCompare.add("Africa");
regionalCompare.add("Asia");
regionalCompare.add("Indian");
regionalCompare.add("Australia");
regionalCompare.add("Pacific");
regionalCompare.add("Arctic");
regionalCompare.add("Antarctica");
regionalCompare.add("Etc");
}
private static String[] TZFiles = { "africa", "antarctica", "asia",
"australasia", "backward", "etcetera", "europe", "northamerica",
"pacificnew", "southamerica", "systemv" };
private static Map<String, String> FIX_UNSTABLE_TZIDS;
private static Set<String> SKIP_LINKS = new HashSet<String>(Arrays.asList(
new String[] {
"America/Montreal", "America/Toronto",
"America/Santa_Isabel", "America/Tijuana" }));
private static Set<String> PREFERRED_BASES = new HashSet<String>(Arrays.asList(new String[] { "Europe/London" }));
private static String[][] ADD_ZONE_ALIASES_DATA = {
{ "Etc/UCT", "Etc/UTC" },
{ "EST", "Etc/GMT+5" },
{ "MST", "Etc/GMT+7" },
{ "HST", "Etc/GMT+10" },
{ "SystemV/AST4", "Etc/GMT+4" },
{ "SystemV/EST5", "Etc/GMT+5" },
{ "SystemV/CST6", "Etc/GMT+6" },
{ "SystemV/MST7", "Etc/GMT+7" },
{ "SystemV/PST8", "Etc/GMT+8" },
{ "SystemV/YST9", "Etc/GMT+9" },
{ "SystemV/HST10", "Etc/GMT+10" },
};
static String[] FIX_DEPRECATED_ZONE_DATA = {
"Africa/Timbuktu",
"America/Argentina/ComodRivadavia",
"America/Santa_Isabel",
"Europe/Belfast",
"Pacific/Yap",
"Antarctica/South_Pole",
"America/Shiprock",
"America/Montreal",
"Asia/Chongqing",
"Asia/Harbin",
"Asia/Kashgar"
};
static {
// The format is <new name>, <old name>
String[][] FIX_UNSTABLE_TZID_DATA = new String[][] {
{ "America/Atikokan", "America/Coral_Harbour" },
{ "America/Argentina/Buenos_Aires", "America/Buenos_Aires" },
{ "America/Argentina/Catamarca", "America/Catamarca" },
{ "America/Argentina/Cordoba", "America/Cordoba" },
{ "America/Argentina/Jujuy", "America/Jujuy" },
{ "America/Argentina/Mendoza", "America/Mendoza" },
{ "America/Kentucky/Louisville", "America/Louisville" },
{ "America/Indiana/Indianapolis", "America/Indianapolis" },
{ "Africa/Asmara", "Africa/Asmera" },
{ "Atlantic/Faroe", "Atlantic/Faeroe" },
{ "Asia/Kolkata", "Asia/Calcutta" },
{ "Asia/Ho_Chi_Minh", "Asia/Saigon" },
{ "Asia/Yangon", "Asia/Rangoon" },
{ "Asia/Kathmandu", "Asia/Katmandu" },
{ "Pacific/Pohnpei", "Pacific/Ponape" },
{ "Pacific/Chuuk", "Pacific/Truk" },
{ "Pacific/Honolulu", "Pacific/Johnston" }
};
FIX_UNSTABLE_TZIDS = CldrUtility.asMap(FIX_UNSTABLE_TZID_DATA);
}
/**
*
*/
private void makeZoneData() {
try {
// get version
BufferedReader versionIn = CldrUtility.getUTF8Data("tzdb-version.txt");
version = versionIn.readLine();
if (!version.matches("[0-9]{4}[a-z]")) {
throw new IllegalArgumentException(String.format("Bad Version number: %s, should be of the form 2007x",
version));
}
versionIn.close();
// String deg = "([+-][0-9]+)";//
String deg = "([+-])([0-9][0-9][0-9]?)([0-9][0-9])([0-9][0-9])?";//
Matcher m = PatternCache.get(deg + deg).matcher("");
zoneData = new TreeMap<String, List<String>>();
BufferedReader in = CldrUtility.getUTF8Data("zone.tab");
while (true) {
String line = in.readLine();
if (line == null)
break;
line = line.trim();
int pos = line.indexOf('#');
if (pos >= 0) {
skippedAliases.add(line);
line = line.substring(0, pos).trim();
}
if (line.length() == 0)
continue;
List<String> pieces = CldrUtility.splitList(line, '\t', true);
String country = pieces.get(0);
String latLong = pieces.get(1);
String tzid = pieces.get(2);
String ntzid = FIX_UNSTABLE_TZIDS.get(tzid);
if (ntzid != null)
tzid = ntzid;
String comment = pieces.size() < 4 ? null : (String) pieces.get(3);
pieces.clear();
if (!m.reset(latLong).matches())
throw new IllegalArgumentException("Bad zone.tab, lat/long format: "
+ line);
pieces.add(getDegrees(m, true).toString());
pieces.add(getDegrees(m, false).toString());
pieces.add(country);
if (comment != null)
pieces.add(comment);
if (zoneData.containsKey(tzid))
throw new IllegalArgumentException("Bad zone.tab, duplicate entry: "
+ line);
zoneData.put(tzid, pieces);
}
in.close();
// add Etcs
for (int i = -14; i <= 12; ++i) {
List<String> pieces = new ArrayList<String>();
int latitude = 0;
int longitude = i * 15;
if (longitude <= -180) {
longitude += 360;
}
pieces.add(new Double(latitude).toString()); // lat
// remember that the sign of the TZIDs is wrong
pieces.add(new Double(-longitude).toString()); // long
pieces.add(StandardCodes.NO_COUNTRY); // country
zoneData.put("Etc/GMT" + (i == 0 ? "" : i < 0 ? "" + i : "+" + i),
pieces);
}
// add Unknown / UTC
List<String> pieces = new ArrayList<String>();
pieces.add(new Double(0).toString()); // lat
pieces.add(new Double(0).toString()); // long
pieces.add(StandardCodes.NO_COUNTRY); // country
zoneData.put("Etc/Unknown", pieces);
zoneData.put("Etc/UTC", pieces);
zoneData = CldrUtility.protectCollection(zoneData); // protect for later
// now get links
Pattern whitespace = PatternCache.get("\\s+");
XEquivalenceClass<String, String> linkedItems = new XEquivalenceClass<String, String>("None");
for (int i = 0; i < TZFiles.length; ++i) {
in = CldrUtility.getUTF8Data(TZFiles[i]);
String zoneID = null;
while (true) {
String line = in.readLine();
if (line == null)
break;
String originalLine = line;
int commentPos = line.indexOf("#");
String comment = null;
if (commentPos >= 0) {
comment = line.substring(commentPos + 1).trim();
line = line.substring(0, commentPos);
}
line = line.trim();
if (line.length() == 0)
continue;
String[] items = whitespace.split(line);
if (zoneID != null || items[0].equals("Zone")) {
List<String> l = new ArrayList<String>();
l.addAll(Arrays.asList(items));
// Zone Africa/Algiers 0:12:12 - LMT 1891 Mar 15 0:01
// 0:09:21 - PMT 1911 Mar 11 # Paris Mean Time
if (zoneID == null) {
l.remove(0); // "Zone"
zoneID = (String) l.get(0);
String ntzid = (String) FIX_UNSTABLE_TZIDS.get(zoneID);
if (ntzid != null)
zoneID = ntzid;
l.remove(0);
}
List<ZoneLine> zoneRules = zone_rules.get(zoneID);
if (zoneRules == null) {
zoneRules = new ArrayList<ZoneLine>();
zone_rules.put(zoneID, zoneRules);
}
if (l.size() < ZoneLine.FIELD_COUNT
|| l.size() > ZoneLine.FIELD_COUNT_UNTIL) {
System.out.println("***Zone incorrect field count:");
System.out.println(l);
System.out.println(originalLine);
}
ZoneLine zoneLine = new ZoneLine(l);
zoneLine.comment = comment;
zoneRules.add(zoneLine);
if (l.size() == ZoneLine.FIELD_COUNT) {
zoneID = null; // no continuation line
}
} else if (items[0].equals("Rule")) {
// # Rule NAME FROM TO TYPE IN ON AT SAVE LETTER/S
// Rule Algeria 1916 only - Jun 14 23:00s 1:00 S
String ruleID = items[1];
List<RuleLine> ruleList = ruleID_rules.get(ruleID);
if (ruleList == null) {
ruleList = new ArrayList<RuleLine>();
ruleID_rules.put(ruleID, ruleList);
}
List<String> l = new ArrayList<String>();
l.addAll(Arrays.asList(items));
l.remove(0);
l.remove(0);
if (l.size() != RuleLine.FIELD_COUNT) {
System.out.println("***Rule incorrect field count:");
System.out.println(l);
}
if (comment != null)
l.add(comment);
RuleLine ruleLine = new RuleLine(l);
ruleList.add(ruleLine);
} else if (items[0].equals("Link")) {
String old = items[2];
String newOne = items[1];
if (!(SKIP_LINKS.contains(old) && SKIP_LINKS.contains(newOne))) {
//System.out.println("Original " + old + "\t=>\t" + newOne);
linkedItems.add(old, newOne);
}
/*
* String conflict = (String) linkold_new.get(old); if (conflict !=
* null) { System.out.println("Conflict with old: " + old + " => " +
* conflict + ", " + newOne); } System.out.println(old + "\t=>\t" +
* newOne); linkold_new.put(old, newOne);
*/
} else {
if (DEBUG)
System.out.println("Unknown zone line: " + line);
}
}
in.close();
}
// add in stuff that should be links
for (int i = 0; i < ADD_ZONE_ALIASES_DATA.length; ++i) {
linkedItems.add(ADD_ZONE_ALIASES_DATA[i][0],
ADD_ZONE_ALIASES_DATA[i][1]);
}
Set<String> isCanonical = zoneData.keySet();
// walk through the sets, and
// if any set contains two canonical items, split it.
// if any contains one, make it the primary
// if any contains zero, problem!
for (Set<String> equivalents : linkedItems.getEquivalenceSets()) {
Set<String> canonicals = new TreeSet<String>(equivalents);
canonicals.retainAll(isCanonical);
if (canonicals.size() == 0)
throw new IllegalArgumentException("No canonicals in: " + equivalents);
if (canonicals.size() > 1) {
if (DEBUG) {
System.out.println("Too many canonicals in: " + equivalents);
System.out
.println("\t*Don't* put these into the same equivalence class: "
+ canonicals);
}
Set<String> remainder = new TreeSet<String>(equivalents);
remainder.removeAll(isCanonical);
if (remainder.size() != 0) {
if (DEBUG) {
System.out
.println("\tThe following should be equivalent to others: "
+ remainder);
}
}
}
{
String newOne;
// get the item that we want to hang all the aliases off of.
// normally this is the first (alphabetically) one, but
// it may be overridden with PREFERRED_BASES
Set<String> preferredItems = new HashSet<String>(PREFERRED_BASES);
preferredItems.retainAll(canonicals);
if (preferredItems.size() > 0) {
newOne = preferredItems.iterator().next();
} else {
newOne = canonicals.iterator().next();
}
for (String oldOne : equivalents) {
if (canonicals.contains(oldOne))
continue;
// System.out.println("Mapping " + oldOne + "\t=>\t" + newOne);
linkold_new.put(oldOne, newOne);
}
}
}
/*
* // fix the links from old to new, to remove chains for (Iterator it =
* linkold_new.keySet().iterator(); it.hasNext();) { Object oldItem =
* it.next(); Object newItem = linkold_new.get(oldItem); while (true) {
* Object linkItem = linkold_new.get(newItem); if (linkItem == null)
* break; if (true) System.out.println("Connecting link chain: " + oldItem +
* "\t=> " + newItem + "\t=> " + linkItem); newItem = linkItem;
* linkold_new.put(oldItem, newItem); } }
* // reverse the links *from* canonical names for (Iterator it =
* linkold_new.keySet().iterator(); it.hasNext();) { Object oldItem =
* it.next(); if (!isCanonical.contains(oldItem)) continue; Object newItem =
* linkold_new.get(oldItem); }
*
* // fix unstable TZIDs Set itemsToRemove = new HashSet(); Map
* itemsToAdd = new HashMap(); for (Iterator it =
* linkold_new.keySet().iterator(); it.hasNext();) { Object oldItem =
* it.next(); Object newItem = linkold_new.get(oldItem); Object modOldItem =
* RESTORE_UNSTABLE_TZIDS.get(oldItem); Object modNewItem =
* FIX_UNSTABLE_TZIDS.get(newItem); if (modOldItem == null && modNewItem ==
* null) continue; if (modOldItem == null) { // just fix old entry
* itemsToAdd.put(oldItem, modNewItem); continue; } // otherwise have to
* nuke and redo itemsToRemove.add(oldItem); if (modNewItem == null)
* modNewItem = newItem; itemsToAdd.put(modOldItem, modNewItem); } // now
* make fixes (we couldn't earlier because we were iterating
* Utility.removeAll(linkold_new, itemsToRemove);
* linkold_new.putAll(itemsToAdd);
* // now remove all links that are from canonical zones
* Utility.removeAll(linkold_new, zoneData.keySet());
*/
// generate list of new to old
for (Iterator<String> it = linkold_new.keySet().iterator(); it.hasNext();) {
String oldZone = it.next();
String newZone = linkold_new.get(oldZone);
Set<String> s = linkNew_oldSet.get(newZone);
if (s == null)
linkNew_oldSet.put(newZone, s = new HashSet<String>());
s.add(oldZone);
}
// PROTECT EVERYTHING
linkNew_oldSet = CldrUtility.protectCollection(linkNew_oldSet);
linkold_new = CldrUtility.protectCollection(linkold_new);
ruleID_rules = CldrUtility.protectCollection(ruleID_rules);
zone_rules = CldrUtility.protectCollection(zone_rules);
// TODO protect zone info later
} catch (IOException e) {
throw new ICUUncheckedIOException(
"Can't find timezone aliases: " + e.toString(), e);
}
}
/**
* @param m
*/
private Double getDegrees(Matcher m, boolean lat) {
int startIndex = lat ? 1 : 5;
double amount = Integer.parseInt(m.group(startIndex + 1))
+ Integer.parseInt(m.group(startIndex + 2)) / 60.0;
if (m.group(startIndex + 3) != null)
amount += Integer.parseInt(m.group(startIndex + 3)) / 3600.0;
if (m.group(startIndex).equals("-"))
amount = -amount;
return new Double(amount);
}
/**
* @return Returns the linkold_new.
*/
public Map<String, String> getZoneLinkold_new() {
getZoneData();
return linkold_new;
}
/**
* @return Returns the linkold_new.
*/
public Map<String, Set<String>> getZoneLinkNew_OldSet() {
getZoneData();
return linkNew_oldSet;
}
/**
* @return Returns the ruleID_rules.
*/
public Map<String, List<RuleLine>> getZoneRuleID_rules() {
getZoneData();
return ruleID_rules;
}
/**
* @return Returns the zone_rules.
*/
public Map<String, List<ZoneLine>> getZone_rules() {
getZoneData();
return zone_rules;
}
public String getVersion() {
return version;
}
}