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