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