001: //##header
002: /*
003: *******************************************************************************
004: * Copyright (C) 2002-2006, International Business Machines Corporation and *
005: * others. All Rights Reserved. *
006: *******************************************************************************
007: */
008: //#ifndef FOUNDATION
009: package com.ibm.icu.dev.test.timezone;
010:
011: import java.util.ArrayList;
012: import java.util.Date;
013: import java.util.Calendar;
014: import java.util.GregorianCalendar;
015: import java.util.HashMap;
016: import java.util.List;
017: import java.util.Locale;
018: import java.util.Map;
019: import java.util.Set;
020: import java.util.TreeSet;
021: import java.util.Iterator;
022:
023: import java.text.DateFormat;
024: import java.text.NumberFormat;
025:
026: import com.ibm.icu.dev.test.*;
027: import com.ibm.icu.dev.test.util.BagFormatter;
028: import com.ibm.icu.util.TimeZone;
029:
030: /**
031: * Class for testing TimeZones for consistency
032: * @author Davis
033: *
034: */
035: public class TimeZoneAliasTest extends TestFmwk {
036:
037: public static void main(String[] args) throws Exception {
038: new TimeZoneAliasTest().run(args);
039: }
040:
041: /**
042: * There are two things to check aliases for:<br>
043: * 1. the alias set must be uniform: if a isAlias b, then aliasSet(a) == aliasSet(b)<br>
044: * 2. all aliases must have the same offsets
045: */
046: public void TestAliases() {
047: if (skipIfBeforeICU(3, 0))
048: return;
049: Zone.Seconds seconds = new Zone.Seconds();
050: for (Iterator it = Zone.getZoneSet().iterator(); it.hasNext();) {
051: Zone zone = (Zone) it.next();
052: String id = zone.id;
053: if (id.indexOf('/') < 0
054: && (id.endsWith("ST") || id.endsWith("DT"))) {
055: if (zone.minRecentOffset != zone.maxRecentOffset) {
056: errln("Standard or Daylight Time not constant: "
057: + id + ": "
058: + Zone.formatHours(zone.minRecentOffset)
059: + " != "
060: + Zone.formatHours(zone.maxRecentOffset));
061: }
062: }
063: Set aliases = zone.getPurportedAliases();
064: Set aliasesSet = new TreeSet(aliases);
065: aliasesSet.add(id); // for comparison
066: Iterator aliasIterator = aliases.iterator();
067: while (aliasIterator.hasNext()) {
068: String otherId = (String) aliasIterator.next();
069: Zone otherZone = Zone.make(otherId);
070: Set otherAliases = otherZone.getPurportedAliases();
071: otherAliases.add(otherId); // for comparison
072: if (!aliasesSet.equals(otherAliases)) {
073: errln("Aliases Unsymmetric: " + id + " => "
074: + Zone.bf.join(aliasesSet) + "; " + otherId
075: + " => " + Zone.bf.join(otherAliases));
076: }
077: if (zone.findOffsetOrdering(otherZone, seconds) != 0) {
078: errln("Aliases differ: " + id + ", " + otherId
079: + " differ at " + seconds);
080: }
081: }
082: }
083: }
084:
085: /**
086: * We check to see that every timezone that is not an alias is actually different!
087: */
088: public void TestDifferences() {
089: if (skipIfBeforeICU(3, 0))
090: return;
091: Zone last = null;
092: Zone.Seconds diffDate = new Zone.Seconds();
093: for (Iterator it = Zone.getZoneSet().iterator(); it.hasNext();) {
094: Zone testZone = (Zone) it.next();
095: if (last != null) {
096: String common = testZone + "\tvs " + last + ":\t";
097: int diff = testZone.findOffsetOrdering(last, diffDate);
098: if (diff != 0) {
099: logln("\t" + common + "difference at: " + diffDate
100: + ", " + Zone.formatHours(diff) + "hr");
101: } else if (testZone.isRealAlias(last)) {
102: logln("\t" + common + "alias, no difference");
103: } else {
104: errln(common + "NOT ALIAS BUT NO DIFFERENCE!");
105: }
106: }
107: last = testZone;
108: }
109: }
110:
111: /**
112: * Utility for printing out zones to be translated.
113: */
114: public static void TestGenerateZones() {
115: int count = 1;
116: for (Iterator it = Zone.getUniqueZoneSet().iterator(); it
117: .hasNext();) {
118: Zone zone = (Zone) it.next();
119: System.out.println(zone.toString(count++));
120: }
121: }
122:
123: /** Utility; ought to be someplace common
124: */
125: /*
126: static String join(Collection c, String separator) {
127: StringBuffer result = new StringBuffer();
128: boolean isFirst = true;
129: for (Iterator it = c.iterator(); it.hasNext(); ) {
130: if (!isFirst) result.append(separator);
131: else isFirst = false;
132: result.append(it.next().toString());
133: }
134: return result.toString();
135: }
136: */
137:
138: /**
139: * The guts is in this subclass. It sucks in all the data from the zones,
140: * and analyses it. It constructs some mappings for the unique ids,
141: * etc.<br>
142: * The main tricky bit is that for performance it pre-analyses all zones
143: * for inflections points; the points in time where the offset changes.
144: * The zones can then be sorted by those points, which allows us to
145: * avoid expensive comparisons.
146: * @author Davis
147: */
148: static class Zone implements Comparable {
149: // class fields
150: static private final BagFormatter bf = new BagFormatter()
151: .setSeparator(", ");
152: static private final DateFormat df = DateFormat
153: .getDateInstance(DateFormat.LONG, Locale.US);
154: static private final NumberFormat nf = NumberFormat
155: .getInstance(Locale.US);
156: static private final long HOUR = 1000 * 60 * 60;
157: static private final double DHOUR = HOUR;
158: static private final long DAY = 24 * HOUR;
159: static private final long GROSS_PERIOD = 30 * DAY;
160: static private final long EPSILON = HOUR / 4;
161: static private final int currentYear = new GregorianCalendar()
162: .get(Calendar.YEAR);
163: static private final long endDate = getDate((currentYear + 1),
164: 0, 1).getTime();
165: static private final long endDate2 = getDate((currentYear + 1),
166: 6, 1).getTime();
167: static private final long recentLimit = getDate(
168: (currentYear - 1), 6, 1).getTime();
169: static private final long startDate = getDate(1905, 0, 1)
170: .getTime();
171:
172: static private final Map idToZone = new HashMap();
173: static private final Set zoneSet = new TreeSet();
174: static private final Set uniqueZoneSet = new TreeSet();
175: static private final Map idToRealAliases = new HashMap();
176:
177: // build everything once.
178: static {
179: String[] foo = TimeZone.getAvailableIDs();
180: for (int i = 0; i < foo.length; ++i) {
181: zoneSet.add(Zone.make(foo[i]));
182: }
183: Zone last = null;
184: Zone.Seconds diffDate = new Zone.Seconds();
185: String lastUnique = "";
186: for (Iterator it = Zone.getZoneSet().iterator(); it
187: .hasNext();) {
188: Zone testZone = (Zone) it.next();
189: if (last == null) {
190: uniqueZoneSet.add(testZone);
191: lastUnique = testZone.id;
192: } else {
193: int diff = testZone.findOffsetOrdering(last,
194: diffDate);
195: if (diff != 0) {
196: uniqueZoneSet.add(testZone);
197: lastUnique = testZone.id;
198: } else {
199: Set aliases = (Set) idToRealAliases
200: .get(lastUnique);
201: if (aliases == null) {
202: aliases = new TreeSet();
203: idToRealAliases.put(lastUnique, aliases);
204: }
205: aliases.add(testZone.id);
206: }
207: }
208: last = testZone;
209: }
210: }
211:
212: static public Set getZoneSet() {
213: return zoneSet;
214: }
215:
216: public static Set getUniqueZoneSet() {
217: return uniqueZoneSet;
218: }
219:
220: static public Zone make(String id) {
221: Zone result = (Zone) idToZone.get(id);
222: if (result != null)
223: return result;
224: result = new Zone(id);
225: idToZone.put(id, result);
226: return result;
227: }
228:
229: static public String formatHours(int hours) {
230: return nf.format(hours / DHOUR);
231: }
232:
233: // utility class for date return, because Date is clunky.
234: public static class Seconds {
235: public long seconds = Long.MIN_VALUE;
236:
237: public String toString() {
238: if (seconds == Long.MIN_VALUE)
239: return "n/a";
240: return df.format(new Date(seconds));
241: }
242: }
243:
244: // instance fields
245: // we keep min/max offsets not only over all time (that we care about)
246: // but also separate ones for recent years.
247: private String id;
248: private TimeZone zone;
249: // computed below
250: private int minOffset;
251: private int maxOffset;
252: private int minRecentOffset;
253: private int maxRecentOffset;
254: private List inflectionPoints = new ArrayList();
255: private Set purportedAliases = new TreeSet();
256:
257: private Zone(String id) { // for interal use only; use make instead!
258: zone = TimeZone.getTimeZone(id);
259: this .id = id;
260:
261: // get aliases
262: int equivCount = TimeZone.countEquivalentIDs(id);
263: for (int j = 0; j < equivCount; ++j) {
264: String altID = TimeZone.getEquivalentID(id, j);
265: if (altID.equals(id))
266: continue;
267: purportedAliases.add(altID);
268: }
269:
270: // find inflexion points; times where the offset changed
271: long lastDate = endDate;
272: if (zone.getOffset(lastDate) < zone.getOffset(endDate2))
273: lastDate = endDate2;
274: maxRecentOffset = minRecentOffset = minOffset = maxOffset = zone
275: .getOffset(lastDate);
276:
277: inflectionPoints.add(new Long(lastDate));
278: int lastOffset = zone.getOffset(endDate);
279: long lastInflection = endDate;
280:
281: // we do a gross search, then narrow in when we find a difference from the last one
282: for (long currentDate = endDate; currentDate >= startDate; currentDate -= GROSS_PERIOD) {
283: int currentOffset = zone.getOffset(currentDate);
284: if (currentOffset != lastOffset) { // Binary Search
285: if (currentOffset < minOffset)
286: minOffset = currentOffset;
287: if (currentOffset > maxOffset)
288: maxOffset = currentOffset;
289: if (lastInflection >= recentLimit) {
290: if (currentOffset < minRecentOffset)
291: minRecentOffset = currentOffset;
292: if (currentOffset > maxRecentOffset)
293: maxRecentOffset = currentOffset;
294: }
295: long low = currentDate;
296: long high = lastDate;
297: while (low - high > EPSILON) {
298: long mid = (high + low) / 2;
299: int midOffset = zone.getOffset(mid);
300: if (midOffset == low) {
301: low = mid;
302: } else {
303: high = mid;
304: }
305: }
306: inflectionPoints.add(new Long(low));
307: lastInflection = low;
308: }
309: lastOffset = currentOffset;
310: }
311: inflectionPoints.add(new Long(startDate)); // just to cap it off for comparisons.
312: }
313:
314: // we assume that places will not convert time zones then back within one day
315: // so we go first by half
316: public int findOffsetOrdering(Zone other, Seconds dateDiffFound) {
317: //System.out.println("-diff: " + id + "\t" + other.id);
318: int result = 0;
319: long seconds = 0;
320: int min = inflectionPoints.size();
321: if (other.inflectionPoints.size() < min)
322: min = other.inflectionPoints.size();
323: main: {
324: for (int i = 0; i < min; ++i) {
325: long myIP = ((Long) inflectionPoints.get(i))
326: .longValue();
327: long otherIP = ((Long) other.inflectionPoints
328: .get(i)).longValue();
329: if (myIP > otherIP) { // take lowest, for transitivity (semi)
330: long temp = myIP;
331: myIP = otherIP;
332: otherIP = temp;
333: }
334: result = zone.getOffset(myIP)
335: - other.zone.getOffset(myIP);
336: if (result != 0) {
337: seconds = myIP;
338: break main;
339: }
340: if (myIP == otherIP)
341: continue; // test other if different
342: myIP = otherIP;
343: result = zone.getOffset(myIP)
344: - other.zone.getOffset(myIP);
345: if (result != 0) {
346: seconds = myIP;
347: break main;
348: }
349: }
350: // if they are equal so far, we don't care about the rest
351: result = 0;
352: seconds = Long.MIN_VALUE;
353: break main;
354: }
355: //System.out.println("+diff: " + (result/HOUR) + "\t" + dateDiffFound);
356: if (dateDiffFound != null)
357: dateDiffFound.seconds = seconds;
358: return result;
359: }
360:
361: // internal buffer to avoid creation all the time.
362: private Seconds diffDateReturn = new Seconds();
363:
364: public int compareTo(Object o) {
365: Zone other = (Zone) o;
366: // first order by max and min offsets
367: // min will usually correspond to standard time, max to daylight
368: // unless there have been historical shifts
369: if (minRecentOffset < other.minRecentOffset)
370: return -1;
371: if (minRecentOffset > other.minRecentOffset)
372: return 1;
373: if (maxRecentOffset < other.maxRecentOffset)
374: return -1;
375: if (maxRecentOffset > other.maxRecentOffset)
376: return 1;
377: // now check that all offsets are the same over history
378: int diffDate = findOffsetOrdering(other, diffDateReturn);
379: if (diffDate != 0)
380: return diffDate;
381: // choose longer name first!!
382: if (id.length() != other.id.length()) {
383: if (id.length() < other.id.length())
384: return 1;
385: return -1;
386: }
387: return id.compareTo(other.id);
388: }
389:
390: public Set getPurportedAliases() {
391: return new TreeSet(purportedAliases); // clone for safety
392: }
393:
394: public boolean isPurportedAlias(String id) {
395: return purportedAliases.contains(id);
396: }
397:
398: public boolean isRealAlias(Zone z) {
399: return purportedAliases.contains(z.id);
400: }
401:
402: public String getPurportedAliasesAsString() {
403: Set s = getPurportedAliases();
404: if (s.size() == 0)
405: return "";
406: return " " + bf.join(s);
407: }
408:
409: public String getRealAliasesAsString() {
410: Set s = (Set) idToRealAliases.get(id);
411: if (s == null)
412: return "";
413: return " *" + bf.join(s);
414: }
415:
416: public String getCity() {
417: int pos = id.lastIndexOf(('/'));
418: String city = id.substring(pos + 1);
419: return city.replace('_', ' ');
420: }
421:
422: public String toString() {
423: return toString(-1);
424: }
425:
426: /**
427: * Where count > 0, returns string that is set up for translation
428: */
429: public String toString(int count) {
430: String city = getCity();
431: String hours = formatHours(minRecentOffset)
432: + (minRecentOffset != maxRecentOffset ? ","
433: + formatHours(maxRecentOffset) : "");
434: if (count < 0) {
435: return id + getPurportedAliasesAsString() + " ("
436: + hours + ")";
437: }
438: // for getting template for translation
439: return "\t{\t\""
440: + id
441: + "\"\t// ["
442: + count
443: + "] "
444: + hours
445: + getRealAliasesAsString()
446: + "\r\n"
447: + "\t\t// translate the following!!\r\n"
448: + (minRecentOffset != maxRecentOffset ? "\t\t\""
449: + city + " Standard Time\"\r\n" + "\t\t\""
450: + city + "-ST\"\r\n" + "\t\t\"" + city
451: + " Daylight Time\"\r\n" + "\t\t\"" + city
452: + "-DT\"\r\n" : "\t\t\"\"\r\n"
453: + "\t\t\"\"\r\n" + "\t\t\"\"\r\n"
454: + "\t\t\"\"\r\n") + "\t\t\"" + city
455: + " Time\"\r\n" + "\t\t\"" + city + "-T\"\r\n"
456: + "\t\t\"" + city + "\"\r\n" + "\t}";
457: }
458: }
459: }
460:
461: //#endif
|