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