]> gitweb.fperrin.net Git - Dictionary.git/blobdiff - jars/icu4j-4_2_1-src/src/com/ibm/icu/dev/test/timezone/TimeZoneAliasTest.java
go
[Dictionary.git] / jars / icu4j-4_2_1-src / src / com / ibm / icu / dev / test / timezone / TimeZoneAliasTest.java
old mode 100755 (executable)
new mode 100644 (file)
index 0ec3860..b653bf7
-//##header\r
-//#if defined(FOUNDATION10) || defined(J2SE13)\r
-//#else\r
-/*\r
- *******************************************************************************\r
- * Copyright (C) 2002-2009, International Business Machines Corporation and    *\r
- * others. All Rights Reserved.                                                *\r
- *******************************************************************************\r
-*/\r
-package com.ibm.icu.dev.test.timezone;\r
-\r
-import java.util.ArrayList;\r
-import java.util.Date;\r
-import java.util.Calendar;\r
-import java.util.GregorianCalendar;\r
-import java.util.HashMap;\r
-import java.util.List;\r
-import java.util.Locale;\r
-import java.util.Map;\r
-import java.util.Set;\r
-import java.util.TreeSet;\r
-import java.util.Iterator;\r
-\r
-import java.text.DateFormat;\r
-import java.text.NumberFormat;\r
-\r
-import com.ibm.icu.dev.test.*;\r
-import com.ibm.icu.dev.test.util.BagFormatter;\r
-import com.ibm.icu.util.TimeZone;\r
-\r
-\r
-/**\r
- * Class for testing TimeZones for consistency\r
- * @author Davis\r
- * \r
- */\r
-public class TimeZoneAliasTest extends TestFmwk {\r
-    \r
-    public static void main(String[] args) throws Exception {\r
-        new TimeZoneAliasTest().run(args);\r
-    }\r
-    \r
-    /**\r
-     * There are two things to check aliases for:<br>\r
-     * 1. the alias set must be uniform: if a isAlias b, then aliasSet(a) == aliasSet(b)<br>\r
-     * 2. all aliases must have the same offsets\r
-      */\r
-    public void TestAliases() {\r
-        Zone.Seconds seconds = new Zone.Seconds();\r
-        for (Iterator it = Zone.getZoneSet().iterator(); it.hasNext(); ) {\r
-            Zone zone = (Zone)it.next();\r
-            String id = zone.id;\r
-            if (id.indexOf('/') < 0 && (id.endsWith("ST") || id.endsWith("DT"))) {\r
-                if (zone.minRecentOffset != zone.maxRecentOffset) {\r
-                    errln(\r
-                        "Standard or Daylight Time not constant: " + id \r
-                        + ": " + Zone.formatHours(zone.minRecentOffset)\r
-                        + " != " + Zone.formatHours(zone.maxRecentOffset));\r
-                }\r
-            }\r
-            Set aliases = zone.getPurportedAliases();\r
-            Set aliasesSet = new TreeSet(aliases);\r
-            aliasesSet.add(id); // for comparison\r
-            Iterator aliasIterator = aliases.iterator();\r
-            while (aliasIterator.hasNext()) {\r
-                String otherId = (String)aliasIterator.next();\r
-                Zone otherZone = Zone.make(otherId);\r
-                Set otherAliases = otherZone.getPurportedAliases();\r
-                otherAliases.add(otherId); // for comparison\r
-                if (!aliasesSet.equals(otherAliases)) {\r
-                    errln(\r
-                        "Aliases Unsymmetric: "\r
-                        + id + " => " + Zone.bf.join(aliasesSet)\r
-                        + "; " \r
-                        + otherId + " => " + Zone.bf.join(otherAliases));\r
-                }\r
-                if (zone.findOffsetOrdering(otherZone, seconds) != 0) {\r
-                    errln("Aliases differ: " + id + ", " + otherId\r
-                         + " differ at " + seconds);\r
-                }\r
-            }\r
-        }\r
-    }\r
-    \r
-    /**\r
-     * We check to see that every timezone that is not an alias is actually different!\r
-     */\r
-    public void TestDifferences() {\r
-        Zone last = null;\r
-        Zone.Seconds diffDate = new Zone.Seconds();        \r
-        for (Iterator it = Zone.getZoneSet().iterator(); it.hasNext();) {\r
-            Zone testZone = (Zone)it.next();\r
-            if (last != null) {\r
-                String common = testZone + "\tvs " + last + ":\t";\r
-                int diff = testZone.findOffsetOrdering(last, diffDate);\r
-                if (diff != 0) {\r
-                    logln("\t" + common + "difference at: " + diffDate \r
-                        + ", " + Zone.formatHours(diff) + "hr");\r
-                } else if (testZone.isRealAlias(last)) {\r
-                    logln("\t" + common + "alias, no difference");\r
-                } else {\r
-                    errln(common + "NOT ALIAS BUT NO DIFFERENCE!");\r
-                }\r
-            }\r
-            last = testZone;\r
-        }\r
-    }\r
-    \r
-    /**\r
-     * Utility for printing out zones to be translated.\r
-     */\r
-    public static void TestGenerateZones() {\r
-        int count = 1;\r
-        for (Iterator it = Zone.getUniqueZoneSet().iterator(); it.hasNext();) {\r
-            Zone zone = (Zone)it.next();\r
-            System.out.println(zone.toString(count++));\r
-        }\r
-    }\r
-    \r
-    /** Utility; ought to be someplace common\r
-     */\r
-    /*\r
-    static String join(Collection c, String separator) {\r
-        StringBuffer result = new StringBuffer();\r
-        boolean isFirst = true;\r
-        for (Iterator it = c.iterator(); it.hasNext(); ) {\r
-            if (!isFirst) result.append(separator);\r
-            else isFirst = false;\r
-            result.append(it.next().toString());\r
-        }\r
-        return result.toString();\r
-    }\r
-    */\r
-        \r
-    /**\r
-     * The guts is in this subclass. It sucks in all the data from the zones,\r
-     * and analyses it. It constructs some mappings for the unique ids,\r
-     * etc.<br>\r
-     * The main tricky bit is that for performance it pre-analyses all zones\r
-     * for inflections points; the points in time where the offset changes.\r
-     * The zones can then be sorted by those points, which allows us to\r
-     * avoid expensive comparisons.\r
-     * @author Davis\r
-     */\r
-    static class Zone implements Comparable {\r
-        // class fields\r
-        static private final BagFormatter bf = new BagFormatter().setSeparator(", ");\r
-        static private final DateFormat df = DateFormat.getDateInstance(DateFormat.LONG, Locale.US);\r
-        static private final NumberFormat nf = NumberFormat.getInstance(Locale.US);\r
-        static private final long HOUR = 1000*60*60;\r
-        static private final double DHOUR = HOUR;\r
-        static private final long DAY = 24*HOUR;\r
-        static private final long GROSS_PERIOD = 30*DAY;\r
-        static private final long EPSILON = HOUR/4;\r
-        static private final int currentYear = new GregorianCalendar().get(Calendar.YEAR);\r
-        static private final long endDate = getDate((currentYear+1),0,1).getTime();\r
-        static private final long endDate2 = getDate((currentYear+1),6,1).getTime();\r
-        static private final long recentLimit = getDate((currentYear-1),6,1).getTime();\r
-        static private final long startDate = getDate(1905,0,1).getTime();\r
-        \r
-        static private final Map idToZone = new HashMap();\r
-        static private final Set zoneSet = new TreeSet();\r
-        static private final Set uniqueZoneSet = new TreeSet();\r
-        static private final Map idToRealAliases = new HashMap();\r
-\r
-        // build everything once.\r
-        static {\r
-            String [] foo = TimeZone.getAvailableIDs();\r
-            for (int i = 0; i < foo.length; ++i) {\r
-                zoneSet.add(Zone.make(foo[i]));\r
-            }\r
-            Zone last = null;\r
-            Zone.Seconds diffDate = new Zone.Seconds();\r
-            String lastUnique = "";      \r
-            for (Iterator it = Zone.getZoneSet().iterator(); it.hasNext();) {\r
-                Zone testZone = (Zone)it.next();\r
-                if (last == null) {\r
-                    uniqueZoneSet.add(testZone);\r
-                    lastUnique = testZone.id;\r
-                } else {\r
-                    int diff = testZone.findOffsetOrdering(last, diffDate);\r
-                    if (diff != 0) {\r
-                        uniqueZoneSet.add(testZone);\r
-                        lastUnique = testZone.id;\r
-                    } else {\r
-                        Set aliases = (Set)idToRealAliases.get(lastUnique);\r
-                        if (aliases == null) {\r
-                            aliases = new TreeSet();\r
-                            idToRealAliases.put(lastUnique, aliases);\r
-                        }\r
-                        aliases.add(testZone.id);\r
-                    }\r
-                }\r
-                last = testZone;\r
-            }\r
-        }\r
-        \r
-        static public Set getZoneSet() {\r
-            return zoneSet;\r
-        }\r
-        \r
-        public static Set getUniqueZoneSet() {\r
-            return uniqueZoneSet;\r
-        }\r
-\r
-        static public Zone make(String id) {\r
-            Zone result = (Zone)idToZone.get(id);\r
-            if (result != null) return result;\r
-            result = new Zone(id);\r
-            idToZone.put(id, result);\r
-            return result;\r
-        }\r
-        \r
-        static public String formatHours(int hours) {\r
-            return nf.format(hours/DHOUR);\r
-        }\r
-        \r
-        // utility class for date return, because Date is clunky.\r
-        public static class Seconds {\r
-            public long seconds = Long.MIN_VALUE;\r
-            public String toString() {\r
-                if (seconds == Long.MIN_VALUE) return "n/a";\r
-                return df.format(new Date(seconds));\r
-            }\r
-        }\r
-        \r
-        // instance fields\r
-        // we keep min/max offsets not only over all time (that we care about)\r
-        // but also separate ones for recent years.\r
-        private String id;\r
-        private TimeZone zone;\r
-        // computed below\r
-        private int minOffset;\r
-        private int maxOffset;\r
-        private int minRecentOffset;\r
-        private int maxRecentOffset;\r
-        private List inflectionPoints = new ArrayList();\r
-        private Set purportedAliases = new TreeSet();\r
-    \r
-        private Zone(String id) { // for interal use only; use make instead!\r
-            zone = TimeZone.getTimeZone(id);\r
-            this.id = id;\r
-            \r
-            // get aliases\r
-            int equivCount = TimeZone.countEquivalentIDs(id);\r
-            for (int j = 0; j < equivCount; ++j) {\r
-                String altID = TimeZone.getEquivalentID(id, j);\r
-                if (altID.equals(id)) continue;\r
-                purportedAliases.add(altID);\r
-            }\r
-\r
-            // find inflexion points; times where the offset changed\r
-            long lastDate = endDate;\r
-            if (zone.getOffset(lastDate) < zone.getOffset(endDate2)) lastDate = endDate2;\r
-            maxRecentOffset = minRecentOffset = minOffset = maxOffset = zone.getOffset(lastDate);\r
-\r
-            inflectionPoints.add(new Long(lastDate));\r
-            int lastOffset = zone.getOffset(endDate);\r
-            long lastInflection = endDate;\r
-            \r
-            // we do a gross search, then narrow in when we find a difference from the last one\r
-            for (long currentDate = endDate; currentDate >= startDate; currentDate -= GROSS_PERIOD) {\r
-                int currentOffset = zone.getOffset(currentDate);\r
-                if (currentOffset != lastOffset) { // Binary Search\r
-                    if (currentOffset < minOffset) minOffset = currentOffset;\r
-                    if (currentOffset > maxOffset) maxOffset = currentOffset;\r
-                    if (lastInflection >= recentLimit) {\r
-                        if (currentOffset < minRecentOffset) minRecentOffset = currentOffset;\r
-                        if (currentOffset > maxRecentOffset) maxRecentOffset = currentOffset;\r
-                    }\r
-                    long low = currentDate;\r
-                    long high = lastDate;\r
-                    while (low - high > EPSILON) {\r
-                        long mid = (high + low)/2;\r
-                        int midOffset = zone.getOffset(mid);\r
-                        if (midOffset == low) {\r
-                            low = mid;\r
-                        } else {\r
-                            high = mid;\r
-                        }\r
-                    }\r
-                    inflectionPoints.add(new Long(low));\r
-                    lastInflection = low;\r
-                }\r
-                lastOffset = currentOffset;\r
-            }\r
-            inflectionPoints.add(new Long(startDate)); // just to cap it off for comparisons.\r
-        }\r
-        \r
-        // we assume that places will not convert time zones then back within one day\r
-        // so we go first by half\r
-        public int findOffsetOrdering(Zone other, Seconds dateDiffFound) {\r
-            //System.out.println("-diff: " + id + "\t" + other.id);\r
-            int result = 0;\r
-            long seconds = 0;\r
-            int min = inflectionPoints.size();\r
-            if (other.inflectionPoints.size() < min) min = other.inflectionPoints.size();\r
-            main:\r
-            {\r
-                for (int i = 0; i < min; ++i) {\r
-                    long myIP = ((Long)inflectionPoints.get(i)).longValue();\r
-                    long otherIP = ((Long)other.inflectionPoints.get(i)).longValue();\r
-                    if (myIP > otherIP) { // take lowest, for transitivity (semi)\r
-                        long temp = myIP;\r
-                        myIP = otherIP;\r
-                        otherIP = temp;\r
-                    }\r
-                    result = zone.getOffset(myIP) - other.zone.getOffset(myIP);\r
-                    if (result != 0) {\r
-                        seconds = myIP;\r
-                        break main;\r
-                    } \r
-                    if (myIP == otherIP) continue; // test other if different\r
-                    myIP = otherIP;\r
-                    result = zone.getOffset(myIP) - other.zone.getOffset(myIP);\r
-                    if (result != 0) {\r
-                        seconds = myIP;\r
-                        break main;\r
-                    } \r
-                }\r
-                // if they are equal so far, we don't care about the rest\r
-                result = 0;\r
-                seconds = Long.MIN_VALUE;\r
-                break main;\r
-            }\r
-            //System.out.println("+diff: " + (result/HOUR) + "\t" + dateDiffFound);\r
-            if (dateDiffFound != null) dateDiffFound.seconds = seconds;\r
-            return result;\r
-        }\r
-        \r
-        // internal buffer to avoid creation all the time.\r
-        private Seconds diffDateReturn = new Seconds();\r
-        \r
-        public int compareTo(Object o) {\r
-            Zone other = (Zone)o;\r
-            // first order by max and min offsets\r
-            // min will usually correspond to standard time, max to daylight\r
-            // unless there have been historical shifts\r
-            if (minRecentOffset < other.minRecentOffset) return -1;\r
-            if (minRecentOffset > other.minRecentOffset) return 1;\r
-            if (maxRecentOffset < other.maxRecentOffset) return -1;\r
-            if (maxRecentOffset > other.maxRecentOffset) return 1;\r
-            // now check that all offsets are the same over history\r
-            int diffDate = findOffsetOrdering(other, diffDateReturn);\r
-            if (diffDate != 0) return diffDate;\r
-            // choose longer name first!!\r
-            if (id.length() != other.id.length()) {\r
-                if (id.length() < other.id.length()) return 1;\r
-                return -1;\r
-            }\r
-            return id.compareTo(other.id);\r
-        }\r
-        \r
-        public Set getPurportedAliases() {\r
-            return new TreeSet(purportedAliases); // clone for safety\r
-        }\r
-        \r
-        public boolean isPurportedAlias(String zoneID) {\r
-            return purportedAliases.contains(zoneID);\r
-        }\r
-        \r
-        public boolean isRealAlias(Zone z) {\r
-            return purportedAliases.contains(z.id);\r
-        }\r
-        \r
-        public String getPurportedAliasesAsString() {\r
-            Set s = getPurportedAliases();\r
-            if (s.size() == 0) return "";\r
-            return " " + bf.join(s);\r
-        }\r
-        \r
-        public String getRealAliasesAsString() {\r
-            Set s = (Set)idToRealAliases.get(id);\r
-            if (s == null) return "";\r
-            return " *" + bf.join(s);\r
-        }\r
-        \r
-        public String getCity() {\r
-            int pos = id.lastIndexOf(('/'));\r
-            String city = id.substring(pos+1);\r
-            return city.replace('_',' ');\r
-        }\r
-\r
-        public String toString() {\r
-            return toString(-1);\r
-        }\r
-        \r
-        /**\r
-         * Where count > 0, returns string that is set up for translation\r
-         */\r
-        public String toString(int count) {\r
-            String city = getCity();\r
-            String hours = formatHours(minRecentOffset)\r
-                + (minRecentOffset != maxRecentOffset \r
-                    ? "," + formatHours(maxRecentOffset) \r
-                    : "");\r
-            if (count < 0) {\r
-                return id + getPurportedAliasesAsString() + " (" + hours + ")";\r
-            } \r
-            // for getting template for translation\r
-            return "\t{\t\"" + id + "\"\t// [" + count + "] " + hours \r
-                + getRealAliasesAsString() + "\r\n"\r
-                + "\t\t// translate the following!!\r\n"\r
-                + (minRecentOffset != maxRecentOffset\r
-                    ? "\t\t\"" + city + " Standard Time\"\r\n"\r
-                    + "\t\t\"" + city + "-ST\"\r\n"\r
-                    + "\t\t\"" + city + " Daylight Time\"\r\n"\r
-                    + "\t\t\"" + city + "-DT\"\r\n"\r
-                    : "\t\t\"\"\r\n"\r
-                    + "\t\t\"\"\r\n"\r
-                    + "\t\t\"\"\r\n"\r
-                    + "\t\t\"\"\r\n")\r
-                + "\t\t\"" + city + " Time\"\r\n"\r
-                + "\t\t\"" + city + "-T\"\r\n"\r
-                + "\t\t\"" + city + "\"\r\n"\r
-                + "\t}";\r
-        }\r
-    }\r
-}\r
-\r
-//#endif\r
+//##header J2SE15
+//#if defined(FOUNDATION10) || defined(J2SE13)
+//#else
+/*
+ *******************************************************************************
+ * Copyright (C) 2002-2009, International Business Machines Corporation and    *
+ * others. All Rights Reserved.                                                *
+ *******************************************************************************
+*/
+package com.ibm.icu.dev.test.timezone;
+
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.Calendar;
+import java.util.GregorianCalendar;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeSet;
+import java.util.Iterator;
+
+import java.text.DateFormat;
+import java.text.NumberFormat;
+
+import com.ibm.icu.dev.test.*;
+import com.ibm.icu.dev.test.util.BagFormatter;
+import com.ibm.icu.util.TimeZone;
+
+
+/**
+ * Class for testing TimeZones for consistency
+ * @author Davis
+ * 
+ */
+public class TimeZoneAliasTest extends TestFmwk {
+    
+    public static void main(String[] args) throws Exception {
+        new TimeZoneAliasTest().run(args);
+    }
+    
+    /**
+     * There are two things to check aliases for:<br>
+     * 1. the alias set must be uniform: if a isAlias b, then aliasSet(a) == aliasSet(b)<br>
+     * 2. all aliases must have the same offsets
+      */
+    public void TestAliases() {
+        Zone.Seconds seconds = new Zone.Seconds();
+        for (Iterator it = Zone.getZoneSet().iterator(); it.hasNext(); ) {
+            Zone zone = (Zone)it.next();
+            String id = zone.id;
+            if (id.indexOf('/') < 0 && (id.endsWith("ST") || id.endsWith("DT"))) {
+                if (zone.minRecentOffset != zone.maxRecentOffset) {
+                    errln(
+                        "Standard or Daylight Time not constant: " + id 
+                        + ": " + Zone.formatHours(zone.minRecentOffset)
+                        + " != " + Zone.formatHours(zone.maxRecentOffset));
+                }
+            }
+            Set aliases = zone.getPurportedAliases();
+            Set aliasesSet = new TreeSet(aliases);
+            aliasesSet.add(id); // for comparison
+            Iterator aliasIterator = aliases.iterator();
+            while (aliasIterator.hasNext()) {
+                String otherId = (String)aliasIterator.next();
+                Zone otherZone = Zone.make(otherId);
+                Set otherAliases = otherZone.getPurportedAliases();
+                otherAliases.add(otherId); // for comparison
+                if (!aliasesSet.equals(otherAliases)) {
+                    errln(
+                        "Aliases Unsymmetric: "
+                        + id + " => " + Zone.bf.join(aliasesSet)
+                        + "; " 
+                        + otherId + " => " + Zone.bf.join(otherAliases));
+                }
+                if (zone.findOffsetOrdering(otherZone, seconds) != 0) {
+                    errln("Aliases differ: " + id + ", " + otherId
+                         + " differ at " + seconds);
+                }
+            }
+        }
+    }
+    
+    /**
+     * We check to see that every timezone that is not an alias is actually different!
+     */
+    public void TestDifferences() {
+        Zone last = null;
+        Zone.Seconds diffDate = new Zone.Seconds();        
+        for (Iterator it = Zone.getZoneSet().iterator(); it.hasNext();) {
+            Zone testZone = (Zone)it.next();
+            if (last != null) {
+                String common = testZone + "\tvs " + last + ":\t";
+                int diff = testZone.findOffsetOrdering(last, diffDate);
+                if (diff != 0) {
+                    logln("\t" + common + "difference at: " + diffDate 
+                        + ", " + Zone.formatHours(diff) + "hr");
+                } else if (testZone.isRealAlias(last)) {
+                    logln("\t" + common + "alias, no difference");
+                } else {
+                    errln(common + "NOT ALIAS BUT NO DIFFERENCE!");
+                }
+            }
+            last = testZone;
+        }
+    }
+    
+    /**
+     * Utility for printing out zones to be translated.
+     */
+    public static void TestGenerateZones() {
+        int count = 1;
+        for (Iterator it = Zone.getUniqueZoneSet().iterator(); it.hasNext();) {
+            Zone zone = (Zone)it.next();
+            System.out.println(zone.toString(count++));
+        }
+    }
+    
+    /** Utility; ought to be someplace common
+     */
+    /*
+    static String join(Collection c, String separator) {
+        StringBuffer result = new StringBuffer();
+        boolean isFirst = true;
+        for (Iterator it = c.iterator(); it.hasNext(); ) {
+            if (!isFirst) result.append(separator);
+            else isFirst = false;
+            result.append(it.next().toString());
+        }
+        return result.toString();
+    }
+    */
+        
+    /**
+     * The guts is in this subclass. It sucks in all the data from the zones,
+     * and analyses it. It constructs some mappings for the unique ids,
+     * etc.<br>
+     * The main tricky bit is that for performance it pre-analyses all zones
+     * for inflections points; the points in time where the offset changes.
+     * The zones can then be sorted by those points, which allows us to
+     * avoid expensive comparisons.
+     * @author Davis
+     */
+    static class Zone implements Comparable {
+        // class fields
+        static private final BagFormatter bf = new BagFormatter().setSeparator(", ");
+        static private final DateFormat df = DateFormat.getDateInstance(DateFormat.LONG, Locale.US);
+        static private final NumberFormat nf = NumberFormat.getInstance(Locale.US);
+        static private final long HOUR = 1000*60*60;
+        static private final double DHOUR = HOUR;
+        static private final long DAY = 24*HOUR;
+        static private final long GROSS_PERIOD = 30*DAY;
+        static private final long EPSILON = HOUR/4;
+        static private final int currentYear = new GregorianCalendar().get(Calendar.YEAR);
+        static private final long endDate = getDate((currentYear+1),0,1).getTime();
+        static private final long endDate2 = getDate((currentYear+1),6,1).getTime();
+        static private final long recentLimit = getDate((currentYear-1),6,1).getTime();
+        static private final long startDate = getDate(1905,0,1).getTime();
+        
+        static private final Map idToZone = new HashMap();
+        static private final Set zoneSet = new TreeSet();
+        static private final Set uniqueZoneSet = new TreeSet();
+        static private final Map idToRealAliases = new HashMap();
+
+        // build everything once.
+        static {
+            String [] foo = TimeZone.getAvailableIDs();
+            for (int i = 0; i < foo.length; ++i) {
+                zoneSet.add(Zone.make(foo[i]));
+            }
+            Zone last = null;
+            Zone.Seconds diffDate = new Zone.Seconds();
+            String lastUnique = "";      
+            for (Iterator it = Zone.getZoneSet().iterator(); it.hasNext();) {
+                Zone testZone = (Zone)it.next();
+                if (last == null) {
+                    uniqueZoneSet.add(testZone);
+                    lastUnique = testZone.id;
+                } else {
+                    int diff = testZone.findOffsetOrdering(last, diffDate);
+                    if (diff != 0) {
+                        uniqueZoneSet.add(testZone);
+                        lastUnique = testZone.id;
+                    } else {
+                        Set aliases = (Set)idToRealAliases.get(lastUnique);
+                        if (aliases == null) {
+                            aliases = new TreeSet();
+                            idToRealAliases.put(lastUnique, aliases);
+                        }
+                        aliases.add(testZone.id);
+                    }
+                }
+                last = testZone;
+            }
+        }
+        
+        static public Set getZoneSet() {
+            return zoneSet;
+        }
+        
+        public static Set getUniqueZoneSet() {
+            return uniqueZoneSet;
+        }
+
+        static public Zone make(String id) {
+            Zone result = (Zone)idToZone.get(id);
+            if (result != null) return result;
+            result = new Zone(id);
+            idToZone.put(id, result);
+            return result;
+        }
+        
+        static public String formatHours(int hours) {
+            return nf.format(hours/DHOUR);
+        }
+        
+        // utility class for date return, because Date is clunky.
+        public static class Seconds {
+            public long seconds = Long.MIN_VALUE;
+            public String toString() {
+                if (seconds == Long.MIN_VALUE) return "n/a";
+                return df.format(new Date(seconds));
+            }
+        }
+        
+        // instance fields
+        // we keep min/max offsets not only over all time (that we care about)
+        // but also separate ones for recent years.
+        private String id;
+        private TimeZone zone;
+        // computed below
+        private int minOffset;
+        private int maxOffset;
+        private int minRecentOffset;
+        private int maxRecentOffset;
+        private List inflectionPoints = new ArrayList();
+        private Set purportedAliases = new TreeSet();
+    
+        private Zone(String id) { // for interal use only; use make instead!
+            zone = TimeZone.getTimeZone(id);
+            this.id = id;
+            
+            // get aliases
+            int equivCount = TimeZone.countEquivalentIDs(id);
+            for (int j = 0; j < equivCount; ++j) {
+                String altID = TimeZone.getEquivalentID(id, j);
+                if (altID.equals(id)) continue;
+                purportedAliases.add(altID);
+            }
+
+            // find inflexion points; times where the offset changed
+            long lastDate = endDate;
+            if (zone.getOffset(lastDate) < zone.getOffset(endDate2)) lastDate = endDate2;
+            maxRecentOffset = minRecentOffset = minOffset = maxOffset = zone.getOffset(lastDate);
+
+            inflectionPoints.add(new Long(lastDate));
+            int lastOffset = zone.getOffset(endDate);
+            long lastInflection = endDate;
+            
+            // we do a gross search, then narrow in when we find a difference from the last one
+            for (long currentDate = endDate; currentDate >= startDate; currentDate -= GROSS_PERIOD) {
+                int currentOffset = zone.getOffset(currentDate);
+                if (currentOffset != lastOffset) { // Binary Search
+                    if (currentOffset < minOffset) minOffset = currentOffset;
+                    if (currentOffset > maxOffset) maxOffset = currentOffset;
+                    if (lastInflection >= recentLimit) {
+                        if (currentOffset < minRecentOffset) minRecentOffset = currentOffset;
+                        if (currentOffset > maxRecentOffset) maxRecentOffset = currentOffset;
+                    }
+                    long low = currentDate;
+                    long high = lastDate;
+                    while (low - high > EPSILON) {
+                        long mid = (high + low)/2;
+                        int midOffset = zone.getOffset(mid);
+                        if (midOffset == low) {
+                            low = mid;
+                        } else {
+                            high = mid;
+                        }
+                    }
+                    inflectionPoints.add(new Long(low));
+                    lastInflection = low;
+                }
+                lastOffset = currentOffset;
+            }
+            inflectionPoints.add(new Long(startDate)); // just to cap it off for comparisons.
+        }
+        
+        // we assume that places will not convert time zones then back within one day
+        // so we go first by half
+        public int findOffsetOrdering(Zone other, Seconds dateDiffFound) {
+            //System.out.println("-diff: " + id + "\t" + other.id);
+            int result = 0;
+            long seconds = 0;
+            int min = inflectionPoints.size();
+            if (other.inflectionPoints.size() < min) min = other.inflectionPoints.size();
+            main:
+            {
+                for (int i = 0; i < min; ++i) {
+                    long myIP = ((Long)inflectionPoints.get(i)).longValue();
+                    long otherIP = ((Long)other.inflectionPoints.get(i)).longValue();
+                    if (myIP > otherIP) { // take lowest, for transitivity (semi)
+                        long temp = myIP;
+                        myIP = otherIP;
+                        otherIP = temp;
+                    }
+                    result = zone.getOffset(myIP) - other.zone.getOffset(myIP);
+                    if (result != 0) {
+                        seconds = myIP;
+                        break main;
+                    } 
+                    if (myIP == otherIP) continue; // test other if different
+                    myIP = otherIP;
+                    result = zone.getOffset(myIP) - other.zone.getOffset(myIP);
+                    if (result != 0) {
+                        seconds = myIP;
+                        break main;
+                    } 
+                }
+                // if they are equal so far, we don't care about the rest
+                result = 0;
+                seconds = Long.MIN_VALUE;
+                break main;
+            }
+            //System.out.println("+diff: " + (result/HOUR) + "\t" + dateDiffFound);
+            if (dateDiffFound != null) dateDiffFound.seconds = seconds;
+            return result;
+        }
+        
+        // internal buffer to avoid creation all the time.
+        private Seconds diffDateReturn = new Seconds();
+        
+        public int compareTo(Object o) {
+            Zone other = (Zone)o;
+            // first order by max and min offsets
+            // min will usually correspond to standard time, max to daylight
+            // unless there have been historical shifts
+            if (minRecentOffset < other.minRecentOffset) return -1;
+            if (minRecentOffset > other.minRecentOffset) return 1;
+            if (maxRecentOffset < other.maxRecentOffset) return -1;
+            if (maxRecentOffset > other.maxRecentOffset) return 1;
+            // now check that all offsets are the same over history
+            int diffDate = findOffsetOrdering(other, diffDateReturn);
+            if (diffDate != 0) return diffDate;
+            // choose longer name first!!
+            if (id.length() != other.id.length()) {
+                if (id.length() < other.id.length()) return 1;
+                return -1;
+            }
+            return id.compareTo(other.id);
+        }
+        
+        public Set getPurportedAliases() {
+            return new TreeSet(purportedAliases); // clone for safety
+        }
+        
+        public boolean isPurportedAlias(String zoneID) {
+            return purportedAliases.contains(zoneID);
+        }
+        
+        public boolean isRealAlias(Zone z) {
+            return purportedAliases.contains(z.id);
+        }
+        
+        public String getPurportedAliasesAsString() {
+            Set s = getPurportedAliases();
+            if (s.size() == 0) return "";
+            return " " + bf.join(s);
+        }
+        
+        public String getRealAliasesAsString() {
+            Set s = (Set)idToRealAliases.get(id);
+            if (s == null) return "";
+            return " *" + bf.join(s);
+        }
+        
+        public String getCity() {
+            int pos = id.lastIndexOf(('/'));
+            String city = id.substring(pos+1);
+            return city.replace('_',' ');
+        }
+
+        public String toString() {
+            return toString(-1);
+        }
+        
+        /**
+         * Where count > 0, returns string that is set up for translation
+         */
+        public String toString(int count) {
+            String city = getCity();
+            String hours = formatHours(minRecentOffset)
+                + (minRecentOffset != maxRecentOffset 
+                    ? "," + formatHours(maxRecentOffset) 
+                    : "");
+            if (count < 0) {
+                return id + getPurportedAliasesAsString() + " (" + hours + ")";
+            } 
+            // for getting template for translation
+            return "\t{\t\"" + id + "\"\t// [" + count + "] " + hours 
+                + getRealAliasesAsString() + "\r\n"
+                + "\t\t// translate the following!!\r\n"
+                + (minRecentOffset != maxRecentOffset
+                    ? "\t\t\"" + city + " Standard Time\"\r\n"
+                    + "\t\t\"" + city + "-ST\"\r\n"
+                    + "\t\t\"" + city + " Daylight Time\"\r\n"
+                    + "\t\t\"" + city + "-DT\"\r\n"
+                    : "\t\t\"\"\r\n"
+                    + "\t\t\"\"\r\n"
+                    + "\t\t\"\"\r\n"
+                    + "\t\t\"\"\r\n")
+                + "\t\t\"" + city + " Time\"\r\n"
+                + "\t\t\"" + city + "-T\"\r\n"
+                + "\t\t\"" + city + "\"\r\n"
+                + "\t}";
+        }
+    }
+}
+
+//#endif