• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 package org.unicode.cldr.unittest;
2 
3 import java.text.DateFormat;
4 import java.util.ArrayList;
5 import java.util.Date;
6 import java.util.List;
7 import java.util.Locale;
8 import java.util.Map;
9 import java.util.Set;
10 import java.util.TreeMap;
11 import java.util.TreeSet;
12 
13 import org.unicode.cldr.util.CldrUtility;
14 import org.unicode.cldr.util.Pair;
15 import org.unicode.cldr.util.SupplementalDataInfo;
16 
17 import com.ibm.icu.impl.OlsonTimeZone;
18 import com.ibm.icu.impl.Relation;
19 import com.ibm.icu.text.DecimalFormat;
20 import com.ibm.icu.text.SimpleDateFormat;
21 import com.ibm.icu.util.Calendar;
22 import com.ibm.icu.util.TimeZone;
23 import com.ibm.icu.util.TimeZoneRule;
24 import com.ibm.icu.util.TimeZoneTransition;
25 import com.ibm.icu.util.ULocale;
26 
27 public class TestMetazoneTransitions {
28 
29     private static final int printDaylightTransitions = 6;
30 
31     private static final int SECOND = 1000;
32     private static final int MINUTE = 60 * SECOND;
33     private static final int HOUR = 60 * MINUTE;
34 
35     static final long startDate;
36 
37     static final long endDate;
38 
39     static final SimpleDateFormat neutralFormat = new SimpleDateFormat(
40         "yyyy-MM-dd HH:mm:ss", ULocale.ENGLISH);
41     static final DecimalFormat threeDigits = new DecimalFormat("000");
42     static final DecimalFormat twoDigits = new DecimalFormat("00");
43 
44     public static final Set<Integer> allOffsets = new TreeSet<Integer>();
45 
46     static {
47         TimeZone GMT = TimeZone.getTimeZone("Etc/GMT");
48         neutralFormat.setTimeZone(GMT);
49         Calendar cal = Calendar.getInstance(GMT, ULocale.US);
50         int year = cal.get(Calendar.YEAR);
cal.clear()51         cal.clear(); // need to clear fractional seconds
52         cal.set(1970, 0, 1, 0, 0, 0);
53         startDate = cal.getTimeInMillis();
54         cal.set(year + 5, 0, 1, 0, 0, 0);
55         endDate = cal.getTimeInMillis();
56         if (startDate != 0) {
IllegalArgumentException()57             throw new IllegalArgumentException();
58         }
59     }
60 
main(String[] args)61     public static void main(String[] args) throws Exception {
62         java.util.TimeZone zone2 = java.util.TimeZone.getTimeZone("GMT+830");
63         System.out.println(zone2.getID());
64         zone2 = java.util.TimeZone.getTimeZone("GMT+08");
65         System.out.println(zone2.getID());
66         zone2 = java.util.TimeZone.getTimeZone("Etc/GMT-8");
67         System.out.println(zone2.getID());
68 
69         java.util.TimeZone.setDefault(java.util.TimeZone
70             .getTimeZone("America/Los_Angeles"));
71         DateFormat javaFormat = DateFormat.getDateTimeInstance(DateFormat.FULL,
72             DateFormat.MEDIUM, Locale.US);
73         long start = new Date(107, 0, 1, 0, 30, 0).getTime();
74         start -= start % 1000; // clean up millis
75         long end = new Date(108, 0, 1, 0, 30, 0).getTime();
76         for (long date = start; date < end; date += 15 * MINUTE) {
77             String formatted = javaFormat.format(date);
78             Date roundTrip = javaFormat.parse(formatted);
79             if (roundTrip.getTime() != date) {
80                 System.out.println("Java roundtrip failed for: " + formatted
81                     + "\tSource: " + new Date(date) + "\tTarget: "
82                     + roundTrip);
83             }
84         }
85         new TestMetazoneTransitions().run();
86     }
87 
88     private static class ZoneTransition implements Comparable<ZoneTransition> {
89         long date;
90 
91         int offset;
92 
equals(Object that)93         public boolean equals(Object that) {
94             ZoneTransition other = (ZoneTransition) that;
95             return date == other.date && offset == other.offset;
96         }
97 
hashCode()98         public int hashCode() {
99             return (int) (date ^ (date >>> 32) ^ offset);
100         }
101 
ZoneTransition(long date, int offset)102         public ZoneTransition(long date, int offset) {
103             this.date = date;
104             this.offset = offset;
105         }
106 
107         /**
108          * Return the one with the smaller offset, or if equal then the smallest
109          * time
110          *
111          * @param o
112          * @return
113          */
compareTo(ZoneTransition o)114         public int compareTo(ZoneTransition o) {
115             int delta = offset - o.offset;
116             if (delta != 0)
117                 return delta;
118             long delta2 = date - o.date;
119             return delta2 == 0 ? 0 : delta2 < 0 ? -1 : 1;
120         }
121 
122         @Override
toString()123         public String toString() {
124             return neutralFormat.format(date) + ": " + ((double) offset) / HOUR
125                 + "hrs";
126         }
127     }
128 
129     enum DaylightChoice {
130         NO_DAYLIGHT, ONLY_DAYLIGHT
131     };
132 
133     private static class ZoneTransitions implements Comparable<ZoneTransitions> {
134         List<ZoneTransition> chronologicalList = new ArrayList<ZoneTransition>();
135 
equals(Object that)136         public boolean equals(Object that) {
137             ZoneTransitions other = (ZoneTransitions) that;
138             return chronologicalList.equals(other.chronologicalList);
139         }
140 
hashCode()141         public int hashCode() {
142             return chronologicalList.hashCode();
143         }
144 
ZoneTransitions(String tzid, DaylightChoice allowDaylight)145         public ZoneTransitions(String tzid, DaylightChoice allowDaylight) {
146             TimeZone zone = TimeZone.getTimeZone(tzid);
147             for (long date = startDate; date < endDate; date = getTransitionAfter(
148                 zone, date)) {
149                 addIfDifferent(zone, date, allowDaylight);
150             }
151         }
152 
addIfDifferent(TimeZone zone, long date, DaylightChoice allowDaylight)153         private void addIfDifferent(TimeZone zone, long date,
154             DaylightChoice allowDaylight) {
155             int offset = zone.getOffset(date);
156             allOffsets.add(offset);
157             int delta = getDSTSavings(zone, date);
158             switch (allowDaylight) {
159             case ONLY_DAYLIGHT:
160                 offset = delta;
161                 break;
162             case NO_DAYLIGHT:
163                 offset -= delta;
164                 break;
165             }
166             int size = chronologicalList.size();
167             if (size > 0) {
168                 ZoneTransition last = chronologicalList.get(size - 1);
169                 if (last.offset == offset) {
170                     return;
171                 }
172             }
173             chronologicalList.add(new ZoneTransition(date, offset));
174         }
175 
compareTo(ZoneTransitions other)176         public int compareTo(ZoneTransitions other) {
177             int minSize = Math.min(chronologicalList.size(),
178                 other.chronologicalList.size());
179             for (int i = 0; i < minSize; ++i) {
180                 ZoneTransition a = chronologicalList.get(i);
181                 ZoneTransition b = other.chronologicalList.get(i);
182                 int order = a.compareTo(b);
183                 if (order != 0)
184                     return order;
185             }
186             return chronologicalList.size() - other.chronologicalList.size();
187         }
188 
toString(String separator, int abbreviateToSize)189         public String toString(String separator, int abbreviateToSize) {
190             if (abbreviateToSize > 0
191                 && chronologicalList.size() > abbreviateToSize) {
192                 int limit = abbreviateToSize / 2;
193                 return CldrUtility.join(slice(chronologicalList, 0, limit),
194                     separator)
195                     + separator
196                     + "..."
197                     + separator
198                     + CldrUtility.join(
199                         slice(chronologicalList,
200                             chronologicalList.size() - limit,
201                             chronologicalList.size()),
202                         separator);
203             }
204             return CldrUtility.join(chronologicalList, separator);
205         }
206 
toString()207         public String toString() {
208             return toString("; ", -1);
209         }
210 
size()211         public int size() {
212             // TODO Auto-generated method stub
213             return chronologicalList.size();
214         }
215 
getDifferenceFrom( ZoneTransitions other)216         public Pair<ZoneTransitions, ZoneTransitions> getDifferenceFrom(
217             ZoneTransitions other) {
218             int minSize = Math.min(chronologicalList.size(),
219                 other.chronologicalList.size());
220             for (int i = 0; i < minSize; ++i) {
221                 ZoneTransition a = chronologicalList.get(i);
222                 ZoneTransition b = other.chronologicalList.get(i);
223                 int order = a.compareTo(b);
224                 if (order != 0)
225                     return new Pair(a, b);
226             }
227             if (chronologicalList.size() > other.chronologicalList.size()) {
228                 return new Pair(chronologicalList.get(minSize), null);
229             } else if (chronologicalList.size() < other.chronologicalList
230                 .size()) {
231                 return new Pair(null, other.chronologicalList.get(minSize));
232             } else {
233                 return new Pair(null, null);
234             }
235         }
236 
get(int i)237         public ZoneTransition get(int i) {
238             return chronologicalList.get(i);
239         }
240     }
241 
242     final static SupplementalDataInfo supplementalData = SupplementalDataInfo
243         .getInstance("C:/cvsdata/unicode/cldr/common/supplemental/");
244 
run()245     private void run() {
246         // String[] zones = TimeZone.getAvailableIDs();
247         Relation<ZoneTransitions, String> partition = Relation.of(
248             new TreeMap<ZoneTransitions, Set<String>>(), TreeSet.class);
249         Relation<ZoneTransitions, String> daylightPartition = Relation.of(
250             new TreeMap<ZoneTransitions, Set<String>>(), TreeSet.class);
251         Map<String, String> toDaylight = new TreeMap<String, String>();
252         Map<ZoneTransitions, String> daylightNames = new TreeMap<ZoneTransitions, String>();
253 
254         // get the main data
255         for (String zone : supplementalData.getCanonicalZones()) {
256             ZoneTransitions transitions = new ZoneTransitions(zone,
257                 DaylightChoice.NO_DAYLIGHT);
258             partition.put(transitions, zone);
259             transitions = new ZoneTransitions(zone,
260                 DaylightChoice.ONLY_DAYLIGHT);
261             if (transitions.size() > 1) {
262                 daylightPartition.put(transitions, zone);
263             }
264         }
265         // now assign names
266         int count = 0;
267         for (ZoneTransitions transitions : daylightPartition.keySet()) {
268             final String dname = "D" + threeDigits.format(++count);
269             daylightNames.put(transitions, dname);
270             for (String zone : daylightPartition.getAll(transitions)) {
271                 toDaylight.put(zone, dname);
272             }
273         }
274         // get the "primary" zone for each metazone
275         Map<String, String> zoneToMeta = new TreeMap<String, String>();
276         Map<String, Map<String, String>> metazoneToRegionToZone = supplementalData
277             .getMetazoneToRegionToZone();
278         for (String meta : metazoneToRegionToZone.keySet()) {
279             Map<String, String> regionToZone = metazoneToRegionToZone.get(meta);
280             String keyZone = regionToZone.get("001");
281             zoneToMeta.put(keyZone, meta);
282         }
283 
284         System.out.println();
285         System.out
286             .println("=====================================================");
287         System.out.println("*** Non-Daylight Partition");
288         System.out
289             .println("=====================================================");
290         System.out.println();
291 
292         count = 0;
293         Set<String> noMeta = new TreeSet<String>();
294         Set<String> multiMeta = new TreeSet<String>();
295         Set<String> stableZones = new TreeSet<String>();
296         for (ZoneTransitions transitions : partition.keySet()) {
297 
298             System.out.println();
299             final String nonDaylightPartitionName = "M"
300                 + threeDigits.format(++count);
301             System.out.println("Non-Daylight Partition "
302                 + nonDaylightPartitionName);
303             int metaCount = 0;
304             Set<String> metas = new TreeSet<String>();
305             for (String zone : partition.getAll(transitions)) {
306                 String daylightName = toDaylight.get(zone);
307                 String meta = zoneToMeta.get(zone);
308                 if (meta != null) {
309                     ++metaCount;
310                     metas.add(meta);
311                 }
312                 System.out.println("\t" + zone
313                     + (daylightName == null ? "" : "\t" + daylightName)
314                     + (meta == null ? "" : "\t\tMETA:" + meta));
315             }
316             if (metaCount == 0) {
317                 noMeta.add(nonDaylightPartitionName + "{"
318                     + CldrUtility.join(partition.getAll(transitions), ", ")
319                     + "}");
320             } else if (metaCount > 1) {
321                 multiMeta.add(nonDaylightPartitionName + "{"
322                     + CldrUtility.join(metas, ", ") + "}");
323             }
324             if (transitions.size() == 1) {
325                 final int offset = transitions.get(0).offset;
326                 allOffsets.remove(offset);
327                 stableZones.add(nonDaylightPartitionName + ", " + offset
328                     / (double) HOUR + "hrs " + "{"
329                     + CldrUtility.join(partition.getAll(transitions), ", ")
330                     + "}");
331             }
332             System.out.println("\t\t"
333                 + transitions.toString(CldrUtility.LINE_SEPARATOR + "\t\t",
334                     -1));
335         }
336         System.out.println();
337         System.out
338             .println("*** Non-Daylight Partitions with no canonical meta");
339         System.out.println("\t"
340             + CldrUtility.join(noMeta, CldrUtility.LINE_SEPARATOR + "\t"));
341         System.out.println();
342         System.out
343             .println("*** Non-Daylight Partitions with more than one canonical meta");
344         System.out.println("\t"
345             + CldrUtility
346                 .join(multiMeta, CldrUtility.LINE_SEPARATOR + "\t"));
347         System.out.println();
348         System.out.println("*** Stable Non-Daylight Partitions");
349         System.out.println("\t"
350             + CldrUtility.join(stableZones, CldrUtility.LINE_SEPARATOR
351                 + "\t"));
352         System.out.println();
353         System.out.println("*** Offsets with no stable partition");
354         for (int offset : allOffsets) {
355             System.out.println("\t" + offset / (double) HOUR + "hrs");
356         }
357         System.out.println();
358 
359         System.out.println();
360         System.out
361             .println("=====================================================");
362         System.out.println("*** Daylight Partition");
363         System.out
364             .println("=====================================================");
365         System.out.println();
366 
367         ZoneTransitions lastTransitions = null;
368         String lastName = null;
369         for (ZoneTransitions transitions : daylightPartition.keySet()) {
370             System.out.println();
371             String daylightName = daylightNames.get(transitions);
372             System.out.println("Daylight Partition\t" + daylightName);
373             for (String zone : daylightPartition.getAll(transitions)) {
374                 System.out.println("\t" + zone);
375             }
376             System.out.println("\t\t"
377                 + transitions.toString(CldrUtility.LINE_SEPARATOR + "\t\t",
378                     printDaylightTransitions));
379             if (lastTransitions != null) {
380                 Pair<ZoneTransitions, ZoneTransitions> diff = transitions
381                     .getDifferenceFrom(lastTransitions);
382                 System.out.println("\t\tTransition Difference from " + lastName
383                     + ":\t" + diff);
384             }
385             lastTransitions = transitions;
386             lastName = daylightName;
387         }
388 
389     }
390 
slice(List<T> list, int start, int limit)391     public static <T> List<T> slice(List<T> list, int start, int limit) {
392         ArrayList<T> temp = new ArrayList<T>();
393         for (int i = start; i < limit; ++i) {
394             temp.add(list.get(i));
395         }
396         return temp;
397     }
398 
399     /* Methods that ought to be on TimeZone */
400     /**
401      * Return the next point in time after date when the zone has a different
402      * offset than what it has on date. If there are no later transitions,
403      * returns Long.MAX_VALUE.
404      *
405      * @param zone
406      *            input zone -- should be method of TimeZone
407      * @param date
408      *            input date, in standard millis since 1970-01-01 00:00:00 GMT
409      */
getTransitionAfter(TimeZone zone, long date)410     public static long getTransitionAfter(TimeZone zone, long date) {
411         TimeZoneTransition transition = ((OlsonTimeZone) zone)
412             .getNextTransition(date, false);
413         if (transition == null) {
414             return Long.MAX_VALUE;
415         }
416         date = transition.getTime();
417         return date;
418     }
419 
420     /**
421      * Return true if the zone is in daylight savings on the date.
422      *
423      * @param zone
424      *            input zone -- should be method of TimeZone
425      * @param date
426      *            input date, in standard millis since 1970-01-01 00:00:00 GMT
427      */
inDaylightTime(TimeZone zone, long date)428     public static boolean inDaylightTime(TimeZone zone, long date) {
429         return ((OlsonTimeZone) zone).inDaylightTime(new Date(date));
430     }
431 
432     /**
433      * Return the daylight savings offset on the given date.
434      *
435      * @param zone
436      *            input zone -- should be method of TimeZone
437      * @param date
438      *            input date, in standard millis since 1970-01-01 00:00:00 GMT
439      */
getDSTSavings(TimeZone zone, long date)440     public static int getDSTSavings(TimeZone zone, long date) {
441         if (!inDaylightTime(zone, date)) {
442             return 0;
443         }
444         TimeZoneTransition transition = ((OlsonTimeZone) zone)
445             .getPreviousTransition(date + 1, true);
446         TimeZoneRule to = transition.getTo();
447         int delta = to.getDSTSavings();
448         // if (delta != HOUR) {
449         // System.out.println("Delta " + delta/(double)HOUR + " for " +
450         // zone.getID());
451         // }
452         return delta;
453     }
454 }