]> gitweb.fperrin.net Git - Dictionary.git/blob - jars/icu4j-4_8_1_1/main/tests/core/src/com/ibm/icu/dev/test/timezone/TimeZoneRuleTest.java
Added flags.
[Dictionary.git] / jars / icu4j-4_8_1_1 / main / tests / core / src / com / ibm / icu / dev / test / timezone / TimeZoneRuleTest.java
1 /*
2  *******************************************************************************
3  * Copyright (C) 2007-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.io.ByteArrayInputStream;
10 import java.io.ByteArrayOutputStream;
11 import java.io.IOException;
12 import java.io.InputStreamReader;
13 import java.io.OutputStreamWriter;
14 import java.io.StringReader;
15 import java.io.StringWriter;
16 import java.util.Date;
17
18 import com.ibm.icu.dev.test.TestFmwk;
19 import com.ibm.icu.util.AnnualTimeZoneRule;
20 import com.ibm.icu.util.BasicTimeZone;
21 import com.ibm.icu.util.Calendar;
22 import com.ibm.icu.util.DateTimeRule;
23 import com.ibm.icu.util.GregorianCalendar;
24 import com.ibm.icu.util.InitialTimeZoneRule;
25 import com.ibm.icu.util.RuleBasedTimeZone;
26 import com.ibm.icu.util.SimpleTimeZone;
27 import com.ibm.icu.util.TimeArrayTimeZoneRule;
28 import com.ibm.icu.util.TimeZone;
29 import com.ibm.icu.util.TimeZoneRule;
30 import com.ibm.icu.util.TimeZoneTransition;
31 import com.ibm.icu.util.ULocale;
32 import com.ibm.icu.util.VTimeZone;
33
34 /**
35  * Test cases for TimeZoneRule and RuleBasedTimeZone
36  */
37 public class TimeZoneRuleTest extends TestFmwk {
38
39     private static final int HOUR = 60 * 60 * 1000;
40
41     public static void main(String[] args) throws Exception {
42         new TimeZoneRuleTest().run(args);
43     }
44
45     /*
46      * RuleBasedTimeZone test cases
47      */
48     public void TestSimpleRuleBasedTimeZone() {
49         SimpleTimeZone stz = new SimpleTimeZone(-1*HOUR, "TestSTZ",
50                 Calendar.SEPTEMBER, -30, -Calendar.SATURDAY, 1*HOUR, SimpleTimeZone.WALL_TIME,
51                 Calendar.FEBRUARY, 2, Calendar.SUNDAY, 1*HOUR, SimpleTimeZone.WALL_TIME,
52                 1*HOUR);
53
54
55         DateTimeRule dtr;
56         AnnualTimeZoneRule atzr;
57         final int STARTYEAR = 2000;
58
59         InitialTimeZoneRule ir = new InitialTimeZoneRule(
60                 "RBTZ_Initial", // Initial time Name
61                 -1*HOUR,        // Raw offset
62                 1*HOUR);        // DST saving amount
63         
64         // RBTZ
65         RuleBasedTimeZone rbtz1 = new RuleBasedTimeZone("RBTZ1", ir);
66         dtr = new DateTimeRule(Calendar.SEPTEMBER, 30, Calendar.SATURDAY, false,
67                 1*HOUR, DateTimeRule.WALL_TIME); // SUN<=30 in September, at 1AM wall time
68         atzr = new AnnualTimeZoneRule("RBTZ_DST1",
69                 -1*HOUR /* rawOffset */, 1*HOUR /* dstSavings */, dtr,
70                 STARTYEAR, AnnualTimeZoneRule.MAX_YEAR);
71         rbtz1.addTransitionRule(atzr);
72         dtr = new DateTimeRule(Calendar.FEBRUARY, 2, Calendar.SUNDAY,
73                 1*HOUR, DateTimeRule.WALL_TIME); // 2nd Sunday in February, at 1AM wall time
74         atzr = new AnnualTimeZoneRule("RBTZ_STD1",
75                 -1*HOUR /* rawOffset */, 0 /* dstSavings */, dtr,
76                 STARTYEAR, AnnualTimeZoneRule.MAX_YEAR);
77         rbtz1.addTransitionRule(atzr);
78
79         // Equivalent, but different date rule type
80         RuleBasedTimeZone rbtz2 = new RuleBasedTimeZone("RBTZ2", ir);
81         dtr = new DateTimeRule(Calendar.SEPTEMBER, -1, Calendar.SATURDAY,
82                 1*HOUR, DateTimeRule.WALL_TIME); // Last Sunday in September at 1AM wall time
83         atzr = new AnnualTimeZoneRule("RBTZ_DST2", -1*HOUR, 1*HOUR, dtr, STARTYEAR, AnnualTimeZoneRule.MAX_YEAR);
84         rbtz2.addTransitionRule(atzr);
85         dtr = new DateTimeRule(Calendar.FEBRUARY, 8, Calendar.SUNDAY, true,
86                 1*HOUR, DateTimeRule.WALL_TIME); // SUN>=8 in February, at 1AM wall time
87         atzr = new AnnualTimeZoneRule("RBTZ_STD2", -1*HOUR, 0, dtr, STARTYEAR, AnnualTimeZoneRule.MAX_YEAR);
88         rbtz2.addTransitionRule(atzr);
89
90         // Equivalent, but different time rule type
91         RuleBasedTimeZone rbtz3 = new RuleBasedTimeZone("RBTZ3", ir);
92         dtr = new DateTimeRule(Calendar.SEPTEMBER, 30, Calendar.SATURDAY, false,
93                 2*HOUR, DateTimeRule.UTC_TIME);
94         atzr = new AnnualTimeZoneRule("RBTZ_DST3", -1*HOUR, 1*HOUR, dtr, STARTYEAR, AnnualTimeZoneRule.MAX_YEAR);
95         rbtz3.addTransitionRule(atzr);
96         dtr = new DateTimeRule(Calendar.FEBRUARY, 2, Calendar.SUNDAY,
97                 0*HOUR, DateTimeRule.STANDARD_TIME);
98         atzr = new AnnualTimeZoneRule("RBTZ_STD3", -1*HOUR, 0, dtr, STARTYEAR, AnnualTimeZoneRule.MAX_YEAR);
99         rbtz3.addTransitionRule(atzr);
100
101         // Check equivalency for 10 years
102         long start = getUTCMillis(STARTYEAR, Calendar.JANUARY, 1);
103         long until = getUTCMillis(STARTYEAR + 10, Calendar.JANUARY, 1);
104
105         if (!(stz.hasEquivalentTransitions(rbtz1, start, until))) {
106             errln("FAIL: rbtz1 must be equivalent to the SimpleTimeZone in the time range.");
107         }
108         if (!(stz.hasEquivalentTransitions(rbtz2, start, until))) {
109             errln("FAIL: rbtz2 must be equivalent to the SimpleTimeZone in the time range.");
110         }
111         if (!(stz.hasEquivalentTransitions(rbtz3, start, until))) {
112             errln("FAIL: rbtz3 must be equivalent to the SimpleTimeZone in the time range.");
113         }
114
115         // hasSameRules
116         if (rbtz1.hasSameRules(rbtz2)) {
117             errln("FAIL: rbtz1 and rbtz2 have different rules, but returned true.");
118         }
119         if (rbtz1.hasSameRules(rbtz3)) {
120             errln("FAIL: rbtz1 and rbtz3 have different rules, but returned true.");
121         }
122         RuleBasedTimeZone rbtz1c = (RuleBasedTimeZone)rbtz1.clone();
123         if (!rbtz1.hasSameRules(rbtz1c)) {
124             errln("FAIL: Cloned RuleBasedTimeZone must have the same rules with the original.");
125         }
126
127         // getOffset
128         GregorianCalendar cal = new GregorianCalendar();
129         int[] offsets = new int[2];
130         int offset;
131         boolean dst;
132
133         cal.setTimeZone(rbtz1);
134         cal.clear();
135
136         // Jan 1, 1000 BC
137         cal.set(Calendar.ERA, GregorianCalendar.BC);
138         cal.set(1000, Calendar.JANUARY, 1);
139
140         offset = rbtz1.getOffset(cal.get(Calendar.ERA), cal.get(Calendar.YEAR), cal.get(Calendar.MONTH),
141                 cal.get(Calendar.DAY_OF_MONTH), cal.get(Calendar.DAY_OF_WEEK), cal.get(Calendar.MILLISECONDS_IN_DAY));
142         if (offset != 0) {
143             errln("FAIL: Invalid time zone offset: " + offset + " /expected: 0");
144         }
145         dst = rbtz1.inDaylightTime(cal.getTime());
146         if (!dst) {
147             errln("FAIL: Invalid daylight saving time");
148         }
149         rbtz1.getOffset(cal.getTimeInMillis(), true, offsets);
150         if (offsets[0] != -3600000) {
151             errln("FAIL: Invalid time zone raw offset: " + offsets[0] + " /expected: -3600000");
152         }
153         if (offsets[1] != 3600000) {            
154             errln("FAIL: Invalid DST amount: " + offsets[1] + " /expected: 3600000");
155         }
156
157         // July 1, 2000, AD
158         cal.set(Calendar.ERA, GregorianCalendar.AD);
159         cal.set(2000, Calendar.JULY, 1);
160
161         offset = rbtz1.getOffset(cal.get(Calendar.ERA), cal.get(Calendar.YEAR), cal.get(Calendar.MONTH),
162                 cal.get(Calendar.DAY_OF_MONTH), cal.get(Calendar.DAY_OF_WEEK), cal.get(Calendar.MILLISECONDS_IN_DAY));
163         if (offset != -3600000) {
164             errln("FAIL: Invalid time zone offset: " + offset + " /expected: -3600000");
165         }
166         dst = rbtz1.inDaylightTime(cal.getTime());
167         if (dst) {
168             errln("FAIL: Invalid daylight saving time");
169         }
170         rbtz1.getOffset(cal.getTimeInMillis(), true, offsets);
171         if (offsets[0] != -3600000) {
172             errln("FAIL: Invalid time zone raw offset: " + offsets[0] + " /expected: -3600000");
173         }
174         if (offsets[1] != 0) {            
175             errln("FAIL: Invalid DST amount: " + offsets[1] + " /expected: 0");
176         }
177
178         // July 1, 2000, AD
179
180         // Try to add 3rd final rule
181         dtr = new DateTimeRule(Calendar.OCTOBER, 15, 1*HOUR, DateTimeRule.WALL_TIME);
182         atzr = new AnnualTimeZoneRule("3RD_ATZ", -1*HOUR, 2*HOUR, dtr, STARTYEAR, AnnualTimeZoneRule.MAX_YEAR);
183         boolean bException = false;
184         try {
185             rbtz1.addTransitionRule(atzr);
186         } catch (IllegalStateException ise) {
187             bException = true;
188         }
189         if (!bException) {
190             errln("FAIL: 3rd final rule must be rejected");
191         }
192
193         // Try to add an initial rule
194         bException = false;
195         try {
196             rbtz1.addTransitionRule(new InitialTimeZoneRule("Test Initial", 2*HOUR, 0));
197         } catch (IllegalArgumentException iae) {
198             bException = true;
199         }
200         if (!bException) {
201             errln("FAIL: InitialTimeZoneRule must be rejected");
202         }
203     }
204
205     /*
206      * Test equivalency between OlsonTimeZone and custom RBTZ representing the
207      * equivalent rules in a certain time range
208      */
209     public void TestHistoricalRuleBasedTimeZone() {
210         // Compare to America/New_York with equivalent RBTZ
211         TimeZone ny = TimeZone.getTimeZone("America/New_York", TimeZone.TIMEZONE_ICU);
212
213         //RBTZ
214         InitialTimeZoneRule ir = new InitialTimeZoneRule("EST", -5*HOUR, 0);
215         RuleBasedTimeZone rbtz = new RuleBasedTimeZone("EST5EDT", ir);
216
217         DateTimeRule dtr;
218         AnnualTimeZoneRule tzr;
219
220         // Standard time
221         dtr = new DateTimeRule(Calendar.OCTOBER, -1, Calendar.SUNDAY,
222                 2*HOUR, DateTimeRule.WALL_TIME);    // Last Sunday in October, at 2AM wall time
223         tzr = new AnnualTimeZoneRule("EST", -5*HOUR /* rawOffset */, 0 /* dstSavings */, dtr, 1967, 2006);
224         rbtz.addTransitionRule(tzr);
225
226         dtr = new DateTimeRule(Calendar.NOVEMBER, 1, Calendar.SUNDAY,
227                 true, 2*HOUR, DateTimeRule.WALL_TIME);  // SUN>=1 in November, at 2AM wall time
228         tzr = new AnnualTimeZoneRule("EST", -5*HOUR, 0, dtr, 2007, AnnualTimeZoneRule.MAX_YEAR);
229         rbtz.addTransitionRule(tzr);
230
231         // Daylight saving time
232         dtr = new DateTimeRule(Calendar.APRIL, -1, Calendar.SUNDAY,
233                 2*HOUR, DateTimeRule.WALL_TIME);    // Last Sunday in April, at 2AM wall time
234         tzr = new AnnualTimeZoneRule("EDT", -5*HOUR, 1*HOUR, dtr, 1967, 1973);
235         rbtz.addTransitionRule(tzr);
236
237         dtr = new DateTimeRule(Calendar.JANUARY, 6,
238                 2*HOUR, DateTimeRule.WALL_TIME);    // January 6, at 2AM wall time
239         tzr = new AnnualTimeZoneRule("EDT", -5*HOUR, 1*HOUR, dtr, 1974, 1974);
240         rbtz.addTransitionRule(tzr);
241         
242         dtr = new DateTimeRule(Calendar.FEBRUARY, 23,
243                 2*HOUR, DateTimeRule.WALL_TIME);    // February 23, at 2AM wall time
244         tzr = new AnnualTimeZoneRule("EDT", -5*HOUR, 1*HOUR, dtr, 1975, 1975);
245         rbtz.addTransitionRule(tzr);
246
247         dtr = new DateTimeRule(Calendar.APRIL, -1, Calendar.SUNDAY,
248                 2*HOUR, DateTimeRule.WALL_TIME);    // Last Sunday in April, at 2AM wall time
249         tzr = new AnnualTimeZoneRule("EDT", -5*HOUR, 1*HOUR, dtr, 1976, 1986);
250         rbtz.addTransitionRule(tzr);
251
252         dtr = new DateTimeRule(Calendar.APRIL, 1, Calendar.SUNDAY,
253                 true, 2*HOUR, DateTimeRule.WALL_TIME);  // SUN>=1 in April, at 2AM wall time
254         tzr = new AnnualTimeZoneRule("EDT", -5*HOUR, 1*HOUR, dtr, 1987, 2006);
255         rbtz.addTransitionRule(tzr);
256
257         dtr = new DateTimeRule(Calendar.MARCH, 8, Calendar.SUNDAY,
258                 true, 2*HOUR, DateTimeRule.WALL_TIME);  // SUN>=8 in March, at 2AM wall time
259         tzr = new AnnualTimeZoneRule("EDT", -5*HOUR, 1*HOUR, dtr, 2007, AnnualTimeZoneRule.MAX_YEAR);
260         rbtz.addTransitionRule(tzr);
261
262         // hasEquivalentTransitions
263         long jan1_1950 = getUTCMillis(1950, Calendar.JANUARY, 1);
264         long jan1_1967 = getUTCMillis(1971, Calendar.JANUARY, 1);
265         long jan1_2010 = getUTCMillis(2010, Calendar.JANUARY, 1);        
266
267         if (!(((BasicTimeZone)ny).hasEquivalentTransitions(rbtz, jan1_1967, jan1_2010))) {
268             errln("FAIL: The RBTZ must be equivalent to America/New_York between 1967 and 2010");
269         }
270         if (((BasicTimeZone)ny).hasEquivalentTransitions(rbtz, jan1_1950, jan1_2010)) {
271             errln("FAIL: The RBTZ must not be equivalent to America/New_York between 1950 and 2010");
272         }
273
274         // Same with above, but calling RBTZ#hasEquivalentTransitions against OlsonTimeZone
275         if (!rbtz.hasEquivalentTransitions(ny, jan1_1967, jan1_2010)) {
276             errln("FAIL: The RBTZ must be equivalent to America/New_York between 1967 and 2010");
277         }
278         if (rbtz.hasEquivalentTransitions(ny, jan1_1950, jan1_2010)) {
279             errln("FAIL: The RBTZ must not be equivalent to America/New_York between 1950 and 2010");
280         }
281
282         // TimeZone APIs
283         if (ny.hasSameRules(rbtz) || rbtz.hasSameRules(ny)) {
284             errln("FAIL: hasSameRules must return false");
285         }
286         RuleBasedTimeZone rbtzc = (RuleBasedTimeZone)rbtz.clone();
287         if (!rbtz.hasSameRules(rbtzc) || !rbtz.hasEquivalentTransitions(rbtzc, jan1_1950, jan1_2010)) {
288             errln("FAIL: hasSameRules/hasEquivalentTransitions must return true for cloned RBTZs");
289         }
290
291         long times[] = {
292            getUTCMillis(2006, Calendar.MARCH, 15),
293            getUTCMillis(2006, Calendar.NOVEMBER, 1),
294            getUTCMillis(2007, Calendar.MARCH, 15),
295            getUTCMillis(2007, Calendar.NOVEMBER, 1),
296            getUTCMillis(2008, Calendar.MARCH, 15),
297            getUTCMillis(2008, Calendar.NOVEMBER, 1)
298         };
299         int[] offsets1 = new int[2];
300         int[] offsets2 = new int[2];
301
302         for (int i = 0; i < times.length; i++) {
303             // Check getOffset - must return the same results for these time data
304             rbtz.getOffset(times[i], false, offsets1);
305             ny.getOffset(times[i], false, offsets2);
306             if (offsets1[0] != offsets2[0] || offsets1[1] != offsets2[1]) {
307                 errln("FAIL: Incompatible time zone offsets for ny and rbtz");
308             }
309             // Check inDaylightTime
310             Date d = new Date(times[i]);
311             if (rbtz.inDaylightTime(d) != ny.inDaylightTime(d)) {
312                 errln("FAIL: Incompatible daylight saving time for ny and rbtz");
313             }
314         }
315     }
316
317     /*
318      * Check if transitions returned by getNextTransition/getPreviousTransition
319      * are actual time transitions.
320      */
321     public void TestOlsonTransition() {
322         String[] zids = getTestZIDs();
323         for (int i = 0; i < zids.length; i++) {
324             TimeZone tz = TimeZone.getTimeZone(zids[i], TimeZone.TIMEZONE_ICU);
325             if (tz == null) {
326                 break;
327             }
328             int j = 0;
329             while (true) {
330                 long[] timerange = getTestTimeRange(j++);
331                 if (timerange == null) {
332                     break;
333                 }
334                 verifyTransitions(tz, timerange[0], timerange[1]);
335             }
336         }
337     }
338
339     /*
340      * Check if an OlsonTimeZone and its equivalent RBTZ have the exact same
341      * transitions.
342      */
343     public void TestRBTZTransition() {
344         int[] STARTYEARS = {
345             1950,
346             1975,
347             2000,
348             2010
349         };
350
351         String[] zids = getTestZIDs();
352         for (int i = 0; i < zids.length; i++) {
353             TimeZone tz = TimeZone.getTimeZone(zids[i], TimeZone.TIMEZONE_ICU);
354             if (tz == null) {
355                 break;
356             }
357             for (int j = 0; j < STARTYEARS.length; j++) {
358                 long startTime = getUTCMillis(STARTYEARS[j], Calendar.JANUARY, 1);
359                 TimeZoneRule[] rules = ((BasicTimeZone)tz).getTimeZoneRules(startTime);
360                 RuleBasedTimeZone rbtz = new RuleBasedTimeZone(tz.getID() + "(RBTZ)",
361                         (InitialTimeZoneRule)rules[0]);
362                 for (int k = 1; k < rules.length; k++) {
363                     rbtz.addTransitionRule(rules[k]);
364                 }
365
366                 // Compare the original OlsonTimeZone with the RBTZ starting the startTime for 20 years
367                 long until = getUTCMillis(STARTYEARS[j] + 20, Calendar.JANUARY, 1);
368
369                 // Ascending
370                 compareTransitionsAscending(tz, rbtz, startTime, until, false);
371                 // Ascending/inclusive
372                 compareTransitionsAscending(tz, rbtz, startTime + 1, until, true);
373                 // Descending
374                 compareTransitionsDescending(tz, rbtz, startTime, until, false);
375                 // Descending/inclusive
376                 compareTransitionsDescending(tz, rbtz, startTime + 1, until, true);
377             }
378             
379         }
380     }
381
382     /*
383      * Test cases for HasTimeZoneRules#hasEquivalentTransitions
384      */
385     public void TestHasEquivalentTransitions() {
386         // America/New_York and America/Indiana/Indianapolis are equivalent
387         // since 2006
388         TimeZone newyork = TimeZone.getTimeZone("America/New_York", TimeZone.TIMEZONE_ICU);
389         TimeZone indianapolis = TimeZone.getTimeZone("America/Indiana/Indianapolis", TimeZone.TIMEZONE_ICU);
390         TimeZone gmt_5 = TimeZone.getTimeZone("Etc/GMT+5", TimeZone.TIMEZONE_ICU);
391
392         long jan1_1971 = getUTCMillis(1971, Calendar.JANUARY, 1);
393         long jan1_2005 = getUTCMillis(2005, Calendar.JANUARY, 1);
394         long jan1_2006 = getUTCMillis(2006, Calendar.JANUARY, 1);
395         long jan1_2007 = getUTCMillis(2007, Calendar.JANUARY, 1);
396         long jan1_2011 = getUTCMillis(2010, Calendar.JANUARY, 1);
397         
398         if (((BasicTimeZone)newyork).hasEquivalentTransitions(indianapolis, jan1_2005, jan1_2011)) {
399             errln("FAIL: New_York is not equivalent to Indianapolis between 2005 and 2010, but returned true");
400         }
401         if (!((BasicTimeZone)newyork).hasEquivalentTransitions(indianapolis, jan1_2006, jan1_2011)) {
402             errln("FAIL: New_York is equivalent to Indianapolis between 2006 and 2010, but returned false");
403         }
404
405         if (!((BasicTimeZone)indianapolis).hasEquivalentTransitions(gmt_5, jan1_1971, jan1_2006)) {
406             errln("FAIL: Indianapolis is equivalent to GMT+5 between 1971 and 2005, but returned false");
407         }
408         if (((BasicTimeZone)indianapolis).hasEquivalentTransitions(gmt_5, jan1_1971, jan1_2007)) {
409             errln("FAIL: Indianapolis is not equivalent to GMT+5 between 1971 and 2006, but returned true");
410         }
411
412         // Cloned TimeZone
413         TimeZone newyork2 = (TimeZone)newyork.clone();
414         if (!((BasicTimeZone)newyork).hasEquivalentTransitions(newyork2, jan1_1971, jan1_2011)) {
415             errln("FAIL: Cloned TimeZone must have the same transitions");
416         }
417         if (!((BasicTimeZone)newyork).hasEquivalentTransitions(newyork2, jan1_1971, jan1_2011, true /*ignoreDstAmount*/)) {
418             errln("FAIL: Cloned TimeZone must have the same transitions");
419         }
420
421         // America/New_York and America/Los_Angeles has same DST start rules, but
422         // raw offsets are different
423         TimeZone losangeles = TimeZone.getTimeZone("America/Los_Angeles", TimeZone.TIMEZONE_ICU);
424         if (((BasicTimeZone)newyork).hasEquivalentTransitions(losangeles, jan1_2006, jan1_2011)) {
425             errln("FAIL: New_York is not equivalent to Los Angeles, but returned true");
426         }
427     }
428
429     /*
430      * Write out time zone rules of OlsonTimeZone into VTIMEZONE format, create a new
431      * VTimeZone from the VTIMEZONE data, then compare transitions
432      */
433     public void TestVTimeZoneRoundTrip() {
434         long startTime = getUTCMillis(1850, Calendar.JANUARY, 1);
435         long endTime = getUTCMillis(2050, Calendar.JANUARY, 1);
436
437         String[] tzids = getTestZIDs();
438         for (int i = 0; i < tzids.length; i++) {
439             BasicTimeZone olsontz = (BasicTimeZone)TimeZone.getTimeZone(tzids[i], TimeZone.TIMEZONE_ICU);
440             VTimeZone vtz_org = VTimeZone.create(tzids[i]);
441             vtz_org.setTZURL("http://source.icu-project.org/timezone");
442             vtz_org.setLastModified(new Date());
443             VTimeZone vtz_new = null;
444             try {
445                 // Write out VTIMEZONE
446                 ByteArrayOutputStream baos = new ByteArrayOutputStream();
447                 OutputStreamWriter writer = new OutputStreamWriter(baos);
448                 vtz_org.write(writer);
449                 writer.close();
450                 byte[] vtzdata = baos.toByteArray();
451                 // Read VTIMEZONE
452                 ByteArrayInputStream bais = new ByteArrayInputStream(vtzdata);
453                 InputStreamReader reader = new InputStreamReader(bais);
454                 vtz_new = VTimeZone.create(reader);
455                 reader.close();
456
457                 // Write out VTIMEZONE one more time
458                 ByteArrayOutputStream baos1 = new ByteArrayOutputStream();
459                 OutputStreamWriter writer1 = new OutputStreamWriter(baos1);
460                 vtz_new.write(writer1);
461                 writer1.close();
462                 byte[] vtzdata1 = baos1.toByteArray();
463
464                 // Make sure VTIMEZONE data is exactly same with the first one
465                 if (vtzdata.length != vtzdata1.length) {
466                     errln("FAIL: different VTIMEZONE data length");
467                 }
468                 for (int j = 0; j < vtzdata.length; j++) {
469                     if (vtzdata[j] != vtzdata1[j]) {
470                         errln("FAIL: different VTIMEZONE data");
471                         break;
472                     }
473                 }
474             } catch (IOException ioe) {
475                 errln("FAIL: IO error while writing/reading VTIMEZONE data");
476             }
477             // Check equivalency after the first transition.
478             // The DST information before the first transition might be lost
479             // because there is no good way to represent the initial time with
480             // VTIMEZONE.
481             if (vtz_new.getOffset(startTime) != olsontz.getOffset(startTime)) {
482                 errln("FAIL: VTimeZone for " + tzids[i]
483                          + " is not equivalent to its OlsonTimeZone corresponding at " + startTime);
484             }
485             TimeZoneTransition tzt = olsontz.getNextTransition(startTime, false);
486             if (tzt != null) {
487                 if (!vtz_new.hasEquivalentTransitions(olsontz, tzt.getTime(), endTime, true)) {
488                     int maxDelta = 1000;
489                     if (!hasEquivalentTransitions(vtz_new, olsontz, tzt.getTime() + maxDelta, endTime, true, maxDelta)) {
490                         errln("FAIL: VTimeZone for " + tzids[i] + " is not equivalent to its OlsonTimeZone corresponding.");
491                     } else {
492                         logln("VTimeZone for " + tzids[i] + " differs from its OlsonTimeZone corresponding with maximum transition time delta - " + maxDelta);
493                     }
494                 }
495                 if (!vtz_new.hasEquivalentTransitions(olsontz, tzt.getTime(), endTime, false)) {
496                     logln("VTimeZone for " + tzids[i] + " is not equivalent to its OlsonTimeZone corresponding in strict comparison mode.");
497                 }
498             }
499         }
500     }
501
502     /*
503      * Write out time zone rules of OlsonTimeZone after a cutoff date into VTIMEZONE format,
504      * create a new VTimeZone from the VTIMEZONE data, then compare transitions
505      */
506     public void TestVTimeZoneRoundTripPartial() {
507         long[] startTimes = new long[] {
508             getUTCMillis(1900, Calendar.JANUARY, 1),
509             getUTCMillis(1950, Calendar.JANUARY, 1),
510             getUTCMillis(2020, Calendar.JANUARY, 1)
511         };
512         long endTime = getUTCMillis(2050, Calendar.JANUARY, 1);
513
514         String[] tzids = getTestZIDs();
515         for (int n = 0; n < startTimes.length; n++) {
516             long startTime = startTimes[n];
517             for (int i = 0; i < tzids.length; i++) {
518                 BasicTimeZone olsontz = (BasicTimeZone)TimeZone.getTimeZone(tzids[i], TimeZone.TIMEZONE_ICU);
519                 VTimeZone vtz_org = VTimeZone.create(tzids[i]);
520                 VTimeZone vtz_new = null;
521                 try {
522                     // Write out VTIMEZONE
523                     ByteArrayOutputStream baos = new ByteArrayOutputStream();
524                     OutputStreamWriter writer = new OutputStreamWriter(baos);
525                     vtz_org.write(writer, startTime);
526                     writer.close();
527                     byte[] vtzdata = baos.toByteArray();
528                     // Read VTIMEZONE
529                     ByteArrayInputStream bais = new ByteArrayInputStream(vtzdata);
530                     InputStreamReader reader = new InputStreamReader(bais);
531                     vtz_new = VTimeZone.create(reader);
532                     reader.close();
533
534                 } catch (IOException ioe) {
535                     errln("FAIL: IO error while writing/reading VTIMEZONE data");
536                 }
537                 // Check equivalency after the first transition.
538                 // The DST information before the first transition might be lost
539                 // because there is no good way to represent the initial time with
540                 // VTIMEZONE.
541                 if (vtz_new.getOffset(startTime) != olsontz.getOffset(startTime)) {
542                     errln("FAIL: VTimeZone for " + tzids[i]
543                              + " is not equivalent to its OlsonTimeZone corresponding at " + startTime);
544                 }
545                 TimeZoneTransition tzt = olsontz.getNextTransition(startTime, false);
546                 if (tzt != null) {
547                     if (!vtz_new.hasEquivalentTransitions(olsontz, tzt.getTime(), endTime, true)) {
548                         int maxDelta = 1000;
549                         if (!hasEquivalentTransitions(vtz_new, olsontz, tzt.getTime() + maxDelta, endTime, true, maxDelta)) {
550                             errln("FAIL: VTimeZone for " + tzids[i] + "(>=" + startTime + ") is not equivalent to its OlsonTimeZone corresponding.");
551                         } else {
552                             logln("VTimeZone for " + tzids[i] + "(>=" + startTime + ")  differs from its OlsonTimeZone corresponding with maximum transition time delta - " + maxDelta);
553                         }
554                     }
555                 }
556             }
557         }
558     }
559
560     /*
561      * Write out simple time zone rules from an OlsonTimeZone at various time into VTIMEZONE
562      * format and create a new VTimeZone from the VTIMEZONE data, then make sure the raw offset
563      * and DST savings are same in these two time zones.
564      */
565     public void TestVTimeZoneSimpleWrite() {
566         long[] testTimes = new long[] {
567                 getUTCMillis(2006, Calendar.JANUARY, 1),
568                 getUTCMillis(2006, Calendar.MARCH, 15),
569                 getUTCMillis(2006, Calendar.MARCH, 31),
570                 getUTCMillis(2006, Calendar.APRIL, 5),
571                 getUTCMillis(2006, Calendar.OCTOBER, 25),
572                 getUTCMillis(2006, Calendar.NOVEMBER, 1),
573                 getUTCMillis(2006, Calendar.NOVEMBER, 5),
574                 getUTCMillis(2007, Calendar.JANUARY, 1)
575         };
576
577         String[] tzids = getTestZIDs();
578         for (int n = 0; n < testTimes.length; n++) {
579             long time = testTimes[n];
580
581             int[] offsets1 = new int[2];
582             int[] offsets2 = new int[2];
583
584             for (int i = 0; i < tzids.length; i++) {
585                 VTimeZone vtz_org = VTimeZone.create(tzids[i]);
586                 VTimeZone vtz_new = null;
587                 try {
588                     // Write out VTIMEZONE
589                     ByteArrayOutputStream baos = new ByteArrayOutputStream();
590                     OutputStreamWriter writer = new OutputStreamWriter(baos);
591                     vtz_org.writeSimple(writer, time);
592                     writer.close();
593                     byte[] vtzdata = baos.toByteArray();
594                     // Read VTIMEZONE
595                     ByteArrayInputStream bais = new ByteArrayInputStream(vtzdata);
596                     InputStreamReader reader = new InputStreamReader(bais);
597                     vtz_new = VTimeZone.create(reader);
598                     reader.close();
599                 } catch (IOException ioe) {
600                     errln("FAIL: IO error while writing/reading VTIMEZONE data");
601                 }
602
603                 // Check equivalency
604                 vtz_org.getOffset(time, false, offsets1);
605                 vtz_new.getOffset(time, false, offsets2);
606                 if (offsets1[0] != offsets2[0] || offsets1[1] != offsets2[1]) {
607                     errln("FAIL: VTimeZone writeSimple for " + tzids[i] + " at time " + time + " failed to the round trip.");
608                 }
609             }
610         }
611     }
612
613     /*
614      * Write out time zone rules of OlsonTimeZone into VTIMEZONE format with RFC2445 header TZURL and
615      * LAST-MODIFIED, create a new VTimeZone from the VTIMEZONE data to see if the headers are preserved.
616      */
617     public void TestVTimeZoneHeaderProps() {
618         String tzid = "America/Chicago";
619         String tzurl = "http://source.icu-project.org";
620         Date lastmod = new Date(getUTCMillis(2007, Calendar.JUNE, 1));
621
622         VTimeZone vtz = VTimeZone.create(tzid);
623         vtz.setTZURL(tzurl);
624         vtz.setLastModified(lastmod);
625
626         // Roundtrip conversion
627         VTimeZone newvtz1 = null;
628         try {
629             // Write out VTIMEZONE
630             ByteArrayOutputStream baos = new ByteArrayOutputStream();
631             OutputStreamWriter writer = new OutputStreamWriter(baos);
632             vtz.write(writer);
633             writer.close();
634             byte[] vtzdata = baos.toByteArray();
635             // Read VTIMEZONE
636             ByteArrayInputStream bais = new ByteArrayInputStream(vtzdata);
637             InputStreamReader reader = new InputStreamReader(bais);
638             newvtz1 = VTimeZone.create(reader);
639             reader.close();
640
641             // Check if TZURL and LAST-MODIFIED headers are preserved
642             if (!(tzurl.equals(newvtz1.getTZURL()))) {
643                 errln("FAIL: TZURL property is not preserved during the roundtrip conversion.  Before:"
644                         + tzurl + "/After:" + newvtz1.getTZURL());
645             }
646             if (!(lastmod.equals(newvtz1.getLastModified()))) {
647                 errln("FAIL: LAST-MODIFIED property is not preserved during the roundtrip conversion.  Before:"
648                         + lastmod.getTime() + "/After:" + newvtz1.getLastModified().getTime());
649             }
650         } catch (IOException ioe) {
651             errln("FAIL: IO error while writing/reading VTIMEZONE data");
652         }
653
654         // Second roundtrip, with a cutoff
655         VTimeZone newvtz2 = null;
656         try {
657             // Set different tzurl
658             String newtzurl = "http://www.ibm.com";
659             newvtz1.setTZURL(newtzurl);
660             // Write out VTIMEZONE
661             ByteArrayOutputStream baos = new ByteArrayOutputStream();
662             OutputStreamWriter writer = new OutputStreamWriter(baos);
663             newvtz1.write(writer, getUTCMillis(2000, Calendar.JANUARY, 1));
664             writer.close();
665             byte[] vtzdata = baos.toByteArray();
666             // Read VTIMEZONE
667             ByteArrayInputStream bais = new ByteArrayInputStream(vtzdata);
668             InputStreamReader reader = new InputStreamReader(bais);
669             newvtz2 = VTimeZone.create(reader);
670             reader.close();
671
672             // Check if TZURL and LAST-MODIFIED headers are preserved
673             if (!(newtzurl.equals(newvtz2.getTZURL()))) {
674                 errln("FAIL: TZURL property is not preserved during the second roundtrip conversion.  Before:"
675                         + newtzurl + "/After:" + newvtz2.getTZURL());
676             }
677             if (!(lastmod.equals(newvtz2.getLastModified()))) {
678                 errln("FAIL: LAST-MODIFIED property is not preserved during the second roundtrip conversion.  Before:"
679                         + lastmod.getTime() + "/After:" + newvtz2.getLastModified().getTime());
680             }
681         } catch (IOException ioe) {
682             errln("FAIL: IO error while writing/reading VTIMEZONE data");
683         }
684         
685     }
686
687     /*
688      * Extract simple rules from an OlsonTimeZone and make sure the rule format matches
689      * the expected format.
690      */
691     public void TestGetSimpleRules() {
692         long[] testTimes = new long[] {
693                 getUTCMillis(1970, Calendar.JANUARY, 1),
694                 getUTCMillis(2000, Calendar.MARCH, 31),
695                 getUTCMillis(2005, Calendar.JULY, 1),
696                 getUTCMillis(2010, Calendar.NOVEMBER, 1),
697             };
698
699         String[] tzids = getTestZIDs();
700         for (int n = 0; n < testTimes.length; n++) {
701             long time = testTimes[n];
702             for (int i = 0; i < tzids.length; i++) {
703                 BasicTimeZone tz = (BasicTimeZone)TimeZone.getTimeZone(tzids[i], TimeZone.TIMEZONE_ICU);
704                 TimeZoneRule[] rules = tz.getSimpleTimeZoneRulesNear(time);
705                 if (rules == null) {
706                     errln("FAIL: Failed to extract simple rules for " + tzids[i] + " at " + time);
707                 } else {
708                     if (rules.length == 1) {
709                         if (!(rules[0] instanceof InitialTimeZoneRule)) {
710                             errln("FAIL: Unexpected rule object type is returned for " + tzids[i] + " at " + time);
711                         }
712                     } else if (rules.length == 3) {
713                         if (!(rules[0] instanceof InitialTimeZoneRule)
714                                 || !(rules[1] instanceof AnnualTimeZoneRule)
715                                 || !(rules[2] instanceof AnnualTimeZoneRule)) {
716                             errln("FAIL: Unexpected rule object type is returned for " + tzids[i] + " at " + time);
717                         }
718                         for (int idx = 1; idx <= 2; idx++) {
719                             DateTimeRule dtr = ((AnnualTimeZoneRule)rules[idx]).getRule();
720                             if (dtr.getTimeRuleType() != DateTimeRule.WALL_TIME) {
721                                 errln("FAIL: WALL_TIME is not used as the time rule in the time zone rule(" + idx + ") for " + tzids[i] + " at " + time);
722                             }
723                             if (dtr.getDateRuleType() != DateTimeRule.DOW) {
724                                 errln("FAIL: DOW is not used as the date rule in the time zone rule(" + idx + ") for " + tzids[i] + " at " + time);
725                             }
726                         }
727                     } else {
728                         errln("FAIL: Unexpected number of rules returned for " + tzids[i] + " at " + time);
729                     }
730                 }
731             }
732         }
733     }
734
735     /*
736      * API coverage tests for TimeZoneRule 
737      */
738     public void TestTimeZoneRuleCoverage() {
739         long time1 = getUTCMillis(2005, Calendar.JULY, 4);
740         long time2 = getUTCMillis(2015, Calendar.JULY, 4);
741         long time3 = getUTCMillis(1950, Calendar.JULY, 4);
742
743         DateTimeRule dtr1 = new DateTimeRule(Calendar.FEBRUARY, 29, Calendar.SUNDAY, false,
744                 3*HOUR, DateTimeRule.WALL_TIME); // Last Sunday on or before Feb 29, at 3 AM, wall time
745         DateTimeRule dtr2 = new DateTimeRule(Calendar.MARCH, 11, 2*HOUR,
746                 DateTimeRule.STANDARD_TIME); // Mar 11, at 2 AM, standard time
747         DateTimeRule dtr3 = new DateTimeRule(Calendar.OCTOBER, -1, Calendar.SATURDAY,
748                 6*HOUR, DateTimeRule.UTC_TIME); //Last Saturday in Oct, at 6 AM, UTC
749         DateTimeRule dtr4 = new DateTimeRule(Calendar.MARCH, 8, Calendar.SUNDAY, true,
750                 2*HOUR, DateTimeRule.WALL_TIME); // First Sunday on or after Mar 8, at 2 AM, wall time
751
752         AnnualTimeZoneRule a1 = new AnnualTimeZoneRule("a1", -3*HOUR, 1*HOUR, dtr1,
753                 2000, AnnualTimeZoneRule.MAX_YEAR);
754         AnnualTimeZoneRule a2 = new AnnualTimeZoneRule("a2", -3*HOUR, 1*HOUR, dtr1,
755                 2000, AnnualTimeZoneRule.MAX_YEAR);
756         AnnualTimeZoneRule a3 = new AnnualTimeZoneRule("a3", -3*HOUR, 1*HOUR, dtr1,
757                 2000, 2010);
758         
759         InitialTimeZoneRule i1 = new InitialTimeZoneRule("i1", -3*HOUR, 0);
760         InitialTimeZoneRule i2 = new InitialTimeZoneRule("i2", -3*HOUR, 0);
761         InitialTimeZoneRule i3 = new InitialTimeZoneRule("i3", -3*HOUR, 1*HOUR);
762         
763         long[] emptytimes = {};
764         long[] trtimes1 = {0};
765         long[] trtimes2 = {0, 10000000};
766
767         TimeArrayTimeZoneRule t0 = null;
768         try {
769             // Try to construct TimeArrayTimeZoneRule with null transition times
770             t0 = new TimeArrayTimeZoneRule("nulltimes", -3*HOUR, 0,
771                     null, DateTimeRule.UTC_TIME);
772         } catch (IllegalArgumentException iae) {
773             logln("TimeArrayTimeZoneRule constructor throws IllegalArgumentException as expected.");
774             t0 = null;
775         }
776         if (t0 != null) {
777             errln("FAIL: TimeArrayTimeZoneRule constructor did not throw IllegalArgumentException for null times");
778         }
779         
780         try {
781             // Try to construct TimeArrayTimeZoneRule with empty transition times
782             t0 = new TimeArrayTimeZoneRule("nulltimes", -3*HOUR, 0,
783                     emptytimes, DateTimeRule.UTC_TIME);
784         } catch (IllegalArgumentException iae) {
785             logln("TimeArrayTimeZoneRule constructor throws IllegalArgumentException as expected.");
786             t0 = null;
787         }
788         if (t0 != null) {
789             errln("FAIL: TimeArrayTimeZoneRule constructor did not throw IllegalArgumentException for empty times");
790         }
791
792         TimeArrayTimeZoneRule t1 = new TimeArrayTimeZoneRule("t1", -3*HOUR, 0, trtimes1, DateTimeRule.UTC_TIME);
793         TimeArrayTimeZoneRule t2 = new TimeArrayTimeZoneRule("t2", -3*HOUR, 0, trtimes1, DateTimeRule.UTC_TIME);
794         TimeArrayTimeZoneRule t3 = new TimeArrayTimeZoneRule("t3", -3*HOUR, 0, trtimes2, DateTimeRule.UTC_TIME);
795         TimeArrayTimeZoneRule t4 = new TimeArrayTimeZoneRule("t4", -3*HOUR, 0, trtimes1, DateTimeRule.STANDARD_TIME);
796         TimeArrayTimeZoneRule t5 = new TimeArrayTimeZoneRule("t5", -4*HOUR, 1*HOUR, trtimes1, DateTimeRule.WALL_TIME);
797
798         // AnnualTimeZoneRule#getRule
799         if (!a1.getRule().equals(a2.getRule())) {
800             errln("FAIL: The same DateTimeRule must be returned from AnnualTimeZoneRule a1 and a2");
801         }
802     
803         // AnnualTimeZoneRule#getStartYear
804         int startYear = a1.getStartYear();
805         if (startYear != 2000) {
806             errln("FAIL: The start year of AnnualTimeZoneRule a1 must be 2000 - returned: " + startYear);
807         }
808
809         // AnnualTimeZoneRule#getEndYear
810         int endYear = a1.getEndYear();
811         if (endYear != AnnualTimeZoneRule.MAX_YEAR) {
812             errln("FAIL: The start year of AnnualTimeZoneRule a1 must be MAX_YEAR - returned: " + endYear);
813         }
814         endYear = a3.getEndYear();
815         if (endYear != 2010) {
816             errln("FAIL: The start year of AnnualTimeZoneRule a3 must be 2010 - returned: " + endYear);
817         }
818         
819         // AnnualTimeZone#getStartInYear
820         Date d1 = a1.getStartInYear(2005, -3*HOUR, 0);
821         Date d2 = a3.getStartInYear(2005, -3*HOUR, 0);
822         if (d1 == null || d2 == null || !d1.equals(d2)) {
823             errln("FAIL: AnnualTimeZoneRule#getStartInYear did not work as expected");
824         }
825         d2 = a3.getStartInYear(2015, -3*HOUR, 0);
826         if (d2 != null) {
827             errln("FAIL: AnnualTimeZoneRule#getSTartInYear returned non-null date for 2015 which is out of rule range");
828         }
829
830         // AnnualTimeZone#getFirstStart
831         d1 = a1.getFirstStart(-3*HOUR, 0);
832         d2 = a1.getFirstStart(-4*HOUR, 1*HOUR);
833         if (d1 == null || d2 == null || !d1.equals(d2)) {
834             errln("FAIL: The same start time should be returned by getFirstStart");
835         }
836
837         // AnnualTimeZone#getFinalStart
838         d1 = a1.getFinalStart(-3*HOUR, 0);
839         if (d1 != null) {
840             errln("FAIL: Non-null Date is returned by getFinalStart for a1");
841         }
842         d1 = a1.getStartInYear(2010, -3*HOUR, 0);
843         d2 = a3.getFinalStart(-3*HOUR, 0);
844         if (d1 == null || d2 == null || !d1.equals(d2)) {
845             errln("FAIL: Bad date is returned by getFinalStart");
846         }
847
848         // AnnualTimeZone#getNextStart / getPreviousStart
849         d1 = a1.getNextStart(time1, -3*HOUR, 0, false);
850         if (d1 == null) {
851             errln("FAIL: Null Date is returned by getNextStart");
852         } else {
853             d2 = a1.getPreviousStart(d1.getTime(), -3*HOUR, 0, true);
854             if (d2 == null || !d1.equals(d2)) {
855                 errln("FAIL: Bad Date is returned by getPreviousStart");
856             }
857         }
858         d1 = a3.getNextStart(time2, -3*HOUR, 0, false);
859         if (d1 != null) {
860             errln("FAIL: getNextStart must return null when no start time is available after the base time");
861         }
862         d1 = a3.getFinalStart(-3*HOUR, 0);
863         d2 = a3.getPreviousStart(time2, -3*HOUR, 0, false);
864         if (d1 == null || d2 == null || !d1.equals(d2)) {
865             errln("FAIL: getPreviousStart does not match with getFinalStart after the end year");
866         }
867
868         // AnnualTimeZone#isEquavalentTo
869         if (!a1.isEquivalentTo(a2)) {
870             errln("FAIL: AnnualTimeZoneRule a1 is equivalent to a2, but returned false");
871         }
872         if (a1.isEquivalentTo(a3)) {
873             errln("FAIL: AnnualTimeZoneRule a1 is not equivalent to a3, but returned true");
874         }
875         if (!a1.isEquivalentTo(a1)) {
876             errln("FAIL: AnnualTimeZoneRule a1 is equivalent to itself, but returned false");
877         }
878         if (a1.isEquivalentTo(t1)) {
879             errln("FAIL: AnnualTimeZoneRule is not equivalent to TimeArrayTimeZoneRule, but returned true");
880         }
881
882         // AnnualTimeZone#isTransitionRule
883         if (!a1.isTransitionRule()) {
884             errln("FAIL: An AnnualTimeZoneRule is a transition rule, but returned false");
885         }
886
887         // AnnualTimeZone#toString
888         String str = a1.toString();
889         if (str == null || str.length() == 0) {
890             errln("FAIL: AnnualTimeZoneRule#toString for a1 returns null or empty string");
891         } else {
892             logln("AnnualTimeZoneRule a1 : " + str);
893         }
894         str = a3.toString();
895         if (str == null || str.length() == 0) {
896             errln("FAIL: AnnualTimeZoneRule#toString for a3 returns null or empty string");
897         } else {
898             logln("AnnualTimeZoneRule a3 : " + str);
899         }
900
901         // InitialTimeZoneRule#isEquivalentRule
902         if (!i1.isEquivalentTo(i2)) {
903             errln("FAIL: InitialTimeZoneRule i1 is equivalent to i2, but returned false");
904         }
905         if (i1.isEquivalentTo(i3)) {
906             errln("FAIL: InitialTimeZoneRule i1 is not equivalent to i3, but returned true");
907         }
908         if (i1.isEquivalentTo(a1)) {
909             errln("FAIL: An InitialTimeZoneRule is not equivalent to an AnnualTimeZoneRule, but returned true");
910         }
911
912         // InitialTimeZoneRule#getFirstStart/getFinalStart/getNextStart/getPreviousStart
913         d1 = i1.getFirstStart(0, 0);
914         if (d1 != null) {
915             errln("FAIL: Non-null Date is returned by InitialTimeZone#getFirstStart");
916         }
917         d1 = i1.getFinalStart(0, 0);
918         if (d1 != null) {
919             errln("FAIL: Non-null Date is returned by InitialTimeZone#getFinalStart");
920         }
921         d1 = i1.getNextStart(time1, 0, 0, false);
922         if (d1 != null) {
923             errln("FAIL: Non-null Date is returned by InitialTimeZone#getNextStart");
924         }
925         d1 = i1.getPreviousStart(time1, 0, 0, false);
926         if (d1 != null) {
927             errln("FAIL: Non-null Date is returned by InitialTimeZone#getPreviousStart");
928         }
929
930         // InitialTimeZoneRule#isTransitionRule
931         if (i1.isTransitionRule()) {
932             errln("FAIL: An InitialTimeZoneRule is not a transition rule, but returned true");
933         }
934
935         // InitialTimeZoneRule#toString
936         str = i1.toString();
937         if (str == null || str.length() == 0) {
938             errln("FAIL: InitialTimeZoneRule#toString returns null or empty string");
939         } else {
940             logln("InitialTimeZoneRule i1 : " + str);
941         }
942         
943         
944         // TimeArrayTimeZoneRule#getStartTimes
945         long[] times = t1.getStartTimes();
946         if (times == null || times.length == 0 || times[0] != 0) {
947             errln("FAIL: Bad start times are returned by TimeArrayTimeZoneRule#getStartTimes");
948         }
949
950         // TimeArrayTimeZoneRule#getTimeType
951         if (t1.getTimeType() != DateTimeRule.UTC_TIME) {
952             errln("FAIL: TimeArrayTimeZoneRule t1 uses UTC_TIME, but different type is returned");
953         }
954         if (t4.getTimeType() != DateTimeRule.STANDARD_TIME) {
955             errln("FAIL: TimeArrayTimeZoneRule t4 uses STANDARD_TIME, but different type is returned");
956         }
957         if (t5.getTimeType() != DateTimeRule.WALL_TIME) {
958             errln("FAIL: TimeArrayTimeZoneRule t5 uses WALL_TIME, but different type is returned");
959         }
960
961         // TimeArrayTimeZoneRule#getFirstStart/getFinalStart
962         d1 = t1.getFirstStart(0, 0);
963         if (d1 == null || d1.getTime() != trtimes1[0]) {
964             errln("FAIL: Bad first start time returned from TimeArrayTimeZoneRule t1");
965         }
966         d1 = t1.getFinalStart(0, 0);
967         if (d1 == null || d1.getTime() != trtimes1[0]) {
968             errln("FAIL: Bad final start time returned from TimeArrayTimeZoneRule t1");
969         }
970         d1 = t4.getFirstStart(-4*HOUR, 1*HOUR);
971         if (d1 == null || (d1.getTime() != trtimes1[0] + 4*HOUR)) {
972             errln("FAIL: Bad first start time returned from TimeArrayTimeZoneRule t4");
973         }
974         d1 = t5.getFirstStart(-4*HOUR, 1*HOUR);
975         if (d1 == null || (d1.getTime() != trtimes1[0] + 3*HOUR)) {
976             errln("FAIL: Bad first start time returned from TimeArrayTimeZoneRule t5");
977         }
978
979         // TimeArrayTimeZoneRule#getNextStart/getPreviousStart
980         d1 = t3.getNextStart(time1, -3*HOUR, 1*HOUR, false);
981         if (d1 != null) {
982             errln("FAIL: Non-null Date is returned by getNextStart after the final transition for t3");
983         }
984         d1 = t3.getPreviousStart(time1, -3*HOUR, 1*HOUR, false);
985         if (d1 == null || d1.getTime() != trtimes2[1]) {
986             errln("FAIL: Bad start time returned by getPreviousStart for t3");
987         } else {
988             d2 = t3.getPreviousStart(d1.getTime(), -3*HOUR, 1*HOUR, false);
989             if (d2 == null || d2.getTime() != trtimes2[0]) {
990                 errln("FAIL: Bad start time returned by getPreviousStart for t3");
991             }
992         }
993         d1 = t3.getPreviousStart(time3, -3*HOUR, 1*HOUR, false); //time3 - year 1950, no result expected
994         if (d1 != null) {
995             errln("FAIL: Non-null Date is returned by getPrevoousStart for t3");
996         }
997
998         // TimeArrayTimeZoneRule#isEquivalentTo
999         if (!t1.isEquivalentTo(t2)) {
1000             errln("FAIL: TimeArrayTimeZoneRule t1 is equivalent to t2, but returned false");
1001         }
1002         if (t1.isEquivalentTo(t3)) {
1003             errln("FAIL: TimeArrayTimeZoneRule t1 is not equivalent to t3, but returned true");
1004         }
1005         if (t1.isEquivalentTo(t4)) {
1006             errln("FAIL: TimeArrayTimeZoneRule t1 is not equivalent to t4, but returned true");
1007         }
1008         if (t1.isEquivalentTo(a1)) {
1009             errln("FAIL: TimeArrayTimeZoneRule is not equivalent to AnnualTimeZoneRule, but returned true");
1010         }
1011
1012         // TimeArrayTimeZoneRule#isTransitionRule
1013         if (!t1.isTransitionRule()) {
1014             errln("FAIL: A TimeArrayTimeZoneRule is a transition rule, but returned false");
1015         }
1016
1017         // TimeArrayTimeZoneRule#toString
1018         str = t3.toString();
1019         if (str == null || str.length() == 0) {
1020             errln("FAIL: TimeArrayTimeZoneRule#toString returns null or empty string");
1021         } else {
1022             logln("TimeArrayTimeZoneRule t3 : " + str);
1023         }
1024
1025         // DateTimeRule#toString
1026         str = dtr1.toString();
1027         if (str == null || str.length() == 0) {
1028             errln("FAIL: DateTimeRule#toString for dtr1 returns null or empty string");
1029         } else {
1030             logln("DateTimeRule dtr1 : " + str);
1031         }
1032         str = dtr2.toString();
1033         if (str == null || str.length() == 0) {
1034             errln("FAIL: DateTimeRule#toString for dtr2 returns null or empty string");
1035         } else {
1036             logln("DateTimeRule dtr1 : " + str);
1037         }
1038         str = dtr3.toString();
1039         if (str == null || str.length() == 0) {
1040             errln("FAIL: DateTimeRule#toString for dtr3 returns null or empty string");
1041         } else {
1042             logln("DateTimeRule dtr1 : " + str);
1043         }
1044         str = dtr4.toString();
1045         if (str == null || str.length() == 0) {
1046             errln("FAIL: DateTimeRule#toString for dtr4 returns null or empty string");
1047         } else {
1048             logln("DateTimeRule dtr1 : " + str);
1049         }
1050     }
1051
1052     /*
1053      * API coverage test for BasicTimeZone APIs in SimpleTimeZone
1054      */
1055     public void TestSimpleTimeZoneCoverage() {
1056
1057         long time1 = getUTCMillis(1990, Calendar.JUNE, 1);
1058         long time2 = getUTCMillis(2000, Calendar.JUNE, 1);
1059
1060         TimeZoneTransition tzt1, tzt2;
1061
1062         // BasicTimeZone API implementation in SimpleTimeZone
1063         SimpleTimeZone stz1 = new SimpleTimeZone(-5*HOUR, "GMT-5");
1064
1065         tzt1 = stz1.getNextTransition(time1, false);
1066         if (tzt1 != null) {
1067             errln("FAIL: No transition must be returned by getNextTranstion for SimpleTimeZone with no DST rule");
1068         }
1069         tzt1 = stz1.getPreviousTransition(time1, false);
1070         if (tzt1 != null) {
1071             errln("FAIL: No transition must be returned by getPreviousTransition  for SimpleTimeZone with no DST rule");
1072         }
1073         TimeZoneRule[] tzrules = stz1.getTimeZoneRules();
1074         if (tzrules.length != 1 || !(tzrules[0] instanceof InitialTimeZoneRule)) {
1075             errln("FAIL: Invalid results returned by SimpleTimeZone#getTimeZoneRules");
1076         }
1077
1078         // Set DST rule
1079         stz1.setStartRule(Calendar.MARCH, 11, 2*HOUR); // March 11
1080         stz1.setEndRule(Calendar.NOVEMBER, 1, Calendar.SUNDAY, 2*HOUR); // First Sunday in November
1081         tzt1 = stz1.getNextTransition(time1, false);
1082         if (tzt1 == null) {
1083             errln("FAIL: Non-null transition must be returned by getNextTranstion for SimpleTimeZone with a DST rule");
1084         } else {
1085             String str = tzt1.toString();
1086             if (str == null || str.length() == 0) {
1087                 errln("FAIL: TimeZoneTransition#toString returns null or empty string");
1088             } else {
1089                 logln(str);
1090             }
1091         }
1092         tzt1 = stz1.getPreviousTransition(time1, false);
1093         if (tzt1 == null) {
1094             errln("FAIL: Non-null transition must be returned by getPreviousTransition  for SimpleTimeZone with a DST rule");
1095         }
1096         tzrules = stz1.getTimeZoneRules();
1097         if (tzrules.length != 3 || !(tzrules[0] instanceof InitialTimeZoneRule)
1098                 || !(tzrules[1] instanceof AnnualTimeZoneRule)
1099                 || !(tzrules[2] instanceof AnnualTimeZoneRule)) {
1100             errln("FAIL: Invalid results returned by SimpleTimeZone#getTimeZoneRules for a SimpleTimeZone with DST");
1101         }
1102         // Set DST start year
1103         stz1.setStartYear(2007);
1104         tzt1 = stz1.getPreviousTransition(time1, false);
1105         if (tzt1 != null) {
1106             errln("FAIL: No transition must be returned before 1990");
1107         }
1108         tzt1 = stz1.getNextTransition(time1, false); // transition after 1990-06-01
1109         tzt2 = stz1.getNextTransition(time2, false); // transition after 2000-06-01
1110         if (tzt1 == null || tzt2 == null || !tzt1.equals(tzt2)) {
1111             errln("FAIL: Bad transition returned by SimpleTimeZone#getNextTransition");
1112         }
1113     }
1114     
1115     /*
1116      * API coverage test for VTimeZone
1117      */
1118     public void TestVTimeZoneCoverage() {
1119         final String TZID = "Europe/Moscow";
1120         BasicTimeZone otz = (BasicTimeZone)TimeZone.getTimeZone(TZID, TimeZone.TIMEZONE_ICU);
1121         VTimeZone vtz = VTimeZone.create(TZID);
1122
1123         // getOffset(era, year, month, day, dayOfWeek, milliseconds)
1124         int offset1 = otz.getOffset(GregorianCalendar.AD, 2007, Calendar.JULY, 1, Calendar.SUNDAY, 0);
1125         int offset2 = vtz.getOffset(GregorianCalendar.AD, 2007, Calendar.JULY, 1, Calendar.SUNDAY, 0);
1126         if (offset1 != offset2) {
1127             errln("FAIL: getOffset(int,int,int,int,int,int) returned different results in VTimeZone and OlsonTimeZone");
1128         }
1129
1130         // getOffset(date, local, offsets)
1131         int[] offsets1 = new int[2];
1132         int[] offsets2 = new int[2];
1133         long t = System.currentTimeMillis();
1134         otz.getOffset(t, false, offsets1);
1135         vtz.getOffset(t, false, offsets2);
1136         if (offsets1[0] != offsets2[0] || offsets1[1] != offsets2[1]) {
1137             errln("FAIL: getOffset(long,boolean,int[]) returned different results in VTimeZone and OlsonTimeZone");
1138         }
1139
1140         // getRawOffset
1141         if (otz.getRawOffset() != vtz.getRawOffset()) {
1142             errln("FAIL: getRawOffset returned different results in VTimeZone and OlsonTimeZone");
1143         }
1144
1145         // inDaylightTime
1146         Date d = new Date();
1147         if (otz.inDaylightTime(d) != vtz.inDaylightTime(d)) {
1148             errln("FAIL: inDaylightTime returned different results in VTimeZone and OlsonTimeZone");
1149         }
1150
1151         // useDaylightTime
1152         if (otz.useDaylightTime() != vtz.useDaylightTime()) {
1153             errln("FAIL: useDaylightTime returned different results in VTimeZone and OlsonTimeZone");
1154         }
1155
1156         // setRawOffset
1157         final int RAW = -10*HOUR;
1158         VTimeZone tmpvtz = (VTimeZone)vtz.clone();
1159         tmpvtz.setRawOffset(RAW);
1160         if (tmpvtz.getRawOffset() != RAW) {
1161             logln("setRawOffset is implemented");
1162         }
1163
1164         // hasSameRules
1165         boolean bSame = otz.hasSameRules(vtz);
1166         logln("OlsonTimeZone#hasSameRules(VTimeZone) should return false always for now - actual: " + bSame);
1167
1168         // getTZURL/setTZURL
1169         final String TZURL = "http://icu-project.org/timezone";
1170         String tzurl = vtz.getTZURL();
1171         if (tzurl != null) {
1172             errln("FAIL: getTZURL returned non-null value");
1173         }
1174         vtz.setTZURL(TZURL);
1175         tzurl = vtz.getTZURL();
1176         if (!TZURL.equals(tzurl)) {
1177             errln("FAIL: URL returned by getTZURL does not match the one set by setTZURL");
1178         }
1179
1180         // getLastModified/setLastModified
1181         Date lastmod = vtz.getLastModified();
1182         if (lastmod != null) {
1183             errln("FAIL: getLastModified returned non-null value");
1184         }
1185         Date newdate = new Date();
1186         vtz.setLastModified(newdate);
1187         lastmod = vtz.getLastModified();
1188         if (!newdate.equals(lastmod)) {
1189             errln("FAIL: Date returned by getLastModified does not match the one set by setLastModified");
1190         }
1191
1192         // getNextTransition/getPreviousTransition
1193         long base = getUTCMillis(2007, Calendar.JULY, 1);
1194         TimeZoneTransition tzt1 = otz.getNextTransition(base, true);
1195         TimeZoneTransition tzt2 = vtz.getNextTransition(base, true);
1196         if (tzt1.equals(tzt2)) {
1197             errln("FAIL: getNextTransition returned different results in VTimeZone and OlsonTimeZone");
1198         }
1199         tzt1 = otz.getPreviousTransition(base, false);
1200         tzt2 = vtz.getPreviousTransition(base, false);
1201         if (tzt1.equals(tzt2)) {
1202             errln("FAIL: getPreviousTransition returned different results in VTimeZone and OlsonTimeZone");
1203         }
1204
1205         // hasEquivalentTransitions
1206         long time1 = getUTCMillis(1950, Calendar.JANUARY, 1);
1207         long time2 = getUTCMillis(2020, Calendar.JANUARY, 1);
1208         if (!vtz.hasEquivalentTransitions(otz, time1, time2)) {
1209             errln("FAIL: hasEquivalentTransitons returned false for the same time zone");
1210         }
1211
1212         // getTimeZoneRules
1213         TimeZoneRule[] rulesetAll = vtz.getTimeZoneRules();
1214         TimeZoneRule[] ruleset1 = vtz.getTimeZoneRules(time1);
1215         TimeZoneRule[] ruleset2 = vtz.getTimeZoneRules(time2);
1216         if (rulesetAll.length < ruleset1.length || ruleset1.length < ruleset2.length) {
1217             errln("FAIL: Number of rules returned by getRules is invalid");
1218         }
1219         
1220         int[] offsets_vtzc = new int[2];
1221         VTimeZone vtzc = VTimeZone.create("PST");
1222         vtzc.getOffsetFromLocal(Calendar.getInstance(vtzc).getTimeInMillis(), VTimeZone.LOCAL_STD, VTimeZone.LOCAL_STD, offsets_vtzc);
1223         if (offsets_vtzc[0] > offsets_vtzc[1]) {
1224             errln("Error getOffsetFromLocal()");
1225         }
1226     }
1227
1228     public void TestVTimeZoneParse() {
1229         // Trying to create VTimeZone from empty data
1230         StringReader r = new StringReader("");
1231         VTimeZone empty = VTimeZone.create(r);
1232         if (empty != null) {
1233             errln("FAIL: Non-null VTimeZone is returned for empty VTIMEZONE data");
1234         }
1235
1236         // Create VTimeZone for Asia/Tokyo
1237         String asiaTokyo =
1238                 "BEGIN:VTIMEZONE\r\n" +
1239                 "TZID:Asia\r\n" +
1240                 "\t/Tokyo\r\n" +
1241                 "BEGIN:STANDARD\r\n" +
1242                 "TZOFFSETFROM:+0900\r\n" +
1243                 "TZOFFSETTO:+0900\r\n" +
1244                 "TZNAME:JST\r\n" +
1245                 "DTSTART:19700101\r\n" +
1246                 " T000000\r\n" +
1247                 "END:STANDARD\r\n" +
1248                 "END:VTIMEZONE";
1249         r = new StringReader(asiaTokyo);
1250         VTimeZone tokyo = VTimeZone.create(r);
1251         if (tokyo == null) {
1252             errln("FAIL: Failed to create a VTimeZone tokyo");
1253         } else {
1254             // Make sure offsets are correct
1255             int[] offsets = new int[2];
1256             tokyo.getOffset(System.currentTimeMillis(), false, offsets);
1257             if (offsets[0] != 9*HOUR || offsets[1] != 0) {
1258                 errln("FAIL: Bad offsets returned by a VTimeZone created for Tokyo");
1259             }
1260         }        
1261
1262         // Create VTimeZone from VTIMEZONE data
1263         String fooData = 
1264             "BEGIN:VCALENDAR\r\n" +
1265             "BEGIN:VTIMEZONE\r\n" +
1266             "TZID:FOO\r\n" +
1267             "BEGIN:STANDARD\r\n" +
1268             "TZOFFSETFROM:-0700\r\n" +
1269             "TZOFFSETTO:-0800\r\n" +
1270             "TZNAME:FST\r\n" +
1271             "DTSTART:20071010T010000\r\n" +
1272             "RRULE:FREQ=YEARLY;BYDAY=WE;BYMONTHDAY=10,11,12,13,14,15,16;BYMONTH=10\r\n" +
1273             "END:STANDARD\r\n" +
1274             "BEGIN:DAYLIGHT\r\n" +
1275             "TZOFFSETFROM:-0800\r\n" +
1276             "TZOFFSETTO:-0700\r\n" +
1277             "TZNAME:FDT\r\n" +
1278             "DTSTART:20070415T010000\r\n" +
1279             "RRULE:FREQ=YEARLY;BYMONTHDAY=15;BYMONTH=4\r\n" +
1280             "END:DAYLIGHT\r\n" +
1281             "END:VTIMEZONE\r\n" +
1282             "END:VCALENDAR";
1283
1284         r = new StringReader(fooData);
1285         VTimeZone foo = VTimeZone.create(r);
1286         if (foo == null) {
1287             errln("FAIL: Failed to create a VTimeZone foo");
1288         } else {
1289             // Write VTIMEZONE data
1290             StringWriter w = new StringWriter();
1291             try {
1292                 foo.write(w, getUTCMillis(2005, Calendar.JANUARY, 1));
1293             } catch (IOException ioe) {
1294                 errln("FAIL: IOException is thrown while writing VTIMEZONE data for foo");
1295             }
1296             logln(w.toString());
1297         }
1298     }
1299
1300     public void TestT6216() {
1301         // Test case in #6216
1302         String tokyoTZ =
1303             "BEGIN:VCALENDAR\r\n" +
1304             "VERSION:2.0\r\n" +
1305             "PRODID:-//PYVOBJECT//NONSGML Version 1//EN\r\n" +
1306             "BEGIN:VTIMEZONE\r\n" +
1307             "TZID:Asia/Tokyo\r\n" +
1308             "BEGIN:STANDARD\r\n" +
1309             "DTSTART:20000101T000000\r\n" +
1310             "RRULE:FREQ=YEARLY;BYMONTH=1\r\n" +
1311             "TZNAME:Asia/Tokyo\r\n" +
1312             "TZOFFSETFROM:+0900\r\n" +
1313             "TZOFFSETTO:+0900\r\n" +
1314             "END:STANDARD\r\n" +
1315             "END:VTIMEZONE\r\n" +
1316             "END:VCALENDAR";
1317
1318         // Single final rule, overlapping with another
1319         String finalOverlap =
1320             "BEGIN:VCALENDAR\r\n" +
1321             "BEGIN:VTIMEZONE\r\n" +
1322             "TZID:FinalOverlap\r\n" +
1323             "BEGIN:STANDARD\r\n" +
1324             "TZOFFSETFROM:-0200\r\n" +
1325             "TZOFFSETTO:-0300\r\n" +
1326             "TZNAME:STD\r\n" +
1327             "DTSTART:20001029T020000\r\n" +
1328             "RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=10\r\n" +
1329             "END:STANDARD\r\n" +
1330             "BEGIN:DAYLIGHT\r\n" +
1331             "TZOFFSETFROM:-0300\r\n" +
1332             "TZOFFSETTO:-0200\r\n" +
1333             "TZNAME:DST\r\n" +
1334             "DTSTART:19990404T020000\r\n" +
1335             "RRULE:FREQ=YEARLY;BYDAY=1SU;BYMONTH=4;UNTIL=20050403T040000Z\r\n" +
1336             "END:DAYLIGHT\r\n" +
1337             "END:VTIMEZONE\r\n" +
1338             "END:VCALENDAR";
1339
1340         // Single final rule, no overlapping with another
1341         String finalNonOverlap = 
1342             "BEGIN:VCALENDAR\r\n" +
1343             "BEGIN:VTIMEZONE\r\n" +
1344             "TZID:FinalNonOverlap\r\n" +
1345             "BEGIN:STANDARD\r\n" +
1346             "TZOFFSETFROM:-0200\r\n" +
1347             "TZOFFSETTO:-0300\r\n" +
1348             "TZNAME:STD\r\n" +
1349             "DTSTART:20001029T020000\r\n" +
1350             "RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=10;UNTIL=20041031T040000Z\r\n" +
1351             "END:STANDARD\r\n" +
1352             "BEGIN:DAYLIGHT\r\n" +
1353             "TZOFFSETFROM:-0300\r\n" +
1354             "TZOFFSETTO:-0200\r\n" +
1355             "TZNAME:DST\r\n" +
1356             "DTSTART:19990404T020000\r\n" +
1357             "RRULE:FREQ=YEARLY;BYDAY=1SU;BYMONTH=4;UNTIL=20050403T040000Z\r\n" +
1358             "END:DAYLIGHT\r\n" +
1359             "BEGIN:STANDARD\r\n" +
1360             "TZOFFSETFROM:-0200\r\n" +
1361             "TZOFFSETTO:-0300\r\n" +
1362             "TZNAME:STDFINAL\r\n" +
1363             "DTSTART:20071028T020000\r\n" +
1364             "RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=10\r\n" +
1365             "END:STANDARD\r\n" +
1366             "END:VTIMEZONE\r\n" +
1367             "END:VCALENDAR";
1368
1369         int[][] TestDates = {
1370                 {1995, Calendar.JANUARY, 1},
1371                 {1995, Calendar.JULY, 1},
1372                 {2000, Calendar.JANUARY, 1},
1373                 {2000, Calendar.JULY, 1},
1374                 {2005, Calendar.JANUARY, 1},
1375                 {2005, Calendar.JULY, 1},
1376                 {2010, Calendar.JANUARY, 1},
1377                 {2010, Calendar.JULY, 1},
1378         };
1379
1380         String[] TestZones = {
1381             tokyoTZ,
1382             finalOverlap,
1383             finalNonOverlap,
1384         };
1385
1386         int[][] Expected = {
1387           //  JAN90      JUL90      JAN00      JUL00      JAN05      JUL05      JAN10      JUL10
1388             { 32400000,  32400000,  32400000,  32400000,  32400000,  32400000,  32400000,  32400000},
1389             {-10800000, -10800000,  -7200000,  -7200000, -10800000,  -7200000, -10800000, -10800000},
1390             {-10800000, -10800000,  -7200000,  -7200000, -10800000,  -7200000, -10800000, -10800000},
1391         };
1392
1393         // Get test times
1394         long[] times = new long[TestDates.length];
1395         Calendar cal = new GregorianCalendar(TimeZone.getTimeZone("Etc/GMT"));
1396         for (int i = 0; i < TestDates.length; i++) {
1397             cal.clear();
1398             cal.set(TestDates[i][0], TestDates[i][1], TestDates[i][2]);
1399             times[i] = cal.getTimeInMillis();
1400         }
1401
1402         for (int i = 0; i < TestZones.length; i++) {
1403             try {
1404                 VTimeZone vtz = VTimeZone.create(new StringReader(TestZones[i]));
1405                 for (int j = 0; j < times.length; j++) {
1406                     int offset = vtz.getOffset(times[j]);
1407                     if (offset != Expected[i][j]) {
1408                         errln("FAIL: Invalid offset at time(" + times[j] + "):" + offset + " Expected:" + Expected[i][j]);
1409                     }
1410                 }
1411             } catch (Exception e) {
1412                 errln("FAIL: Failed to calculate the offset for VTIMEZONE data " + i);
1413             }
1414         }
1415     }
1416
1417     public void TestT6669() {
1418         // getNext/PreviousTransition implementation in SimpleTimeZone
1419         // used to use a bad condition for detecting if DST is enabled or not.
1420
1421         SimpleTimeZone stz = new SimpleTimeZone(0, "CustomID",
1422                 Calendar.JANUARY, 1, Calendar.SUNDAY, 0,
1423                 Calendar.JULY, 1, Calendar.SUNDAY, 0);
1424
1425         long t = 1230681600000L; //2008-12-31T00:00:00
1426         long expectedNext = 1231027200000L; //2009-01-04T00:00:00
1427         long expectedPrev = 1215298800000L; //2008-07-06T00:00:00
1428
1429         TimeZoneTransition tzt = stz.getNextTransition(t, false);
1430         if (tzt == null) {
1431             errln("FAIL: No transition returned by getNextTransition.");
1432         } else if (tzt.getTime() != expectedNext){
1433             errln("FAIL: Wrong transition time returned by getNextTransition - "
1434                     + tzt.getTime() + " Expected: " + expectedNext);
1435         }
1436
1437         tzt = stz.getPreviousTransition(t, true);
1438         if (tzt == null) {
1439             errln("FAIL: No transition returned by getPreviousTransition.");
1440         } else if (tzt.getTime() != expectedPrev){
1441             errln("FAIL: Wrong transition time returned by getPreviousTransition - "
1442                     + tzt.getTime() + " Expected: " + expectedPrev);
1443         }
1444     }
1445     
1446     public void TestBasicTimeZoneCoverage() {
1447         TimeZone tz = TimeZone.getTimeZone("PST");
1448         if (tz instanceof BasicTimeZone) {
1449             BasicTimeZone btz = (BasicTimeZone)tz;
1450             int []offsets = new int[2];
1451
1452             btz.getOffsetFromLocal(Calendar.getInstance().getTimeInMillis(), BasicTimeZone.LOCAL_STD, BasicTimeZone.LOCAL_STD, offsets);
1453             if (offsets[0] > offsets[1]) {
1454                 errln("Error calling getOffsetFromLocal().");
1455             }
1456         } else {
1457             logln("Skipping TestBasicTimeZoneCoverage: ICU4J is configured to use JDK TimeZone");
1458         }
1459     }
1460
1461     // Internal utility methods -----------------------------------------
1462
1463     /*
1464      * Check if a time shift really happens on each transition returned by getNextTransition or
1465      * getPreviousTransition in the specified time range
1466      */
1467     private void verifyTransitions(TimeZone tz, long start, long end) {
1468         BasicTimeZone icutz = (BasicTimeZone)tz;
1469         long time;
1470         int[] before = new int[2];
1471         int[] after = new int[2];
1472         TimeZoneTransition tzt0;
1473
1474         // Ascending
1475         tzt0 = null;
1476         time = start;
1477         while(true) {
1478             TimeZoneTransition tzt = icutz.getNextTransition(time, false);
1479
1480             if (tzt == null) {
1481                 break;
1482             }
1483             time = tzt.getTime();
1484             if (time >= end) {
1485                 break;
1486             }
1487             icutz.getOffset(time, false, after);
1488             icutz.getOffset(time - 1, false, before);
1489
1490             if (after[0] == before[0] && after[1] == before[1]) {
1491                 errln("FAIL: False transition returned by getNextTransition for " + icutz.getID() + " at " + time);
1492             }
1493             if (tzt0 != null &&
1494                     (tzt0.getTo().getRawOffset() != tzt.getFrom().getRawOffset()
1495                     || tzt0.getTo().getDSTSavings() != tzt.getFrom().getDSTSavings())) {
1496                 errln("FAIL: TO rule of the previous transition does not match FROM rule of this transtion at "
1497                         + time + " for " + icutz.getID());                
1498             }
1499             tzt0 = tzt;
1500         }
1501
1502         // Descending
1503         tzt0 = null;
1504         time = end;
1505         while(true) {
1506             TimeZoneTransition tzt = icutz.getPreviousTransition(time, false);
1507             if (tzt == null) {
1508                 break;
1509             }
1510             time = tzt.getTime();
1511             if (time <= start) {
1512                 break;
1513             }
1514             icutz.getOffset(time, false, after);
1515             icutz.getOffset(time - 1, false, before);
1516
1517             if (after[0] == before[0] && after[1] == before[1]) {
1518                 errln("FAIL: False transition returned by getPreviousTransition for " + icutz.getID() + " at " + time);
1519             }
1520
1521             if (tzt0 != null &&
1522                     (tzt0.getFrom().getRawOffset() != tzt.getTo().getRawOffset()
1523                     || tzt0.getFrom().getDSTSavings() != tzt.getTo().getDSTSavings())) {
1524                 errln("FAIL: TO rule of the next transition does not match FROM rule in this transtion at "
1525                         + time + " for " + icutz.getID());                
1526             }
1527             tzt0 = tzt;
1528         }
1529     }
1530
1531     /*
1532      * Compare all time transitions in 2 time zones in the specified time range in ascending order
1533      */
1534     private void compareTransitionsAscending(TimeZone tz1, TimeZone tz2, long start, long end, boolean inclusive) {
1535         BasicTimeZone z1 = (BasicTimeZone)tz1;
1536         BasicTimeZone z2 = (BasicTimeZone)tz2;
1537         String zid1 = tz1.getID();
1538         String zid2 = tz2.getID();
1539
1540         long time = start;
1541         while(true) {
1542             TimeZoneTransition tzt1 = z1.getNextTransition(time, inclusive);
1543             TimeZoneTransition tzt2 = z2.getNextTransition(time, inclusive);
1544             boolean inRange1 = false;
1545             boolean inRange2 = false;
1546             if (tzt1 != null) {
1547                 if (tzt1.getTime() < end || (inclusive && tzt1.getTime() == end)) {
1548                     inRange1 = true;
1549                 }
1550             }
1551             if (tzt2 != null) {
1552                 if (tzt2.getTime() < end || (inclusive && tzt2.getTime() == end)) {
1553                     inRange2 = true;
1554                 }
1555             }
1556             if (!inRange1 && !inRange2) {
1557                 // No more transition in the range
1558                 break;
1559             }
1560             if (!inRange1) {
1561                 errln("FAIL: " + zid1 + " does not have any transitions after " + time + " before " + end);
1562                 break;
1563             }
1564             if (!inRange2) {
1565                 errln("FAIL: " + zid2 + " does not have any transitions after " + time + " before " + end);
1566                 break;
1567             }
1568             if (tzt1.getTime() != tzt2.getTime()) {
1569                 errln("FAIL: First transition after " + time + " "
1570                         + zid1 + "[" + tzt1.getTime() + "] "
1571                         + zid2 + "[" + tzt2.getTime() + "]");
1572                 break;
1573             }
1574             time = tzt1.getTime();
1575             if (inclusive) {
1576                 time++;
1577             }
1578         }
1579     }
1580
1581     /*
1582      * Compare all time transitions in 2 time zones in the specified time range in descending order
1583      */
1584     private void compareTransitionsDescending(TimeZone tz1, TimeZone tz2, long start, long end, boolean inclusive) {
1585         BasicTimeZone z1 = (BasicTimeZone)tz1;
1586         BasicTimeZone z2 = (BasicTimeZone)tz2;
1587         String zid1 = tz1.getID();
1588         String zid2 = tz2.getID();
1589         long time = end;
1590         while(true) {
1591             TimeZoneTransition tzt1 = z1.getPreviousTransition(time, inclusive);
1592             TimeZoneTransition tzt2 = z2.getPreviousTransition(time, inclusive);
1593             boolean inRange1 = false;
1594             boolean inRange2 = false;
1595             if (tzt1 != null) {
1596                 if (tzt1.getTime() > start || (inclusive && tzt1.getTime() == start)) {
1597                     inRange1 = true;
1598                 }
1599             }
1600             if (tzt2 != null) {
1601                 if (tzt2.getTime() > start || (inclusive && tzt2.getTime() == start)) {
1602                     inRange2 = true;
1603                 }
1604             }
1605             if (!inRange1 && !inRange2) {
1606                 // No more transition in the range
1607                 break;
1608             }
1609             if (!inRange1) {
1610                 errln("FAIL: " + zid1 + " does not have any transitions before " + time + " after " + start);
1611                 break;
1612             }
1613             if (!inRange2) {
1614                 errln("FAIL: " + zid2 + " does not have any transitions before " + time + " after " + start);
1615                 break;
1616             }
1617             if (tzt1.getTime() != tzt2.getTime()) {
1618                 errln("FAIL: Last transition before " + time + " "
1619                         + zid1 + "[" + tzt1.getTime() + "] "
1620                         + zid2 + "[" + tzt2.getTime() + "]");
1621                 break;
1622             }
1623             time = tzt1.getTime();
1624             if (inclusive) {
1625                 time--;
1626             }
1627         }
1628     }
1629
1630     private static final String[] TESTZIDS = {
1631         "AGT",
1632         "America/New_York",
1633         "America/Los_Angeles",
1634         "America/Indiana/Indianapolis",
1635         "America/Havana",
1636         "Europe/Lisbon",
1637         "Europe/Paris",
1638         "Asia/Tokyo",
1639         "Asia/Sakhalin",
1640         "Africa/Cairo",
1641         "Africa/Windhoek",
1642         "Australia/Sydney",
1643         "Etc/GMT+8",
1644         "Asia/Amman",
1645     };
1646
1647     private String[] getTestZIDs() {
1648         if (getInclusion() > 5) {
1649             return TimeZone.getAvailableIDs();
1650         }
1651         return TESTZIDS;
1652     }
1653
1654     private static final int[][] TESTYEARS = {
1655         {1895, 1905}, // including int32 minimum second
1656         {1965, 1975}, // including the epoch
1657         {1995, 2015}  // practical year range
1658     };
1659
1660     private long[] getTestTimeRange(int idx) {
1661         int loyear, hiyear;
1662         if (idx < TESTYEARS.length) {
1663             loyear = TESTYEARS[idx][0];
1664             hiyear = TESTYEARS[idx][1];
1665         } else if (idx == TESTYEARS.length && getInclusion() > 5) {
1666             loyear = 1850;
1667             hiyear = 2050;
1668         } else {
1669             return null;
1670         }
1671
1672         long[] times = new long[2];
1673         times[0] = getUTCMillis(loyear, Calendar.JANUARY, 1);
1674         times[1] = getUTCMillis(hiyear + 1, Calendar.JANUARY, 1);
1675
1676         return times;
1677     }
1678
1679     private GregorianCalendar utcCal;
1680
1681     private long getUTCMillis(int year, int month, int dayOfMonth) {
1682         if (utcCal == null) {
1683             utcCal = new GregorianCalendar(TimeZone.getTimeZone("UTC"), ULocale.ROOT);
1684         }
1685         utcCal.clear();
1686         utcCal.set(year, month, dayOfMonth);
1687         return utcCal.getTimeInMillis();
1688     }
1689
1690     /*
1691      * Slightly modified version of BasicTimeZone#hasEquivalentTransitions.
1692      * This version returns true if transition time delta is within the given
1693      * delta range.
1694      */
1695     private static boolean hasEquivalentTransitions(BasicTimeZone tz1, BasicTimeZone tz2,
1696                                             long start, long end, 
1697                                             boolean ignoreDstAmount, int maxTransitionTimeDelta) {
1698         if (tz1.hasSameRules(tz2)) {
1699             return true;
1700         }
1701
1702         // Check the offsets at the start time
1703         int[] offsets1 = new int[2];
1704         int[] offsets2 = new int[2];
1705
1706         tz1.getOffset(start, false, offsets1);
1707         tz2.getOffset(start, false, offsets2);
1708
1709         if (ignoreDstAmount) {
1710             if ((offsets1[0] + offsets1[1] != offsets2[0] + offsets2[1])
1711                 || (offsets1[1] != 0 && offsets2[1] == 0)
1712                 || (offsets1[1] == 0 && offsets2[1] != 0)) {
1713                 return false;
1714             }
1715         } else {
1716             if (offsets1[0] != offsets2[0] || offsets1[1] != offsets2[1]) {
1717                 return false;
1718             }
1719         }
1720
1721         // Check transitions in the range
1722         long time = start;
1723         while (true) {
1724             TimeZoneTransition tr1 = tz1.getNextTransition(time, false);
1725             TimeZoneTransition tr2 = tz2.getNextTransition(time, false);
1726
1727             if (ignoreDstAmount) {
1728                 // Skip a transition which only differ the amount of DST savings
1729                 while (true) {
1730                     if (tr1 != null
1731                             && tr1.getTime() <= end
1732                             && (tr1.getFrom().getRawOffset() + tr1.getFrom().getDSTSavings()
1733                                     == tr1.getTo().getRawOffset() + tr1.getTo().getDSTSavings())
1734                             && (tr1.getFrom().getDSTSavings() != 0 && tr1.getTo().getDSTSavings() != 0)) {
1735                         tr1 = tz1.getNextTransition(tr1.getTime(), false);
1736                     } else {
1737                         break;
1738                     }
1739                 }
1740                 while (true) {
1741                     if (tr2 != null
1742                             && tr2.getTime() <= end
1743                             && (tr2.getFrom().getRawOffset() + tr2.getFrom().getDSTSavings()
1744                                     == tr2.getTo().getRawOffset() + tr2.getTo().getDSTSavings())
1745                             && (tr2.getFrom().getDSTSavings() != 0 && tr2.getTo().getDSTSavings() != 0)) {
1746                         tr2 = tz2.getNextTransition(tr2.getTime(), false);
1747                     } else {
1748                         break;
1749                     }
1750                 }            }
1751
1752             boolean inRange1 = false;
1753             boolean inRange2 = false;
1754             if (tr1 != null) {
1755                 if (tr1.getTime() <= end) {
1756                     inRange1 = true;
1757                 }
1758             }
1759             if (tr2 != null) {
1760                 if (tr2.getTime() <= end) {
1761                     inRange2 = true;
1762                 }
1763             }
1764             if (!inRange1 && !inRange2) {
1765                 // No more transition in the range
1766                 break;
1767             }
1768             if (!inRange1 || !inRange2) {
1769                 return false;
1770             }
1771             if (Math.abs(tr1.getTime() - tr2.getTime()) > maxTransitionTimeDelta) {
1772                 return false;
1773             }
1774             if (ignoreDstAmount) {
1775                 if (tr1.getTo().getRawOffset() + tr1.getTo().getDSTSavings()
1776                             != tr2.getTo().getRawOffset() + tr2.getTo().getDSTSavings()
1777                         || tr1.getTo().getDSTSavings() != 0 &&  tr2.getTo().getDSTSavings() == 0
1778                         || tr1.getTo().getDSTSavings() == 0 &&  tr2.getTo().getDSTSavings() != 0) {
1779                     return false;
1780                 }
1781             } else {
1782                 if (tr1.getTo().getRawOffset() != tr2.getTo().getRawOffset() ||
1783                     tr1.getTo().getDSTSavings() != tr2.getTo().getDSTSavings()) {
1784                     return false;
1785                 }
1786             }
1787             time = tr1.getTime() > tr2.getTime() ? tr1.getTime() : tr2.getTime();
1788         }
1789         return true;
1790     }
1791 }