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