]> gitweb.fperrin.net Git - Dictionary.git/blob - jars/icu4j-4_4_2-src/main/tests/core/src/com/ibm/icu/dev/test/timezone/TimeZoneAliasTest.java
go
[Dictionary.git] / jars / icu4j-4_4_2-src / main / tests / core / src / com / ibm / icu / dev / test / timezone / TimeZoneAliasTest.java
1 /*\r
2  *******************************************************************************\r
3  * Copyright (C) 2002-2010, International Business Machines Corporation and    *\r
4  * others. All Rights Reserved.                                                *\r
5  *******************************************************************************\r
6 */\r
7 package com.ibm.icu.dev.test.timezone;\r
8 \r
9 import java.text.DateFormat;\r
10 import java.text.NumberFormat;\r
11 import java.util.ArrayList;\r
12 import java.util.Calendar;\r
13 import java.util.Collection;\r
14 import java.util.Date;\r
15 import java.util.GregorianCalendar;\r
16 import java.util.HashMap;\r
17 import java.util.Iterator;\r
18 import java.util.List;\r
19 import java.util.Locale;\r
20 import java.util.Map;\r
21 import java.util.Set;\r
22 import java.util.TreeSet;\r
23 \r
24 import com.ibm.icu.dev.test.TestFmwk;\r
25 import com.ibm.icu.util.TimeZone;\r
26 \r
27 \r
28 /**\r
29  * Class for testing TimeZones for consistency\r
30  * @author Davis\r
31  * \r
32  */\r
33 public class TimeZoneAliasTest extends TestFmwk {\r
34     \r
35     public static void main(String[] args) throws Exception {\r
36         new TimeZoneAliasTest().run(args);\r
37     }\r
38     \r
39     /**\r
40      * There are two things to check aliases for:<br>\r
41      * 1. the alias set must be uniform: if a isAlias b, then aliasSet(a) == aliasSet(b)<br>\r
42      * 2. all aliases must have the same offsets\r
43       */\r
44     public void TestAliases() {\r
45         Zone.Seconds seconds = new Zone.Seconds();\r
46         for (Iterator it = Zone.getZoneSet().iterator(); it.hasNext(); ) {\r
47             Zone zone = (Zone)it.next();\r
48             String id = zone.id;\r
49             if (id.indexOf('/') < 0 && (id.endsWith("ST") || id.endsWith("DT"))) {\r
50                 if (zone.minRecentOffset != zone.maxRecentOffset) {\r
51                     errln(\r
52                         "Standard or Daylight Time not constant: " + id \r
53                         + ": " + Zone.formatHours(zone.minRecentOffset)\r
54                         + " != " + Zone.formatHours(zone.maxRecentOffset));\r
55                 }\r
56             }\r
57             Set aliases = zone.getPurportedAliases();\r
58             Set aliasesSet = new TreeSet(aliases);\r
59             aliasesSet.add(id); // for comparison\r
60             Iterator aliasIterator = aliases.iterator();\r
61             while (aliasIterator.hasNext()) {\r
62                 String otherId = (String)aliasIterator.next();\r
63                 Zone otherZone = Zone.make(otherId);\r
64                 Set otherAliases = otherZone.getPurportedAliases();\r
65                 otherAliases.add(otherId); // for comparison\r
66                 if (!aliasesSet.equals(otherAliases)) {\r
67                     errln(\r
68                         "Aliases Unsymmetric: "\r
69                         + id + " => " + Zone.bf.join(aliasesSet)\r
70                         + "; " \r
71                         + otherId + " => " + Zone.bf.join(otherAliases));\r
72                 }\r
73                 if (zone.findOffsetOrdering(otherZone, seconds) != 0) {\r
74                     errln("Aliases differ: " + id + ", " + otherId\r
75                          + " differ at " + seconds);\r
76                 }\r
77             }\r
78         }\r
79     }\r
80     \r
81     /**\r
82      * We check to see that every timezone that is not an alias is actually different!\r
83      */\r
84     public void TestDifferences() {\r
85         Zone last = null;\r
86         Zone.Seconds diffDate = new Zone.Seconds();        \r
87         for (Iterator it = Zone.getZoneSet().iterator(); it.hasNext();) {\r
88             Zone testZone = (Zone)it.next();\r
89             if (last != null) {\r
90                 String common = testZone + "\tvs " + last + ":\t";\r
91                 int diff = testZone.findOffsetOrdering(last, diffDate);\r
92                 if (diff != 0) {\r
93                     logln("\t" + common + "difference at: " + diffDate \r
94                         + ", " + Zone.formatHours(diff) + "hr");\r
95                 } else if (testZone.isRealAlias(last)) {\r
96                     logln("\t" + common + "alias, no difference");\r
97                 } else {\r
98                     errln(common + "NOT ALIAS BUT NO DIFFERENCE!");\r
99                 }\r
100             }\r
101             last = testZone;\r
102         }\r
103     }\r
104     \r
105     /**\r
106      * Utility for printing out zones to be translated.\r
107      */\r
108     public static void TestGenerateZones() {\r
109         int count = 1;\r
110         for (Iterator it = Zone.getUniqueZoneSet().iterator(); it.hasNext();) {\r
111             Zone zone = (Zone)it.next();\r
112             System.out.println(zone.toString(count++));\r
113         }\r
114     }\r
115     \r
116     /** Utility; ought to be someplace common\r
117      */\r
118     // remove dependency on bagformatter for now\r
119     static class CollectionJoiner {\r
120         private String separator;\r
121         CollectionJoiner(String separator) {\r
122             this.separator = separator;\r
123         }\r
124         String join(Collection c) {\r
125             StringBuffer result = new StringBuffer();\r
126             boolean isFirst = true;\r
127             for (Iterator it = c.iterator(); it.hasNext(); ) {\r
128                 if (!isFirst) result.append(separator);\r
129                 else isFirst = false;\r
130                 result.append(it.next().toString());\r
131             }\r
132             return result.toString();\r
133         }\r
134     }\r
135 \r
136         \r
137     /**\r
138      * The guts is in this subclass. It sucks in all the data from the zones,\r
139      * and analyses it. It constructs some mappings for the unique ids,\r
140      * etc.<br>\r
141      * The main tricky bit is that for performance it pre-analyses all zones\r
142      * for inflections points; the points in time where the offset changes.\r
143      * The zones can then be sorted by those points, which allows us to\r
144      * avoid expensive comparisons.\r
145      * @author Davis\r
146      */\r
147     static class Zone implements Comparable {\r
148         // class fields\r
149       // static private final BagFormatter bf = new BagFormatter().setSeparator(", ");\r
150         private static final CollectionJoiner bf = new CollectionJoiner(", ");\r
151         static private final DateFormat df = DateFormat.getDateInstance(DateFormat.LONG, Locale.US);\r
152         static private final NumberFormat nf = NumberFormat.getInstance(Locale.US);\r
153         static private final long HOUR = 1000*60*60;\r
154         static private final double DHOUR = HOUR;\r
155         static private final long DAY = 24*HOUR;\r
156         static private final long GROSS_PERIOD = 30*DAY;\r
157         static private final long EPSILON = HOUR/4;\r
158         static private final int currentYear = new GregorianCalendar().get(Calendar.YEAR);\r
159         static private final long endDate = getDate((currentYear+1),0,1).getTime();\r
160         static private final long endDate2 = getDate((currentYear+1),6,1).getTime();\r
161         static private final long recentLimit = getDate((currentYear-1),6,1).getTime();\r
162         static private final long startDate = getDate(1905,0,1).getTime();\r
163         \r
164         static private final Map idToZone = new HashMap();\r
165         static private final Set zoneSet = new TreeSet();\r
166         static private final Set uniqueZoneSet = new TreeSet();\r
167         static private final Map idToRealAliases = new HashMap();\r
168 \r
169         // build everything once.\r
170         static {\r
171             String [] foo = TimeZone.getAvailableIDs();\r
172             for (int i = 0; i < foo.length; ++i) {\r
173                 zoneSet.add(Zone.make(foo[i]));\r
174             }\r
175             Zone last = null;\r
176             Zone.Seconds diffDate = new Zone.Seconds();\r
177             String lastUnique = "";      \r
178             for (Iterator it = Zone.getZoneSet().iterator(); it.hasNext();) {\r
179                 Zone testZone = (Zone)it.next();\r
180                 if (last == null) {\r
181                     uniqueZoneSet.add(testZone);\r
182                     lastUnique = testZone.id;\r
183                 } else {\r
184                     int diff = testZone.findOffsetOrdering(last, diffDate);\r
185                     if (diff != 0) {\r
186                         uniqueZoneSet.add(testZone);\r
187                         lastUnique = testZone.id;\r
188                     } else {\r
189                         Set aliases = (Set)idToRealAliases.get(lastUnique);\r
190                         if (aliases == null) {\r
191                             aliases = new TreeSet();\r
192                             idToRealAliases.put(lastUnique, aliases);\r
193                         }\r
194                         aliases.add(testZone.id);\r
195                     }\r
196                 }\r
197                 last = testZone;\r
198             }\r
199         }\r
200         \r
201         static public Set getZoneSet() {\r
202             return zoneSet;\r
203         }\r
204         \r
205         public static Set getUniqueZoneSet() {\r
206             return uniqueZoneSet;\r
207         }\r
208 \r
209         static public Zone make(String id) {\r
210             Zone result = (Zone)idToZone.get(id);\r
211             if (result != null) return result;\r
212             result = new Zone(id);\r
213             idToZone.put(id, result);\r
214             return result;\r
215         }\r
216         \r
217         static public String formatHours(int hours) {\r
218             return nf.format(hours/DHOUR);\r
219         }\r
220         \r
221         // utility class for date return, because Date is clunky.\r
222         public static class Seconds {\r
223             public long seconds = Long.MIN_VALUE;\r
224             public String toString() {\r
225                 if (seconds == Long.MIN_VALUE) return "n/a";\r
226                 return df.format(new Date(seconds));\r
227             }\r
228         }\r
229         \r
230         // instance fields\r
231         // we keep min/max offsets not only over all time (that we care about)\r
232         // but also separate ones for recent years.\r
233         private String id;\r
234         private TimeZone zone;\r
235         // computed below\r
236         private int minOffset;\r
237         private int maxOffset;\r
238         private int minRecentOffset;\r
239         private int maxRecentOffset;\r
240         private List inflectionPoints = new ArrayList();\r
241         private Set purportedAliases = new TreeSet();\r
242     \r
243         private Zone(String id) { // for interal use only; use make instead!\r
244             zone = TimeZone.getTimeZone(id);\r
245             this.id = id;\r
246             \r
247             // get aliases\r
248             int equivCount = TimeZone.countEquivalentIDs(id);\r
249             for (int j = 0; j < equivCount; ++j) {\r
250                 String altID = TimeZone.getEquivalentID(id, j);\r
251                 if (altID.equals(id)) continue;\r
252                 purportedAliases.add(altID);\r
253             }\r
254 \r
255             // find inflexion points; times where the offset changed\r
256             long lastDate = endDate;\r
257             if (zone.getOffset(lastDate) < zone.getOffset(endDate2)) lastDate = endDate2;\r
258             maxRecentOffset = minRecentOffset = minOffset = maxOffset = zone.getOffset(lastDate);\r
259 \r
260             inflectionPoints.add(new Long(lastDate));\r
261             int lastOffset = zone.getOffset(endDate);\r
262             long lastInflection = endDate;\r
263             \r
264             // we do a gross search, then narrow in when we find a difference from the last one\r
265             for (long currentDate = endDate; currentDate >= startDate; currentDate -= GROSS_PERIOD) {\r
266                 int currentOffset = zone.getOffset(currentDate);\r
267                 if (currentOffset != lastOffset) { // Binary Search\r
268                     if (currentOffset < minOffset) minOffset = currentOffset;\r
269                     if (currentOffset > maxOffset) maxOffset = currentOffset;\r
270                     if (lastInflection >= recentLimit) {\r
271                         if (currentOffset < minRecentOffset) minRecentOffset = currentOffset;\r
272                         if (currentOffset > maxRecentOffset) maxRecentOffset = currentOffset;\r
273                     }\r
274                     long low = currentDate;\r
275                     long high = lastDate;\r
276                     while (low - high > EPSILON) {\r
277                         long mid = (high + low)/2;\r
278                         int midOffset = zone.getOffset(mid);\r
279                         if (midOffset == low) {\r
280                             low = mid;\r
281                         } else {\r
282                             high = mid;\r
283                         }\r
284                     }\r
285                     inflectionPoints.add(new Long(low));\r
286                     lastInflection = low;\r
287                 }\r
288                 lastOffset = currentOffset;\r
289             }\r
290             inflectionPoints.add(new Long(startDate)); // just to cap it off for comparisons.\r
291         }\r
292         \r
293         // we assume that places will not convert time zones then back within one day\r
294         // so we go first by half\r
295         public int findOffsetOrdering(Zone other, Seconds dateDiffFound) {\r
296             //System.out.println("-diff: " + id + "\t" + other.id);\r
297             int result = 0;\r
298             long seconds = 0;\r
299             int min = inflectionPoints.size();\r
300             if (other.inflectionPoints.size() < min) min = other.inflectionPoints.size();\r
301             main:\r
302             {\r
303                 for (int i = 0; i < min; ++i) {\r
304                     long myIP = ((Long)inflectionPoints.get(i)).longValue();\r
305                     long otherIP = ((Long)other.inflectionPoints.get(i)).longValue();\r
306                     if (myIP > otherIP) { // take lowest, for transitivity (semi)\r
307                         long temp = myIP;\r
308                         myIP = otherIP;\r
309                         otherIP = temp;\r
310                     }\r
311                     result = zone.getOffset(myIP) - other.zone.getOffset(myIP);\r
312                     if (result != 0) {\r
313                         seconds = myIP;\r
314                         break main;\r
315                     } \r
316                     if (myIP == otherIP) continue; // test other if different\r
317                     myIP = otherIP;\r
318                     result = zone.getOffset(myIP) - other.zone.getOffset(myIP);\r
319                     if (result != 0) {\r
320                         seconds = myIP;\r
321                         break main;\r
322                     } \r
323                 }\r
324                 // if they are equal so far, we don't care about the rest\r
325                 result = 0;\r
326                 seconds = Long.MIN_VALUE;\r
327                 break main;\r
328             }\r
329             //System.out.println("+diff: " + (result/HOUR) + "\t" + dateDiffFound);\r
330             if (dateDiffFound != null) dateDiffFound.seconds = seconds;\r
331             return result;\r
332         }\r
333         \r
334         // internal buffer to avoid creation all the time.\r
335         private Seconds diffDateReturn = new Seconds();\r
336         \r
337         public int compareTo(Object o) {\r
338             Zone other = (Zone)o;\r
339             // first order by max and min offsets\r
340             // min will usually correspond to standard time, max to daylight\r
341             // unless there have been historical shifts\r
342             if (minRecentOffset < other.minRecentOffset) return -1;\r
343             if (minRecentOffset > other.minRecentOffset) return 1;\r
344             if (maxRecentOffset < other.maxRecentOffset) return -1;\r
345             if (maxRecentOffset > other.maxRecentOffset) return 1;\r
346             // now check that all offsets are the same over history\r
347             int diffDate = findOffsetOrdering(other, diffDateReturn);\r
348             if (diffDate != 0) return diffDate;\r
349             // choose longer name first!!\r
350             if (id.length() != other.id.length()) {\r
351                 if (id.length() < other.id.length()) return 1;\r
352                 return -1;\r
353             }\r
354             return id.compareTo(other.id);\r
355         }\r
356         \r
357         public Set getPurportedAliases() {\r
358             return new TreeSet(purportedAliases); // clone for safety\r
359         }\r
360         \r
361         public boolean isPurportedAlias(String zoneID) {\r
362             return purportedAliases.contains(zoneID);\r
363         }\r
364         \r
365         public boolean isRealAlias(Zone z) {\r
366             return purportedAliases.contains(z.id);\r
367         }\r
368         \r
369         public String getPurportedAliasesAsString() {\r
370             Set s = getPurportedAliases();\r
371             if (s.size() == 0) return "";\r
372             return " " + bf.join(s);\r
373         }\r
374         \r
375         public String getRealAliasesAsString() {\r
376             Set s = (Set)idToRealAliases.get(id);\r
377             if (s == null) return "";\r
378             return " *" + bf.join(s);\r
379         }\r
380         \r
381         public String getCity() {\r
382             int pos = id.lastIndexOf(('/'));\r
383             String city = id.substring(pos+1);\r
384             return city.replace('_',' ');\r
385         }\r
386 \r
387         public String toString() {\r
388             return toString(-1);\r
389         }\r
390         \r
391         /**\r
392          * Where count > 0, returns string that is set up for translation\r
393          */\r
394         public String toString(int count) {\r
395             String city = getCity();\r
396             String hours = formatHours(minRecentOffset)\r
397                 + (minRecentOffset != maxRecentOffset \r
398                     ? "," + formatHours(maxRecentOffset) \r
399                     : "");\r
400             if (count < 0) {\r
401                 return id + getPurportedAliasesAsString() + " (" + hours + ")";\r
402             } \r
403             // for getting template for translation\r
404             return "\t{\t\"" + id + "\"\t// [" + count + "] " + hours \r
405                 + getRealAliasesAsString() + "\r\n"\r
406                 + "\t\t// translate the following!!\r\n"\r
407                 + (minRecentOffset != maxRecentOffset\r
408                     ? "\t\t\"" + city + " Standard Time\"\r\n"\r
409                     + "\t\t\"" + city + "-ST\"\r\n"\r
410                     + "\t\t\"" + city + " Daylight Time\"\r\n"\r
411                     + "\t\t\"" + city + "-DT\"\r\n"\r
412                     : "\t\t\"\"\r\n"\r
413                     + "\t\t\"\"\r\n"\r
414                     + "\t\t\"\"\r\n"\r
415                     + "\t\t\"\"\r\n")\r
416                 + "\t\t\"" + city + " Time\"\r\n"\r
417                 + "\t\t\"" + city + "-T\"\r\n"\r
418                 + "\t\t\"" + city + "\"\r\n"\r
419                 + "\t}";\r
420         }\r
421     }\r
422 }\r