• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 package org.unicode.cldr.util;
2 
3 import java.io.BufferedReader;
4 import java.io.IOException;
5 import java.util.ArrayList;
6 import java.util.Arrays;
7 import java.util.Collections;
8 import java.util.Comparator;
9 import java.util.HashSet;
10 import java.util.Iterator;
11 import java.util.List;
12 import java.util.Locale;
13 import java.util.Map;
14 import java.util.Set;
15 import java.util.TreeMap;
16 import java.util.TreeSet;
17 import java.util.regex.Matcher;
18 import java.util.regex.Pattern;
19 
20 import com.ibm.icu.util.ICUUncheckedIOException;
21 
22 public class ZoneParser {
23     static final boolean DEBUG = false;
24 
25     private String version;
26 
27     private Map<String, String> zone_to_country;
28 
29     private Map<String, Set<String>> country_to_zoneSet;
30 
31     /**
32      * @return mapping from zone id to country. If a zone has no country, then XX
33      *         is used.
34      */
getZoneToCounty()35     public Map<String, String> getZoneToCounty() {
36         if (zone_to_country == null)
37             make_zone_to_country();
38         return zone_to_country;
39     }
40 
41     /**
42      * @return mapping from country to zoneid. If a zone has no country, then XX
43      *         is used.
44      */
getCountryToZoneSet()45     public Map<String, Set<String>> getCountryToZoneSet() {
46         if (country_to_zoneSet == null)
47             make_zone_to_country();
48         return country_to_zoneSet;
49     }
50 
51     /**
52      * @return map from tzids to a list: latitude, longitude, country, comment?. + =
53      *         N or E
54      */
getZoneData()55     public Map<String, List<String>> getZoneData() {
56         if (zoneData == null)
57             makeZoneData();
58         return zoneData;
59     }
60 
getDeprecatedZoneIDs()61     public List<String> getDeprecatedZoneIDs() {
62         return Arrays.asList(FIX_DEPRECATED_ZONE_DATA);
63     }
64 
65     /**
66      *
67      */
make_zone_to_country()68     private void make_zone_to_country() {
69         zone_to_country = new TreeMap<String, String>(TZIDComparator);
70         country_to_zoneSet = new TreeMap<String, Set<String>>();
71         // Map aliasMap = getAliasMap();
72         Map<String, List<String>> zoneData = getZoneData();
73         for (String zone : zoneData.keySet()) {
74             String country = (String) zoneData.get(zone).get(2);
75             zone_to_country.put(zone, country);
76             Set<String> s = country_to_zoneSet.get(country);
77             if (s == null)
78                 country_to_zoneSet.put(country, s = new TreeSet<String>());
79             s.add(zone);
80         }
81         /*
82          * Set territories = getAvailableCodes("territory"); for (Iterator it =
83          * territories.iterator(); it.hasNext();) { String code = (String)
84          * it.next(); String[] zones = TimeZone.getAvailableIDs(code); for (int i =
85          * 0; i < zones.length; ++i) { if (aliasMap.get(zones[i]) != null) continue;
86          * zone_to_country.put(zones[i], code); } } String[] zones =
87          * TimeZone.getAvailableIDs(); for (int i = 0; i < zones.length; ++i) { if
88          * (aliasMap.get(zones[i]) != null) continue; if
89          * (zone_to_country.get(zones[i]) == null) { zone_to_country.put(zones[i],
90          * NO_COUNTRY); } } for (Iterator it = zone_to_country.keySet().iterator();
91          * it.hasNext();) { String tzid = (String) it.next(); String country =
92          * (String) zone_to_country.get(tzid); Set s = (Set)
93          * country_to_zoneSet.get(country); if (s == null)
94          * country_to_zoneSet.put(country, s = new TreeSet()); s.add(tzid); }
95          */
96         // protect
97         zone_to_country = Collections.unmodifiableMap(zone_to_country);
98         country_to_zoneSet = CldrUtility.protectCollection(country_to_zoneSet);
99     }
100 
101     /**
102      *
103      *
104      * private Map bogusZones = null;
105      *
106      * private Map getAliasMap() { if (bogusZones == null) { try { bogusZones =
107      * new TreeMap(); BufferedReader in =
108      * Utility.getUTF8Data"TimeZoneAliases.txt"); while (true) { String line =
109      * in.readLine(); if (line == null) break; line = line.trim(); int pos =
110      * line.indexOf('#'); if (pos >= 0) { skippedAliases.add(line); line =
111      * line.substring(0,pos).trim(); } if (line.length() == 0) continue; List
112      * pieces = Utility.splitList(line,';', true); bogusZones.put(pieces.get(0),
113      * pieces.get(1)); } in.close(); } catch (IOException e) { throw new
114      * IllegalArgumentException("Can't find timezone aliases"); } } return
115      * bogusZones; }
116      */
117 
118     Map<String, List<String>> zoneData;
119 
120     Set<String> skippedAliases = new TreeSet<String>();
121 
122     /*
123      * # This file contains a table with the following columns: # 1. ISO 3166
124      * 2-character country code. See the file `iso3166.tab'. # 2. Latitude and
125      * longitude of the zone's principal location # in ISO 6709
126      * sign-degrees-minutes-seconds format, # either +-DDMM+-DDDMM or
127      * +-DDMMSS+-DDDMMSS, # first latitude (+ is north), then longitude (+ is
128      * east). # 3. Zone name used in value of TZ environment variable. # 4.
129      * Comments; present if and only if the country has multiple rows. # # Columns
130      * are separated by a single tab.
131      */
parseYear(String year, int defaultValue)132     static int parseYear(String year, int defaultValue) {
133         if ("only".startsWith(year))
134             return defaultValue;
135         if ("minimum".startsWith(year))
136             return Integer.MIN_VALUE;
137         if ("maximum".startsWith(year))
138             return Integer.MAX_VALUE;
139         return Integer.parseInt(year);
140     }
141 
142     public static class Time {
143         public int seconds;
144         public byte type;
145         static final byte WALL = 0, STANDARD = 1, UNIVERSAL = 2;
146 
Time(String in)147         Time(String in) {
148             if (in.equals("-")) return; // zero/WALL is the default
149             char suffix = in.charAt(in.length() - 1);
150             switch (suffix) {
151             case 'w':
152                 in = in.substring(0, in.length() - 1);
153                 break;
154             case 's':
155                 in = in.substring(0, in.length() - 1);
156                 type = STANDARD;
157                 break;
158             case 'u':
159             case 'g':
160             case 'z':
161                 in = in.substring(0, in.length() - 1);
162                 type = UNIVERSAL;
163                 break;
164             }
165             seconds = parseSeconds(in, false);
166         }
167 
parseSeconds(String in, boolean allowNegative)168         public static int parseSeconds(String in, boolean allowNegative) {
169             boolean negative = false;
170             if (in.startsWith("-")) {
171                 assert (allowNegative);
172                 negative = true;
173                 in = in.substring(1);
174             }
175             String[] pieces = in.split(":");
176             int multiplier = 3600;
177             int result = 0;
178             for (int i = 0; i < pieces.length; ++i) {
179                 result += multiplier * Integer.parseInt(pieces[i]);
180                 multiplier /= 60;
181                 assert (multiplier >= 0);
182             }
183             if (negative) result = -result;
184             return result;
185         }
186 
toString()187         public String toString() {
188             return BoilerplateUtilities.toStringHelper(this);
189         }
190     }
191 
192     static final String[] months = { "january", "february", "march", "april", "may", "june", "july", "august",
193         "september", "october", "november", "december" };
194     static final String[] weekdays = { "sunday", "monday", "tuesday", "wednesday", "thursday", "friday", "saturday" };
195 
findStartsWith(String value, String[] array, boolean exact)196     static int findStartsWith(String value, String[] array, boolean exact) {
197         value = value.toLowerCase(Locale.ENGLISH);
198         for (int i = 0; i < array.length; ++i) {
199             if (array[i].startsWith(value)) return i;
200         }
201         throw new IllegalArgumentException("Can't find " + value + " in " + Arrays.asList(months));
202     }
203 
204     static Pattern dayPattern = PatternCache.get("([0-9]+)|(last)([a-z]+)|([a-z]+)([<=>]+)([0-9]+)");
205     static final String[] relations = { "<=", ">=" };
206 
207     public static class Day implements Comparable<Object> {
208         public int number;
209         public byte relation;
210         public int weekDay;
211         static final byte NONE = 0, LEQ = 2, GEQ = 4;
212 
Day(String value)213         Day(String value) {
214             value = value.toLowerCase();
215             Matcher matcher = dayPattern.matcher(value);
216             if (!matcher.matches()) {
217                 throw new IllegalArgumentException();
218             }
219             if (matcher.group(1) != null) {
220                 number = Integer.parseInt(matcher.group(1));
221                 return;
222             }
223             if (matcher.group(2) != null) {
224                 weekDay = findStartsWith(matcher.group(3), weekdays, false);
225                 number = 31;
226                 relation = LEQ;
227                 return;
228             }
229             if (matcher.group(4) != null) {
230                 weekDay = findStartsWith(matcher.group(4), weekdays, false);
231                 relation = (byte) findStartsWith(matcher.group(5), relations, false);
232                 number = Integer.parseInt(matcher.group(6));
233                 return;
234             }
235             throw new IllegalArgumentException();
236         }
237 
toString()238         public String toString() {
239             return BoilerplateUtilities.toStringHelper(this);
240         }
241 
compareTo(Object other)242         public int compareTo(Object other) {
243             return toString().compareTo(other.toString());
244         }
245     }
246 
247     /**
248      *
249      A rule line has the form
250      *
251      * Rule NAME FROM TO TYPE IN ON AT SAVE LETTER/S
252      *
253      * For example:
254      *
255      * Rule US 1967 1973 - Apr lastSun 2:00 1:00 D
256      *
257      * The fields that make up a rule line are:
258      *
259      * NAME Gives the (arbitrary) name of the set of rules this
260      * rule is part of.
261      *
262      * FROM Gives the first year in which the rule applies. Any
263      * integer year can be supplied; the Gregorian calendar
264      * is assumed. The word minimum (or an abbreviation)
265      * means the minimum year representable as an integer.
266      * The word maximum (or an abbreviation) means the
267      * maximum year representable as an integer. Rules can
268      * describe times that are not representable as time
269      * values, with the unrepresentable times ignored; this
270      * allows rules to be portable among hosts with
271      * differing time value types.
272      *
273      * TO Gives the final year in which the rule applies. In
274      * addition to minimum and maximum (as above), the word
275      * only (or an abbreviation) may be used to repeat the
276      * value of the FROM field.
277      *
278      * TYPE Gives the type of year in which the rule applies.
279      * If TYPE is - then the rule applies in all years
280      * between FROM and TO inclusive. If TYPE is something
281      * else, then zic executes the command
282      * yearistype year type
283      * to check the type of a year: an exit status of zero
284      * is taken to mean that the year is of the given type;
285      * an exit status of one is taken to mean that the year
286      * is not of the given type.
287      *
288      * IN Names the month in which the rule takes effect.
289      * Month names may be abbreviated.
290      *
291      * ON Gives the day on which the rule takes effect.
292      * Recognized forms include:
293      *
294      * 5 the fifth of the month
295      * lastSun the last Sunday in the month
296      * lastMon the last Monday in the month
297      * Sun>=8 first Sunday on or after the eighth
298      * Sun<=25 last Sunday on or before the 25th
299      *
300      * Names of days of the week may be abbreviated or
301      * spelled out in full. Note that there must be no
302      * spaces within the ON field.
303      *
304      * AT Gives the time of day at which the rule takes
305      * effect. Recognized forms include:
306      *
307      * 2 time in hours
308      * 2:00 time in hours and minutes
309      * 15:00 24-hour format time (for times after noon)
310      * 1:28:14 time in hours, minutes, and seconds
311      * - equivalent to 0
312      *
313      * where hour 0 is midnight at the start of the day,
314      * and hour 24 is midnight at the end of the day. Any
315      * of these forms may be followed by the letter w if
316      * the given time is local "wall clock" time, s if the
317      * given time is local "standard" time, or u (or g or
318      * z) if the given time is universal time; in the
319      * absence of an indicator, wall clock time is assumed.
320      *** cannot be negative
321      *
322      * SAVE Gives the amount of time to be added to local
323      * standard time when the rule is in effect. This
324      * field has the same format as the AT field (although,
325      * of course, the w and s suffixes are not used).
326      *** can be positive or negative
327      *
328      * LETTER/S
329      * Gives the "variable part" (for example, the "S" or
330      * "D" in "EST" or "EDT") of time zone abbreviations to
331      * be used when this rule is in effect. If this field
332      * is -, the variable part is null.
333      *
334      *
335      *
336      */
337 
338     public static class RuleLine {
339         public static Set<String> types = new TreeSet<String>();
340         public static Set<Day> days = new TreeSet<Day>();
341         static Set<Integer> saves = new TreeSet<Integer>();
342 
RuleLine(List<String> l)343         RuleLine(List<String> l) {
344             fromYear = parseYear(l.get(0), 0);
345             toYear = parseYear(l.get(1), fromYear);
346             type = l.get(2);
347             if (type.equals("-")) type = null;
348             month = 1 + findStartsWith((String) l.get(3), months, false);
349             day = new Day(l.get(4));
350             time = new Time(l.get(5));
351             save = Time.parseSeconds(l.get(6), true);
352             letter = l.get(7);
353             if (letter.equals("-")) letter = null;
354             if (type != null) types.add(type);
355             days.add(day);
356         }
357 
toString()358         public String toString() {
359             return BoilerplateUtilities.toStringHelper(this);
360         }
361 
362         public int fromYear;
363 
364         public int toYear;
365 
366         public String type;
367 
368         public int month;
369 
370         public Day day;
371 
372         public Time time;
373 
374         public int save;
375 
376         public String letter;
377 
378         public static final int FIELD_COUNT = 8; // excluding Rule, Name
379     }
380 
381     /**
382      * A zone line has the form
383      *
384      * Zone NAME GMTOFF RULES/SAVE FORMAT [UNTIL]
385      *
386      * For example:
387      *
388      * Zone Australia/Adelaide 9:30 Aus CST 1971 Oct 31 2:00
389      *
390      * The fields that make up a zone line are:
391      *
392      * NAME The name of the time zone. This is the name used in
393      * creating the time conversion information file for the
394      * zone.
395      *
396      * GMTOFF
397      * The amount of time to add to UTC to get standard time
398      * in this zone. This field has the same format as the
399      * AT and SAVE fields of rule lines; begin the field with
400      * a minus sign if time must be subtracted from UTC.
401      *
402      * RULES/SAVE
403      * The name of the rule(s) that apply in the time zone
404      * or, alternately, an amount of time to add to local
405      * standard time. If this field is - then standard time
406      * always applies in the time zone.
407      *
408      * FORMAT
409      * The format for time zone abbreviations in this time
410      * zone. The pair of characters %s is used to show where
411      * the "variable part" of the time zone abbreviation
412      * goes. Alternately, a slash (/) separates standard and
413      * daylight abbreviations.
414      *
415      * UNTIL The time at which the UTC offset or the rule(s) change
416      * for a location. It is specified as a year, a month, a
417      * day, and a time of day. If this is specified, the
418      * time zone information is generated from the given UTC
419      * offset and rule change until the time specified. The
420      * month, day, and time of day have the same format as
421      * the IN, ON, and AT columns of a rule; trailing columns
422      * can be omitted, and default to the earliest possible
423      * value for the missing columns.
424      *
425      * The next line must be a "continuation" line; this has
426      * the same form as a zone line except that the string
427      * "Zone" and the name are omitted, as the continuation
428      * line will place information starting at the time
429      * specified as the UNTIL field in the previous line in
430      * the file used by the previous line. Continuation
431      * lines may contain an UNTIL field, just as zone lines
432      * do, indicating that the next line is a further
433      * continuation.
434      */
435     public static class ZoneLine {
436         public static Set<Day> untilDays = new TreeSet<Day>();
437         public static Set<String> rulesSaves = new TreeSet<String>();
438 
ZoneLine(List<String> l)439         ZoneLine(List<String> l) {
440             gmtOff = Time.parseSeconds(l.get(0), true);
441             rulesSave = (String) l.get(1);
442             if (rulesSave.equals("-"))
443                 rulesSave = "0";
444             else if (rulesSave.charAt(0) < 'A') rulesSave = "" + Time.parseSeconds(rulesSave, false);
445 
446             format = (String) l.get(2);
447             switch (l.size()) {
448             case 7:
449                 untilTime = new Time(l.get(6)); // fall through
450             case 6:
451                 untilDay = new Day(l.get(5)); // fall through
452                 untilDays.add(untilDay);
453             case 5:
454                 untilMonth = 1 + findStartsWith((String) l.get(4), months, false); // fall through
455             case 4:
456                 untilYear = parseYear(l.get(3), Integer.MAX_VALUE); // fall through
457             case 3:
458                 break; // ok
459             default:
460                 throw new IllegalArgumentException("Wrong field count: " + l);
461             }
462             rulesSaves.add(rulesSave);
463         }
464 
toString()465         public String toString() {
466             return BoilerplateUtilities.toStringHelper(this);
467         }
468 
469         public int gmtOff;
470 
471         public String rulesSave;
472 
473         public String format;
474 
475         public int untilYear = Integer.MAX_VALUE; // indicating continuation
476 
477         public int untilMonth;
478 
479         public Day untilDay;
480 
481         public Time untilTime;
482 
483         public String comment;
484 
485         public static final int FIELD_COUNT = 3; // excluding Zone, Name
486 
487         public static final int FIELD_COUNT_UNTIL = 7; // excluding Zone, Name
488     }
489 
490     Map<String, List<RuleLine>> ruleID_rules = new TreeMap<String, List<RuleLine>>();
491 
492     Map<String, List<ZoneLine>> zone_rules = new TreeMap<String, List<ZoneLine>>();
493 
494     Map<String, String> linkold_new = new TreeMap<String, String>();
495 
496     Map<String, Set<String>> linkNew_oldSet = new TreeMap<String, Set<String>>();
497 
498     public class Transition {
499         public long date;
500         public long offset;
501         public String abbreviation;
502     }
503 
504     public class TransitionList {
505 
addTransitions(ZoneLine lastZoneLine, ZoneLine zoneLine, int startYear, int endYear)506         void addTransitions(ZoneLine lastZoneLine, ZoneLine zoneLine, int startYear, int endYear) {
507             // add everything between the zonelines
508             if (lastZoneLine == null) {
509                 return;
510             }
511             startYear = Math.max(startYear, lastZoneLine.untilYear);
512             endYear = Math.min(endYear, zoneLine.untilYear);
513             int gmtOffset = lastZoneLine.gmtOff;
514             for (int year = startYear; year <= endYear; ++year) {
515                 resolveTime(gmtOffset, lastZoneLine.untilYear, lastZoneLine.untilMonth,
516                     lastZoneLine.untilDay, lastZoneLine.untilTime);
517             }
518         }
519 
resolveTime(int gmtOffset, int untilYear, int untilMonth, Day untilDay, Time untilTime)520         private long resolveTime(int gmtOffset, int untilYear, int untilMonth, Day untilDay, Time untilTime) {
521             return 0;
522         }
523     }
524 
getTransitions(String zoneID, int startYear, int endYear)525     public TransitionList getTransitions(String zoneID, int startYear, int endYear) {
526         TransitionList results = new TransitionList();
527         List<ZoneLine> rules = zone_rules.get(zoneID);
528         ZoneLine lastZoneLine = null;
529         for (ZoneLine zoneLine : rules) {
530             results.addTransitions(lastZoneLine, zoneLine, startYear, endYear);
531             lastZoneLine = zoneLine;
532         }
533         return results;
534     }
535 
getTZIDComparator()536     public Comparator<String> getTZIDComparator() {
537         return TZIDComparator;
538     }
539 
540     private static List<String> errorData = Arrays.asList(new String[] {
541         new Double(Double.MIN_VALUE).toString(), new Double(Double.MIN_VALUE).toString(), "" });
542 
543     private Comparator<String> TZIDComparator = new Comparator<String>() {
544         Map<String, List<String>> data = getZoneData();
545 
546         public int compare(String s1, String s2) {
547             List<String> data1 = data.get(s1);
548             if (data1 == null)
549                 data1 = errorData;
550             List<String> data2 = data.get(s2);
551             if (data2 == null)
552                 data2 = errorData;
553 
554             int result;
555             // country
556             String country1 = (String) data1.get(2);
557             String country2 = (String) data2.get(2);
558 
559             if ((result = country1.compareTo(country2)) != 0)
560                 return result;
561             // longitude
562             Double d1 = Double.valueOf(data1.get(1));
563             Double d2 = Double.valueOf(data2.get(1));
564             if ((result = d1.compareTo(d2)) != 0)
565                 return result;
566             // latitude
567             d1 = Double.valueOf(data1.get(0));
568             d2 = Double.valueOf(data2.get(0));
569             if ((result = d1.compareTo(d2)) != 0)
570                 return result;
571             // name
572             return s1.compareTo(s2);
573         }
574     };
575 
576     public static MapComparator<String> regionalCompare = new MapComparator<String>();
577     static {
578         regionalCompare.add("America");
579         regionalCompare.add("Atlantic");
580         regionalCompare.add("Europe");
581         regionalCompare.add("Africa");
582         regionalCompare.add("Asia");
583         regionalCompare.add("Indian");
584         regionalCompare.add("Australia");
585         regionalCompare.add("Pacific");
586         regionalCompare.add("Arctic");
587         regionalCompare.add("Antarctica");
588         regionalCompare.add("Etc");
589     }
590 
591     private static String[] TZFiles = { "africa", "antarctica", "asia",
592         "australasia", "backward", "etcetera", "europe", "northamerica",
593         "pacificnew", "southamerica", "systemv" };
594 
595     private static Map<String, String> FIX_UNSTABLE_TZIDS;
596 
597     private static Set<String> SKIP_LINKS = new HashSet<String>(Arrays.asList(
598         new String[] {
599             "America/Montreal", "America/Toronto",
600             "America/Santa_Isabel", "America/Tijuana" }));
601 
602     private static Set<String> PREFERRED_BASES = new HashSet<String>(Arrays.asList(new String[] { "Europe/London" }));
603 
604     private static String[][] ADD_ZONE_ALIASES_DATA = {
605         { "Etc/UCT", "Etc/UTC" },
606 
607         { "EST", "Etc/GMT+5" },
608         { "MST", "Etc/GMT+7" },
609         { "HST", "Etc/GMT+10" },
610 
611         { "SystemV/AST4", "Etc/GMT+4" },
612         { "SystemV/EST5", "Etc/GMT+5" },
613         { "SystemV/CST6", "Etc/GMT+6" },
614         { "SystemV/MST7", "Etc/GMT+7" },
615         { "SystemV/PST8", "Etc/GMT+8" },
616         { "SystemV/YST9", "Etc/GMT+9" },
617         { "SystemV/HST10", "Etc/GMT+10" },
618     };
619 
620     static String[] FIX_DEPRECATED_ZONE_DATA = {
621         "Africa/Timbuktu",
622         "America/Argentina/ComodRivadavia",
623         "America/Santa_Isabel",
624         "Europe/Belfast",
625         "Pacific/Yap",
626         "Antarctica/South_Pole",
627         "America/Shiprock",
628         "America/Montreal",
629         "Asia/Chongqing",
630         "Asia/Harbin",
631         "Asia/Kashgar"
632     };
633     static {
634         // The format is <new name>, <old name>
635         String[][] FIX_UNSTABLE_TZID_DATA = new String[][] {
636             { "America/Atikokan", "America/Coral_Harbour" },
637             { "America/Argentina/Buenos_Aires", "America/Buenos_Aires" },
638             { "America/Argentina/Catamarca", "America/Catamarca" },
639             { "America/Argentina/Cordoba", "America/Cordoba" },
640             { "America/Argentina/Jujuy", "America/Jujuy" },
641             { "America/Argentina/Mendoza", "America/Mendoza" },
642             { "America/Kentucky/Louisville", "America/Louisville" },
643             { "America/Indiana/Indianapolis", "America/Indianapolis" },
644             { "Africa/Asmara", "Africa/Asmera" },
645             { "Atlantic/Faroe", "Atlantic/Faeroe" },
646             { "Asia/Kolkata", "Asia/Calcutta" },
647             { "Asia/Ho_Chi_Minh", "Asia/Saigon" },
648             { "Asia/Yangon", "Asia/Rangoon" },
649             { "Asia/Kathmandu", "Asia/Katmandu" },
650             { "Pacific/Pohnpei", "Pacific/Ponape" },
651             { "Pacific/Chuuk", "Pacific/Truk" },
652             { "Pacific/Honolulu", "Pacific/Johnston" }
653         };
654         FIX_UNSTABLE_TZIDS = CldrUtility.asMap(FIX_UNSTABLE_TZID_DATA);
655     }
656 
657     /**
658      *
659      */
makeZoneData()660     private void makeZoneData() {
661         try {
662             // get version
663             BufferedReader versionIn = CldrUtility.getUTF8Data("tzdb-version.txt");
664             version = versionIn.readLine();
665             if (!version.matches("[0-9]{4}[a-z]")) {
666                 throw new IllegalArgumentException(String.format("Bad Version number: %s, should be of the form 2007x",
667                     version));
668             }
669             versionIn.close();
670 
671             // String deg = "([+-][0-9]+)";//
672             String deg = "([+-])([0-9][0-9][0-9]?)([0-9][0-9])([0-9][0-9])?";//
673             Matcher m = PatternCache.get(deg + deg).matcher("");
674             zoneData = new TreeMap<String, List<String>>();
675             BufferedReader in = CldrUtility.getUTF8Data("zone.tab");
676             while (true) {
677                 String line = in.readLine();
678                 if (line == null)
679                     break;
680                 line = line.trim();
681                 int pos = line.indexOf('#');
682                 if (pos >= 0) {
683                     skippedAliases.add(line);
684                     line = line.substring(0, pos).trim();
685                 }
686                 if (line.length() == 0)
687                     continue;
688                 List<String> pieces = CldrUtility.splitList(line, '\t', true);
689                 String country = pieces.get(0);
690                 String latLong = pieces.get(1);
691                 String tzid = pieces.get(2);
692                 String ntzid = FIX_UNSTABLE_TZIDS.get(tzid);
693                 if (ntzid != null)
694                     tzid = ntzid;
695                 String comment = pieces.size() < 4 ? null : (String) pieces.get(3);
696                 pieces.clear();
697                 if (!m.reset(latLong).matches())
698                     throw new IllegalArgumentException("Bad zone.tab, lat/long format: "
699                         + line);
700 
701                 pieces.add(getDegrees(m, true).toString());
702                 pieces.add(getDegrees(m, false).toString());
703                 pieces.add(country);
704                 if (comment != null)
705                     pieces.add(comment);
706                 if (zoneData.containsKey(tzid))
707                     throw new IllegalArgumentException("Bad zone.tab, duplicate entry: "
708                         + line);
709                 zoneData.put(tzid, pieces);
710             }
711             in.close();
712             // add Etcs
713             for (int i = -14; i <= 12; ++i) {
714                 List<String> pieces = new ArrayList<String>();
715                 int latitude = 0;
716                 int longitude = i * 15;
717                 if (longitude <= -180) {
718                     longitude += 360;
719                 }
720                 pieces.add(new Double(latitude).toString()); // lat
721                 // remember that the sign of the TZIDs is wrong
722                 pieces.add(new Double(-longitude).toString()); // long
723                 pieces.add(StandardCodes.NO_COUNTRY); // country
724 
725                 zoneData.put("Etc/GMT" + (i == 0 ? "" : i < 0 ? "" + i : "+" + i),
726                     pieces);
727             }
728             // add Unknown / UTC
729             List<String> pieces = new ArrayList<String>();
730             pieces.add(new Double(0).toString()); // lat
731             pieces.add(new Double(0).toString()); // long
732             pieces.add(StandardCodes.NO_COUNTRY); // country
733             zoneData.put("Etc/Unknown", pieces);
734             zoneData.put("Etc/UTC", pieces);
735 
736             zoneData = CldrUtility.protectCollection(zoneData); // protect for later
737 
738             // now get links
739             Pattern whitespace = PatternCache.get("\\s+");
740             XEquivalenceClass<String, String> linkedItems = new XEquivalenceClass<String, String>("None");
741             for (int i = 0; i < TZFiles.length; ++i) {
742                 in = CldrUtility.getUTF8Data(TZFiles[i]);
743                 String zoneID = null;
744                 while (true) {
745                     String line = in.readLine();
746                     if (line == null)
747                         break;
748                     String originalLine = line;
749                     int commentPos = line.indexOf("#");
750                     String comment = null;
751                     if (commentPos >= 0) {
752                         comment = line.substring(commentPos + 1).trim();
753                         line = line.substring(0, commentPos);
754                     }
755                     line = line.trim();
756                     if (line.length() == 0)
757                         continue;
758                     String[] items = whitespace.split(line);
759                     if (zoneID != null || items[0].equals("Zone")) {
760                         List<String> l = new ArrayList<String>();
761                         l.addAll(Arrays.asList(items));
762 
763                         // Zone Africa/Algiers 0:12:12 - LMT 1891 Mar 15 0:01
764                         // 0:09:21 - PMT 1911 Mar 11 # Paris Mean Time
765                         if (zoneID == null) {
766                             l.remove(0); // "Zone"
767                             zoneID = (String) l.get(0);
768                             String ntzid = (String) FIX_UNSTABLE_TZIDS.get(zoneID);
769                             if (ntzid != null)
770                                 zoneID = ntzid;
771                             l.remove(0);
772                         }
773                         List<ZoneLine> zoneRules = zone_rules.get(zoneID);
774                         if (zoneRules == null) {
775                             zoneRules = new ArrayList<ZoneLine>();
776                             zone_rules.put(zoneID, zoneRules);
777                         }
778 
779                         if (l.size() < ZoneLine.FIELD_COUNT
780                             || l.size() > ZoneLine.FIELD_COUNT_UNTIL) {
781                             System.out.println("***Zone incorrect field count:");
782                             System.out.println(l);
783                             System.out.println(originalLine);
784                         }
785 
786                         ZoneLine zoneLine = new ZoneLine(l);
787                         zoneLine.comment = comment;
788                         zoneRules.add(zoneLine);
789                         if (l.size() == ZoneLine.FIELD_COUNT) {
790                             zoneID = null; // no continuation line
791                         }
792                     } else if (items[0].equals("Rule")) {
793                         // # Rule NAME FROM TO TYPE IN ON AT SAVE LETTER/S
794                         // Rule Algeria 1916 only - Jun 14 23:00s 1:00 S
795 
796                         String ruleID = items[1];
797                         List<RuleLine> ruleList = ruleID_rules.get(ruleID);
798                         if (ruleList == null) {
799                             ruleList = new ArrayList<RuleLine>();
800                             ruleID_rules.put(ruleID, ruleList);
801                         }
802                         List<String> l = new ArrayList<String>();
803                         l.addAll(Arrays.asList(items));
804                         l.remove(0);
805                         l.remove(0);
806                         if (l.size() != RuleLine.FIELD_COUNT) {
807                             System.out.println("***Rule incorrect field count:");
808                             System.out.println(l);
809                         }
810                         if (comment != null)
811                             l.add(comment);
812                         RuleLine ruleLine = new RuleLine(l);
813                         ruleList.add(ruleLine);
814 
815                     } else if (items[0].equals("Link")) {
816                         String old = items[2];
817                         String newOne = items[1];
818                         if (!(SKIP_LINKS.contains(old) && SKIP_LINKS.contains(newOne))) {
819                             //System.out.println("Original " + old + "\t=>\t" + newOne);
820                             linkedItems.add(old, newOne);
821                         }
822                         /*
823                          * String conflict = (String) linkold_new.get(old); if (conflict !=
824                          * null) { System.out.println("Conflict with old: " + old + " => " +
825                          * conflict + ", " + newOne); } System.out.println(old + "\t=>\t" +
826                          * newOne); linkold_new.put(old, newOne);
827                          */
828                     } else {
829                         if (DEBUG)
830                             System.out.println("Unknown zone line: " + line);
831                     }
832                 }
833                 in.close();
834             }
835             // add in stuff that should be links
836             for (int i = 0; i < ADD_ZONE_ALIASES_DATA.length; ++i) {
837                 linkedItems.add(ADD_ZONE_ALIASES_DATA[i][0],
838                     ADD_ZONE_ALIASES_DATA[i][1]);
839             }
840 
841             Set<String> isCanonical = zoneData.keySet();
842 
843             // walk through the sets, and
844             // if any set contains two canonical items, split it.
845             // if any contains one, make it the primary
846             // if any contains zero, problem!
847             for (Set<String> equivalents : linkedItems.getEquivalenceSets()) {
848                 Set<String> canonicals = new TreeSet<String>(equivalents);
849                 canonicals.retainAll(isCanonical);
850                 if (canonicals.size() == 0)
851                     throw new IllegalArgumentException("No canonicals in: " + equivalents);
852                 if (canonicals.size() > 1) {
853                     if (DEBUG) {
854                         System.out.println("Too many canonicals in: " + equivalents);
855                         System.out
856                             .println("\t*Don't* put these into the same equivalence class: "
857                                 + canonicals);
858                     }
859                     Set<String> remainder = new TreeSet<String>(equivalents);
860                     remainder.removeAll(isCanonical);
861                     if (remainder.size() != 0) {
862                         if (DEBUG) {
863                             System.out
864                                 .println("\tThe following should be equivalent to others: "
865                                     + remainder);
866                         }
867                     }
868                 }
869                 {
870                     String newOne;
871                     // get the item that we want to hang all the aliases off of.
872                     // normally this is the first (alphabetically) one, but
873                     // it may be overridden with PREFERRED_BASES
874                     Set<String> preferredItems = new HashSet<String>(PREFERRED_BASES);
875                     preferredItems.retainAll(canonicals);
876                     if (preferredItems.size() > 0) {
877                         newOne = preferredItems.iterator().next();
878                     } else {
879                         newOne = canonicals.iterator().next();
880                     }
881                     for (String oldOne : equivalents) {
882                         if (canonicals.contains(oldOne))
883                             continue;
884                         // System.out.println("Mapping " + oldOne + "\t=>\t" + newOne);
885                         linkold_new.put(oldOne, newOne);
886                     }
887                 }
888             }
889 
890             /*
891              * // fix the links from old to new, to remove chains for (Iterator it =
892              * linkold_new.keySet().iterator(); it.hasNext();) { Object oldItem =
893              * it.next(); Object newItem = linkold_new.get(oldItem); while (true) {
894              * Object linkItem = linkold_new.get(newItem); if (linkItem == null)
895              * break; if (true) System.out.println("Connecting link chain: " + oldItem +
896              * "\t=> " + newItem + "\t=> " + linkItem); newItem = linkItem;
897              * linkold_new.put(oldItem, newItem); } }
898              * // reverse the links *from* canonical names for (Iterator it =
899              * linkold_new.keySet().iterator(); it.hasNext();) { Object oldItem =
900              * it.next(); if (!isCanonical.contains(oldItem)) continue; Object newItem =
901              * linkold_new.get(oldItem); }
902              *
903              * // fix unstable TZIDs Set itemsToRemove = new HashSet(); Map
904              * itemsToAdd = new HashMap(); for (Iterator it =
905              * linkold_new.keySet().iterator(); it.hasNext();) { Object oldItem =
906              * it.next(); Object newItem = linkold_new.get(oldItem); Object modOldItem =
907              * RESTORE_UNSTABLE_TZIDS.get(oldItem); Object modNewItem =
908              * FIX_UNSTABLE_TZIDS.get(newItem); if (modOldItem == null && modNewItem ==
909              * null) continue; if (modOldItem == null) { // just fix old entry
910              * itemsToAdd.put(oldItem, modNewItem); continue; } // otherwise have to
911              * nuke and redo itemsToRemove.add(oldItem); if (modNewItem == null)
912              * modNewItem = newItem; itemsToAdd.put(modOldItem, modNewItem); } // now
913              * make fixes (we couldn't earlier because we were iterating
914              * Utility.removeAll(linkold_new, itemsToRemove);
915              * linkold_new.putAll(itemsToAdd);
916              * // now remove all links that are from canonical zones
917              * Utility.removeAll(linkold_new, zoneData.keySet());
918              */
919 
920             // generate list of new to old
921             for (Iterator<String> it = linkold_new.keySet().iterator(); it.hasNext();) {
922                 String oldZone = it.next();
923                 String newZone = linkold_new.get(oldZone);
924                 Set<String> s = linkNew_oldSet.get(newZone);
925                 if (s == null)
926                     linkNew_oldSet.put(newZone, s = new HashSet<String>());
927                 s.add(oldZone);
928             }
929 
930             // PROTECT EVERYTHING
931             linkNew_oldSet = CldrUtility.protectCollection(linkNew_oldSet);
932             linkold_new = CldrUtility.protectCollection(linkold_new);
933             ruleID_rules = CldrUtility.protectCollection(ruleID_rules);
934             zone_rules = CldrUtility.protectCollection(zone_rules);
935             // TODO protect zone info later
936         } catch (IOException e) {
937             throw new ICUUncheckedIOException(
938                 "Can't find timezone aliases: " + e.toString(), e);
939         }
940     }
941 
942     /**
943      * @param m
944      */
945     private Double getDegrees(Matcher m, boolean lat) {
946         int startIndex = lat ? 1 : 5;
947         double amount = Integer.parseInt(m.group(startIndex + 1))
948             + Integer.parseInt(m.group(startIndex + 2)) / 60.0;
949         if (m.group(startIndex + 3) != null)
950             amount += Integer.parseInt(m.group(startIndex + 3)) / 3600.0;
951         if (m.group(startIndex).equals("-"))
952             amount = -amount;
953         return new Double(amount);
954     }
955 
956     /**
957      * @return Returns the linkold_new.
958      */
959     public Map<String, String> getZoneLinkold_new() {
960         getZoneData();
961         return linkold_new;
962     }
963 
964     /**
965      * @return Returns the linkold_new.
966      */
967     public Map<String, Set<String>> getZoneLinkNew_OldSet() {
968         getZoneData();
969         return linkNew_oldSet;
970     }
971 
972     /**
973      * @return Returns the ruleID_rules.
974      */
975     public Map<String, List<RuleLine>> getZoneRuleID_rules() {
976         getZoneData();
977         return ruleID_rules;
978     }
979 
980     /**
981      * @return Returns the zone_rules.
982      */
983     public Map<String, List<ZoneLine>> getZone_rules() {
984         getZoneData();
985         return zone_rules;
986     }
987 
988     public String getVersion() {
989         return version;
990     }
991 
992 }