/*
 * Copyright (c) 1998, 2023, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */

/*
 * @test
 * @bug 4052967 4073209 4073215 4084933 4096952 4109314 4126678 4151406 4151429
 * 4154525 4154537 4154542 4154650 4159922 4162593 4173604 4176686 4184229 4208960
 * 4966229 6433179 6851214 8007520 8008577
 * @library /java/text/testlib
 * @run junit/othervm -Djava.locale.providers=COMPAT,SPI TimeZoneRegression
 */

import java.util.*;
import java.io.*;
import java.text.*;

import org.junit.jupiter.api.Test;

import static org.junit.jupiter.api.Assertions.fail;

public class TimeZoneRegression {

    @Test
    public void Test4073209() {
        TimeZone z1 = TimeZone.getTimeZone("PST");
        TimeZone z2 = TimeZone.getTimeZone("PST");
        if (z1 == z2) {
            fail("Fail: TimeZone should return clones");
        }
    }

    @SuppressWarnings("deprecation")
    @Test
    public void Test4073215() {
        SimpleTimeZone z = new SimpleTimeZone(0, "GMT");
        if (z.useDaylightTime()) {
            fail("Fail: Fix test to start with non-DST zone");
        }
        z.setStartRule(Calendar.FEBRUARY, 1, Calendar.SUNDAY, 0);
        z.setEndRule(Calendar.MARCH, -1, Calendar.SUNDAY, 0);
        if (!z.useDaylightTime()) {
            fail("Fail: DST not active");
        }
        if (z.inDaylightTime(new Date(97, Calendar.JANUARY, 31)) ||
            !z.inDaylightTime(new Date(97, Calendar.MARCH, 1)) ||
            z.inDaylightTime(new Date(97, Calendar.MARCH, 31))) {
            fail("Fail: DST not working as expected");
        }
    }

    /**
     * The expected behavior of TimeZone around the boundaries is:
     * (Assume transition time of 2:00 AM)
     *    day of onset 1:59 AM STD  = display name 1:59 AM ST
     *                 2:00 AM STD  = display name 3:00 AM DT
     *    day of end   0:59 AM STD  = display name 1:59 AM DT
     *                 1:00 AM STD  = display name 1:00 AM ST
     */
    @Test
    public void Test4084933() {
        // test both SimpleTimeZone and ZoneInfo objects.
        // @since 1.4
        sub4084933(getPST());
        sub4084933(TimeZone.getTimeZone("PST"));
    }

    private void sub4084933(TimeZone tz) {
        long offset1 = tz.getOffset(1,
            1997, Calendar.OCTOBER, 26, Calendar.SUNDAY, (2*60*60*1000));
        long offset2 = tz.getOffset(1,
            1997, Calendar.OCTOBER, 26, Calendar.SUNDAY, (2*60*60*1000)-1);

        long offset3 = tz.getOffset(1,
            1997, Calendar.OCTOBER, 26, Calendar.SUNDAY, (1*60*60*1000));
        long offset4 = tz.getOffset(1,
            1997, Calendar.OCTOBER, 26, Calendar.SUNDAY, (1*60*60*1000)-1);

        /*
         *  The following was added just for consistency.  It shows that going *to* Daylight
         *  Savings Time (PDT) does work at 2am.
         */

        long offset5 = tz.getOffset(1,
            1997, Calendar.APRIL, 6, Calendar.SUNDAY, (2*60*60*1000));
        long offset6 = tz.getOffset(1,
            1997, Calendar.APRIL, 6, Calendar.SUNDAY, (2*60*60*1000)-1);

        long offset7 = tz.getOffset(1,
            1997, Calendar.APRIL, 6, Calendar.SUNDAY, (1*60*60*1000));
        long offset8 = tz.getOffset(1,
            1997, Calendar.APRIL, 6, Calendar.SUNDAY, (1*60*60*1000)-1);

        long SToffset = -8 * 60*60*1000L;
        long DToffset = -7 * 60*60*1000L;
        if (offset1 != SToffset || offset2 != SToffset ||
            offset3 != SToffset || offset4 != DToffset ||
            offset5 != DToffset || offset6 != SToffset ||
            offset7 != SToffset || offset8 != SToffset)
            fail("Fail: TimeZone misbehaving"); {
        }
    }

    @Test
    public void Test4096952() {
        String[] ZONES = { "GMT", "MET", "IST" };
        boolean pass = true;
        try {
            for (int i=0; i<ZONES.length; ++i) {
                TimeZone zone = TimeZone.getTimeZone(ZONES[i]);
                if (!zone.getID().equals(ZONES[i]))
                    fail("Fail: Test broken; zones not instantiating");

                ByteArrayOutputStream baos;
                ObjectOutputStream ostream =
                    new ObjectOutputStream(baos = new
                                           ByteArrayOutputStream());
                ostream.writeObject(zone);
                ostream.close();
                baos.close();
                ObjectInputStream istream =
                    new ObjectInputStream(new
                                          ByteArrayInputStream(baos.toByteArray()));
                TimeZone frankenZone = (TimeZone) istream.readObject();
                //logln("Zone:        " + zone);
                //logln("FrankenZone: " + frankenZone);
                if (!zone.equals(frankenZone)) {
                    System.out.println("TimeZone " + zone.getID() +
                          " not equal to serialized/deserialized one");
                    pass = false;
                }
            }
            if (!pass) fail("Fail: TimeZone serialization/equality bug");
        }
        catch (IOException e) {
            fail("Fail: " + e);
            e.printStackTrace();
        }
        catch (ClassNotFoundException e) {
            fail("Fail: " + e);
            e.printStackTrace();
        }
    }

    @Test
    public void Test4109314() {
        Locale locale = Locale.getDefault();
        if (!TestUtils.usesGregorianCalendar(locale)) {
            System.out.println("Skipping this test because locale is " + locale);
            return;
        }

        // test both SimpleTimeZone and ZoneInfo objects.
        // @since 1.4
        sub4109314(getPST());
        sub4109314(TimeZone.getTimeZone("PST"));
    }


    @SuppressWarnings("deprecation")
    private void sub4109314(TimeZone PST) {
        GregorianCalendar testCal = (GregorianCalendar)Calendar.getInstance();
        Object[] testData = {
            PST, new Date(98,Calendar.APRIL,4,22,0), new Date(98, Calendar.APRIL, 5,6,0),
            PST, new Date(98,Calendar.OCTOBER,24,22,0), new Date(98,Calendar.OCTOBER,25,6,0),
        };
        boolean pass=true;
        for (int i=0; i<testData.length; i+=3) {
            testCal.setTimeZone((TimeZone) testData[i]);
            long t = ((Date)testData[i+1]).getTime();
            Date end = (Date) testData[i+2];
            while (t < end.getTime()) {
                testCal.setTime(new Date(t));
                if (!checkCalendar314(testCal, (TimeZone) testData[i]))
                    pass = false;
                t += 60*60*1000L;
            }
        }
        if (!pass) fail("Fail: TZ API inconsistent");
    }

    boolean checkCalendar314(GregorianCalendar testCal, TimeZone testTZ) {
        // GregorianCalendar testCal = (GregorianCalendar)aCal.clone();

        final int ONE_DAY = 24*60*60*1000;

        int tzOffset, tzRawOffset;
        Float tzOffsetFloat,tzRawOffsetFloat;
        // Here is where the user made an error.  They were passing in the value of
        // the MILLSECOND field; you need to pass in the millis in the day in STANDARD
        // time.
        int millis = testCal.get(Calendar.MILLISECOND) +
            1000 * (testCal.get(Calendar.SECOND) +
                    60 * (testCal.get(Calendar.MINUTE) +
                          60 * (testCal.get(Calendar.HOUR_OF_DAY)))) -
            testCal.get(Calendar.DST_OFFSET);

        /* Fix up millis to be in range.  ASSUME THAT WE ARE NOT AT THE
         * BEGINNING OR END OF A MONTH.  We must add this code because
         * getOffset() has been changed to be more strict about the parameters
         * it receives -- it turns out that this test was passing in illegal
         * values. */
        int date = testCal.get(Calendar.DATE);
        int dow  = testCal.get(Calendar.DAY_OF_WEEK);
        while (millis < 0) {
            millis += ONE_DAY;
            --date;
            dow = Calendar.SUNDAY + ((dow - Calendar.SUNDAY + 6) % 7);
        }
        while (millis >= ONE_DAY) {
            millis -= ONE_DAY;
            ++date;
            dow = Calendar.SUNDAY + ((dow - Calendar.SUNDAY + 1) % 7);
        }

        tzOffset = testTZ.getOffset(testCal.get(Calendar.ERA),
                                    testCal.get(Calendar.YEAR),
                                    testCal.get(Calendar.MONTH),
                                    date,
                                    dow,
                                    millis);
        tzRawOffset = testTZ.getRawOffset();
        tzOffsetFloat = new Float((float)tzOffset/(float)3600000);
        tzRawOffsetFloat = new Float((float)tzRawOffset/(float)3600000);

        Date testDate = testCal.getTime();

        boolean inDaylightTime = testTZ.inDaylightTime(testDate);
        SimpleDateFormat sdf = new SimpleDateFormat("MM/dd/yyyy HH:mm");
        sdf.setCalendar(testCal);
        String inDaylightTimeString;

        boolean passed;

        if (inDaylightTime)
        {
            inDaylightTimeString = " DST ";
            passed = (tzOffset == (tzRawOffset + 3600000));
        }
        else
        {
            inDaylightTimeString = "     ";
            passed = (tzOffset == tzRawOffset);
        }

        String output = testTZ.getID() + " " + sdf.format(testDate) +
            " Offset(" + tzOffsetFloat + ")" +
            " RawOffset(" + tzRawOffsetFloat + ")" +
            " " + millis/(float)3600000 + " " +
            inDaylightTimeString;

        if (passed)
            output += "     ";
        else
            output += "ERROR";

        if (passed) System.out.println(output); else fail(output);
        return passed;
    }

    /**
     * CANNOT REPRODUDE
     *
     * Yet another _alleged_ bug in TimeZone.getOffset(), a method that never
     * should have been made public.  It's simply too hard to use correctly.
     *
     * The original test code failed to do the following:
     * (1) Call Calendar.setTime() before getting the fields!
     * (2) Use the right millis (as usual) for getOffset(); they were passing
     *     in the MILLIS field, instead of the STANDARD MILLIS IN DAY.
     * When you fix these two problems, the test passes, as expected.
     */
    @Test
    public void Test4126678() {
        Locale locale = Locale.getDefault();
        if (!TestUtils.usesGregorianCalendar(locale)) {
            System.out.println("Skipping this test because locale is " + locale);
            return;
        }

        // Note: this test depends on the PST time zone.
        TimeZone initialZone = TimeZone.getDefault();

        // test both SimpleTimeZone and ZoneInfo objects.
        // @since 1.4
        sub4126678(getPST());
        sub4126678(TimeZone.getTimeZone("PST"));

        // restore the initial time zone so that this test case
        // doesn't affect the others.
        TimeZone.setDefault(initialZone);
    }

    @SuppressWarnings("deprecation")
    private void sub4126678(TimeZone tz) {
        Calendar cal = Calendar.getInstance();
        TimeZone.setDefault(tz);
        cal.setTimeZone(tz);

        Date dt = new Date(1998-1900, Calendar.APRIL, 5, 10, 0);
        // the dt value is local time in PST.
        if (!tz.inDaylightTime(dt))
            fail("We're not in Daylight Savings Time and we should be.\n");

        cal.setTime(dt);
        int era = cal.get(Calendar.ERA);
        int year = cal.get(Calendar.YEAR);
        int month = cal.get(Calendar.MONTH);
        int day = cal.get(Calendar.DATE);
        int dayOfWeek = cal.get(Calendar.DAY_OF_WEEK);
        int millis = cal.get(Calendar.MILLISECOND) +
            (cal.get(Calendar.SECOND) +
             (cal.get(Calendar.MINUTE) +
              (cal.get(Calendar.HOUR) * 60) * 60) * 1000) -
            cal.get(Calendar.DST_OFFSET);

        long offset = tz.getOffset(era, year, month, day, dayOfWeek, millis);
        long raw_offset = tz.getRawOffset();
        if (offset == raw_offset) {
            fail("Offsets should not match when in DST");
        }
    }

    /**
     * TimeZone.getAvailableIDs(int) throws exception for certain values,
     * due to a faulty constant in TimeZone.java.
     */
    @Test
    public void Test4151406() {
        int max = 0;
        for (int h=-28; h<=30; ++h) {
            // h is in half-hours from GMT; rawoffset is in millis
            int rawoffset = h * 1800000;
            int hh = (h<0) ? -h : h;
            String hname = ((h<0) ? "GMT-" : "GMT+") +
                ((hh/2 < 10) ? "0" : "") +
                (hh/2) + ':' +
                ((hh%2==0) ? "00" : "30");
            try {
                String[] ids = TimeZone.getAvailableIDs(rawoffset);
                if (ids.length > max) max = ids.length;
                System.out.println(hname + ' ' + ids.length +
                      ((ids.length > 0) ? (" e.g. " + ids[0]) : ""));
            } catch (Exception e) {
                fail(hname + ' ' + "Fail: " + e);
            }
        }
        System.out.println("Maximum zones per offset = " + max);
    }

    @Test
    public void Test4151429() {
        try {
            TimeZone tz = TimeZone.getTimeZone("GMT");
            String name = tz.getDisplayName(true, Integer.MAX_VALUE,
                                            Locale.getDefault());
            fail("IllegalArgumentException not thrown by TimeZone.getDisplayName()");
        } catch(IllegalArgumentException e) {}
    }

    /**
     * SimpleTimeZone accepts illegal DST savings values.  These values
     * must be non-zero.  There is no upper limit at this time.
     */
    @Test
    public void Test4154525() {
        final int GOOD = 1, BAD = 0;
        int[] DATA = {
            1, GOOD,
            0, BAD,
            -1, BAD,
            60*60*1000, GOOD,
            Integer.MIN_VALUE, BAD,
            // Integer.MAX_VALUE, ?, // no upper limit on DST savings at this time
        };
        for (int i=0; i<DATA.length; i+=2) {
            int savings = DATA[i];
            boolean valid = DATA[i+1] == GOOD;
            String method = null;
            for (int j=0; j<2; ++j) {
                try {
                    switch (j) {
                    case 0:
                        method = "constructor";
                        SimpleTimeZone z = new SimpleTimeZone(0, "id",
                            Calendar.JANUARY, 1, 0, 0,
                            Calendar.MARCH, 1, 0, 0,
                            savings); // <- what we're interested in
                        break;
                    case 1:
                        method = "setDSTSavings()";
                        z = new SimpleTimeZone(0, "GMT");
                        z.setDSTSavings(savings);
                        break;
                    }
                    if (valid) {
                        System.out.println("Pass: DST savings of " + savings + " accepted by " + method);
                    } else {
                        fail("Fail: DST savings of " + savings + " accepted by " + method);
                    }
                } catch (IllegalArgumentException e) {
                    if (valid) {
                        fail("Fail: DST savings of " + savings + " to " + method + " gave " + e);
                    } else {
                        System.out.println("Pass: DST savings of " + savings + " to " + method + " gave " + e);
                    }
                }
            }
        }
    }

    /**
     * SimpleTimeZone.hasSameRules() doesn't work for zones with no DST
     * and different DST parameters.
     */
    @Test
    public void Test4154537() {
        // tz1 and tz2 have no DST and different rule parameters
        SimpleTimeZone tz1 = new SimpleTimeZone(0, "1", 0, 0, 0, 0, 2, 0, 0, 0);
        SimpleTimeZone tz2 = new SimpleTimeZone(0, "2", 1, 0, 0, 0, 3, 0, 0, 0);
        // tza and tzA have the same rule params
        SimpleTimeZone tza = new SimpleTimeZone(0, "a", 0, 1, 0, 0, 3, 2, 0, 0);
        SimpleTimeZone tzA = new SimpleTimeZone(0, "A", 0, 1, 0, 0, 3, 2, 0, 0);
        // tzb differs from tza
        SimpleTimeZone tzb = new SimpleTimeZone(0, "b", 0, 1, 0, 0, 3, 1, 0, 0);
        if (tz1.useDaylightTime() || tz2.useDaylightTime() ||
            !tza.useDaylightTime() || !tzA.useDaylightTime() ||
            !tzb.useDaylightTime()) {
            fail("Test is broken -- rewrite it");
        }
        if (!tza.hasSameRules(tzA) || tza.hasSameRules(tzb)) {
            fail("Fail: hasSameRules() broken for zones with rules");
        }
        if (!tz1.hasSameRules(tz2)) {
            fail("Fail: hasSameRules() returns false for zones without rules");
            fail("zone 1 = " + tz1);
            fail("zone 2 = " + tz2);
        }
    }

    /**
     * SimpleTimeZone constructors, setStartRule(), and setEndRule() don't
     * check for out-of-range arguments.
     */
    @Test
    public void Test4154542() {
        final int GOOD = 1;
        final int BAD  = 0;

        final int GOOD_MONTH       = Calendar.JANUARY;
        final int GOOD_DAY         = 1;
        final int GOOD_DAY_OF_WEEK = Calendar.SUNDAY;
        final int GOOD_TIME        = 0;

        int[] DATA = {
            GOOD, Integer.MIN_VALUE,    0,  Integer.MAX_VALUE,   Integer.MIN_VALUE,
            GOOD, Calendar.JANUARY,    -5,  Calendar.SUNDAY,     0,
            GOOD, Calendar.DECEMBER,    5,  Calendar.SATURDAY,   24*60*60*1000-1,
            GOOD, Calendar.DECEMBER,    5,  Calendar.SATURDAY,   24*60*60*1000,
            BAD,  Calendar.DECEMBER,    5,  Calendar.SATURDAY,   24*60*60*1000+1,
            BAD,  Calendar.DECEMBER,    5,  Calendar.SATURDAY,  -1,
            BAD,  Calendar.JANUARY,    -6,  Calendar.SUNDAY,     0,
            BAD,  Calendar.DECEMBER,    6,  Calendar.SATURDAY,   24*60*60*1000,
            GOOD, Calendar.DECEMBER,    1,  0,                   0,
            GOOD, Calendar.DECEMBER,   31,  0,                   0,
            BAD,  Calendar.APRIL,      31,  0,                   0,
            BAD,  Calendar.DECEMBER,   32,  0,                   0,
            BAD,  Calendar.JANUARY-1,   1,  Calendar.SUNDAY,     0,
            BAD,  Calendar.DECEMBER+1,  1,  Calendar.SUNDAY,     0,
            GOOD, Calendar.DECEMBER,   31, -Calendar.SUNDAY,     0,
            GOOD, Calendar.DECEMBER,   31, -Calendar.SATURDAY,   0,
            BAD,  Calendar.DECEMBER,   32, -Calendar.SATURDAY,   0,
            BAD,  Calendar.DECEMBER,  -32, -Calendar.SATURDAY,   0,
            BAD,  Calendar.DECEMBER,   31, -Calendar.SATURDAY-1, 0,
        };
        SimpleTimeZone zone = new SimpleTimeZone(0, "Z");
        for (int i=0; i<DATA.length; i+=5) {
            boolean shouldBeGood = (DATA[i] == GOOD);
            int month     = DATA[i+1];
            int day       = DATA[i+2];
            int dayOfWeek = DATA[i+3];
            int time      = DATA[i+4];

            Exception ex = null;
            try {
                zone.setStartRule(month, day, dayOfWeek, time);
            } catch (IllegalArgumentException e) {
                ex = e;
            }
            if ((ex == null) != shouldBeGood) {
                fail("setStartRule(month=" + month + ", day=" + day +
                      ", dayOfWeek=" + dayOfWeek + ", time=" + time +
                      (shouldBeGood ? (") should work but throws " + ex)
                       : ") should fail but doesn't"));
            }

            ex = null;
            try {
                zone.setEndRule(month, day, dayOfWeek, time);
            } catch (IllegalArgumentException e) {
                ex = e;
            }
            if ((ex == null) != shouldBeGood) {
                fail("setEndRule(month=" + month + ", day=" + day +
                      ", dayOfWeek=" + dayOfWeek + ", time=" + time +
                      (shouldBeGood ? (") should work but throws " + ex)
                       : ") should fail but doesn't"));
            }

            ex = null;
            try {
                SimpleTimeZone temp = new SimpleTimeZone(0, "Z",
                        month, day, dayOfWeek, time,
                        GOOD_MONTH, GOOD_DAY, GOOD_DAY_OF_WEEK, GOOD_TIME);
            } catch (IllegalArgumentException e) {
                ex = e;
            }
            if ((ex == null) != shouldBeGood) {
                fail("SimpleTimeZone(month=" + month + ", day=" + day +
                      ", dayOfWeek=" + dayOfWeek + ", time=" + time +
                      (shouldBeGood ? (", <end>) should work but throws " + ex)
                       : ", <end>) should fail but doesn't"));
            }

            ex = null;
            try {
                SimpleTimeZone temp = new SimpleTimeZone(0, "Z",
                        GOOD_MONTH, GOOD_DAY, GOOD_DAY_OF_WEEK, GOOD_TIME,
                        month, day, dayOfWeek, time);
            } catch (IllegalArgumentException e) {
                ex = e;
            }
            if ((ex == null) != shouldBeGood) {
                fail("SimpleTimeZone(<start>, month=" + month + ", day=" + day +
                      ", dayOfWeek=" + dayOfWeek + ", time=" + time +
                      (shouldBeGood ? (") should work but throws " + ex)
                       : ") should fail but doesn't"));
            }
        }
    }

    /**
     * SimpleTimeZone.getOffset accepts illegal arguments.
     */
    @Test
    public void Test4154650() {
        final int GOOD=1, BAD=0;
        final int GOOD_ERA=GregorianCalendar.AD, GOOD_YEAR=1998, GOOD_MONTH=Calendar.AUGUST;
        final int GOOD_DAY=2, GOOD_DOW=Calendar.SUNDAY, GOOD_TIME=16*3600000;
        int[] DATA = {
            GOOD, GOOD_ERA, GOOD_YEAR, GOOD_MONTH, GOOD_DAY, GOOD_DOW, GOOD_TIME,

            GOOD, GregorianCalendar.BC, GOOD_YEAR, GOOD_MONTH, GOOD_DAY, GOOD_DOW, GOOD_TIME,
            GOOD, GregorianCalendar.AD, GOOD_YEAR, GOOD_MONTH, GOOD_DAY, GOOD_DOW, GOOD_TIME,
            BAD,  GregorianCalendar.BC-1, GOOD_YEAR, GOOD_MONTH, GOOD_DAY, GOOD_DOW, GOOD_TIME,
            BAD,  GregorianCalendar.AD+1, GOOD_YEAR, GOOD_MONTH, GOOD_DAY, GOOD_DOW, GOOD_TIME,

            GOOD, GOOD_ERA, GOOD_YEAR, Calendar.JANUARY, GOOD_DAY, GOOD_DOW, GOOD_TIME,
            GOOD, GOOD_ERA, GOOD_YEAR, Calendar.DECEMBER, GOOD_DAY, GOOD_DOW, GOOD_TIME,
            BAD,  GOOD_ERA, GOOD_YEAR, Calendar.JANUARY-1, GOOD_DAY, GOOD_DOW, GOOD_TIME,
            BAD,  GOOD_ERA, GOOD_YEAR, Calendar.DECEMBER+1, GOOD_DAY, GOOD_DOW, GOOD_TIME,

            GOOD, GOOD_ERA, GOOD_YEAR, Calendar.JANUARY, 1, GOOD_DOW, GOOD_TIME,
            GOOD, GOOD_ERA, GOOD_YEAR, Calendar.JANUARY, 31, GOOD_DOW, GOOD_TIME,
            BAD,  GOOD_ERA, GOOD_YEAR, Calendar.JANUARY, 0, GOOD_DOW, GOOD_TIME,
            BAD,  GOOD_ERA, GOOD_YEAR, Calendar.JANUARY, 32, GOOD_DOW, GOOD_TIME,

            GOOD, GOOD_ERA, GOOD_YEAR, GOOD_MONTH, GOOD_DAY, Calendar.SUNDAY, GOOD_TIME,
            GOOD, GOOD_ERA, GOOD_YEAR, GOOD_MONTH, GOOD_DAY, Calendar.SATURDAY, GOOD_TIME,
            BAD,  GOOD_ERA, GOOD_YEAR, GOOD_MONTH, GOOD_DAY, Calendar.SUNDAY-1, GOOD_TIME,
            BAD,  GOOD_ERA, GOOD_YEAR, GOOD_MONTH, GOOD_DAY, Calendar.SATURDAY+1, GOOD_TIME,

            GOOD, GOOD_ERA, GOOD_YEAR, GOOD_MONTH, GOOD_DAY, GOOD_DOW, 0,
            GOOD, GOOD_ERA, GOOD_YEAR, GOOD_MONTH, GOOD_DAY, GOOD_DOW, 24*3600000-1,
            BAD,  GOOD_ERA, GOOD_YEAR, GOOD_MONTH, GOOD_DAY, GOOD_DOW, -1,
            BAD,  GOOD_ERA, GOOD_YEAR, GOOD_MONTH, GOOD_DAY, GOOD_DOW, 24*3600000,
        };

        TimeZone tz = TimeZone.getDefault();
        for (int i=0; i<DATA.length; i+=7) {
            boolean good = DATA[i] == GOOD;
            IllegalArgumentException e = null;
            try {
                int offset = tz.getOffset(DATA[i+1], DATA[i+2], DATA[i+3],
                                          DATA[i+4], DATA[i+5], DATA[i+6]);
           } catch (IllegalArgumentException ex) {
                e = ex;
            }
            if (good != (e == null)) {
                fail("Fail: getOffset(" +
                      DATA[i+1] + ", " + DATA[i+2] + ", " + DATA[i+3] + ", " +
                      DATA[i+4] + ", " + DATA[i+5] + ", " + DATA[i+6] +
                      (good ? (") threw " + e) : ") accepts invalid args"));
            }
        }
    }

    /**
     * TimeZone constructors allow null IDs.
     */
    @Test
    public void Test4159922() {
        TimeZone z = null;

        // TimeZone API.  Only hasSameRules() and setDefault() should
        // allow null.
        try {
            z = TimeZone.getTimeZone((String)null);
            fail("FAIL: Null allowed in getTimeZone");
        } catch (NullPointerException e) {}
        z = TimeZone.getTimeZone("GMT");
        try {
            z.getDisplayName(false, TimeZone.SHORT, null);
            fail("FAIL: Null allowed in getDisplayName(3)");
        } catch (NullPointerException e) {}
        try {
            z.getDisplayName(null);
            fail("FAIL: Null allowed in getDisplayName(1)");
        } catch (NullPointerException e) {}
        try {
            if (z.hasSameRules(null)) {
                fail("FAIL: hasSameRules returned true");
            }
        } catch (NullPointerException e) {
            fail("FAIL: Null NOT allowed in hasSameRules");
        }
        try {
            z.inDaylightTime(null);
            fail("FAIL: Null allowed in inDaylightTime");
        } catch (NullPointerException e) {}
        try {
            z.setID(null);
            fail("FAIL: Null allowed in setID");
        } catch (NullPointerException e) {}

        TimeZone save = TimeZone.getDefault();
        try {
            TimeZone.setDefault(null);
        } catch (NullPointerException e) {
            fail("FAIL: Null NOT allowed in setDefault");
        } finally {
            TimeZone.setDefault(save);
        }

        // SimpleTimeZone API
        SimpleTimeZone s = null;
        try {
            s = new SimpleTimeZone(0, null);
            fail("FAIL: Null allowed in SimpleTimeZone(2)");
        } catch (NullPointerException e) {}
        try {
            s = new SimpleTimeZone(0, null, 0, 1, 0, 0, 0, 1, 0, 0);
            fail("FAIL: Null allowed in SimpleTimeZone(10)");
        } catch (NullPointerException e) {}
        try {
            s = new SimpleTimeZone(0, null, 0, 1, 0, 0, 0, 1, 0, 0, 1000);
            fail("FAIL: Null allowed in SimpleTimeZone(11)");
        } catch (NullPointerException e) {}
    }

    /**
     * TimeZone broken at midnight.  The TimeZone code fails to handle
     * transitions at midnight correctly.
     */
    @SuppressWarnings("deprecation")
    @Test
    public void Test4162593() {
        SimpleDateFormat fmt = new SimpleDateFormat("z", Locale.US);
        final int ONE_HOUR = 60*60*1000;
        TimeZone initialZone = TimeZone.getDefault();

        SimpleTimeZone asuncion = new SimpleTimeZone(-4*ONE_HOUR, "America/Asuncion" /*PY%sT*/,
            Calendar.OCTOBER, 1, 0 /*DOM*/, 0*ONE_HOUR,
            Calendar.MARCH, 1, 0 /*DOM*/, 0*ONE_HOUR, 1*ONE_HOUR);

        /* Zone
         * Starting time
         * Transition expected between start+1H and start+2H
         */
        Object[] DATA = {
            new SimpleTimeZone(2*ONE_HOUR, "Asia/Damascus" /*EE%sT*/,
                Calendar.APRIL, 1, 0 /*DOM*/, 0*ONE_HOUR,
                Calendar.OCTOBER, 1, 0 /*DOM*/, 0*ONE_HOUR, 1*ONE_HOUR),
            new int[] {98, Calendar.SEPTEMBER, 30, 22, 0},
            Boolean.TRUE,

            asuncion,
            new int[] {100, Calendar.FEBRUARY, 28, 22, 0},
            Boolean.FALSE,

            asuncion,
            new int[] {100, Calendar.FEBRUARY, 29, 22, 0},
            Boolean.TRUE,
        };

        String[] zone = new String[4];

        try {
            for (int j=0; j<DATA.length; j+=3) {
                TimeZone tz = (TimeZone)DATA[j];
                TimeZone.setDefault(tz);
                fmt.setTimeZone(tz);

                // Must construct the Date object AFTER setting the default zone
                int[] p = (int[])DATA[j+1];
                Date d = new Date(p[0], p[1], p[2], p[3], p[4]);
                boolean transitionExpected = ((Boolean)DATA[j+2]).booleanValue();

                System.out.println(tz.getID() + ":");
                for (int i=0; i<4; ++i) {
                    zone[i] = fmt.format(d);
                    System.out.println("" + i + ": " + d);
                    d = new Date(d.getTime() + ONE_HOUR);
                }
                if (zone[0].equals(zone[1]) &&
                    (zone[1].equals(zone[2]) != transitionExpected) &&
                    zone[2].equals(zone[3])) {
                    System.out.println("Ok: transition " + transitionExpected);
                } else {
                    fail("Fail: boundary transition incorrect");
                }
            }
        }
        finally {
            // restore the initial time zone so that this test case
            // doesn't affect the others.
            TimeZone.setDefault(initialZone);
        }
    }

    /**
     * TimeZone broken in last hour of year
     */
    @Test
    public void Test4173604() {
        // test both SimpleTimeZone and ZoneInfo objects.
        // @since 1.4
        sub4173604(getPST());
        sub4173604(TimeZone.getTimeZone("PST"));
    }

    private void sub4173604(TimeZone pst) {
        int o22 = pst.getOffset(1, 1998, 11, 31, Calendar.THURSDAY, 22*60*60*1000);
        int o23 = pst.getOffset(1, 1998, 11, 31, Calendar.THURSDAY, 23*60*60*1000);
        int o00 = pst.getOffset(1, 1999, 0, 1, Calendar.FRIDAY, 0);
        if (o22 != o23 || o22 != o00) {
            fail("Offsets should be the same (for PST), but got: " +
                  "12/31 22:00 " + o22 +
                  ", 12/31 23:00 " + o23 +
                  ", 01/01 00:00 " + o00);
        }

        GregorianCalendar cal = new GregorianCalendar();
        cal.setTimeZone(pst);
        cal.clear();
        cal.set(1998, Calendar.JANUARY, 1);
        int lastDST = cal.get(Calendar.DST_OFFSET);
        int transitions = 0;
        int delta = 5;
        while (cal.get(Calendar.YEAR) < 2000) {
            cal.add(Calendar.MINUTE, delta);
            if (cal.get(Calendar.DST_OFFSET) != lastDST) {
                ++transitions;
                Calendar t = (Calendar)cal.clone();
                t.add(Calendar.MINUTE, -delta);
                System.out.println(t.getTime() + "  " + t.get(Calendar.DST_OFFSET));
                System.out.println(cal.getTime() + "  " + (lastDST=cal.get(Calendar.DST_OFFSET)));
            }
        }
        if (transitions != 4) {
            fail("Saw " + transitions + " transitions; should have seen 4");
        }
    }

    /**
     * getDisplayName doesn't work with unusual savings/offsets.
     */
    @SuppressWarnings("deprecation")
    @Test
    public void Test4176686() {
        // Construct a zone that does not observe DST but
        // that does have a DST savings (which should be ignored).
        int offset = 90 * 60000; // 1:30
        SimpleTimeZone z1 = new SimpleTimeZone(offset, "_std_zone_");
        z1.setDSTSavings(45 * 60000); // 0:45

        // Construct a zone that observes DST for the first 6 months.
        SimpleTimeZone z2 = new SimpleTimeZone(offset, "_dst_zone_");
        z2.setDSTSavings(45 * 60000); // 0:45
        z2.setStartRule(Calendar.JANUARY, 1, 0);
        z2.setEndRule(Calendar.JULY, 1, 0);

        // Also check DateFormat
        DateFormat fmt1 = new SimpleDateFormat("z");
        fmt1.setTimeZone(z1); // Format uses standard zone
        DateFormat fmt2 = new SimpleDateFormat("z");
        fmt2.setTimeZone(z2); // Format uses DST zone
        Date dst = new Date(1970-1900, Calendar.FEBRUARY, 1); // Time in DST
        Date std = new Date(1970-1900, Calendar.AUGUST, 1); // Time in standard

        // Description, Result, Expected Result
        String[] DATA = {
            "getDisplayName(false, SHORT)/std zone",
            z1.getDisplayName(false, TimeZone.SHORT), "GMT+01:30",
            "getDisplayName(false, LONG)/std zone",
            z1.getDisplayName(false, TimeZone.LONG ), "GMT+01:30",
            "getDisplayName(true, SHORT)/std zone",
            z1.getDisplayName(true, TimeZone.SHORT), "GMT+01:30",
            "getDisplayName(true, LONG)/std zone",
            z1.getDisplayName(true, TimeZone.LONG ), "GMT+01:30",
            "getDisplayName(false, SHORT)/dst zone",
            z2.getDisplayName(false, TimeZone.SHORT), "GMT+01:30",
            "getDisplayName(false, LONG)/dst zone",
            z2.getDisplayName(false, TimeZone.LONG ), "GMT+01:30",
            "getDisplayName(true, SHORT)/dst zone",
            z2.getDisplayName(true, TimeZone.SHORT), "GMT+02:15",
            "getDisplayName(true, LONG)/dst zone",
            z2.getDisplayName(true, TimeZone.LONG ), "GMT+02:15",
            "DateFormat.format(std)/std zone", fmt1.format(std), "GMT+01:30",
            "DateFormat.format(dst)/std zone", fmt1.format(dst), "GMT+01:30",
            "DateFormat.format(std)/dst zone", fmt2.format(std), "GMT+01:30",
            "DateFormat.format(dst)/dst zone", fmt2.format(dst), "GMT+02:15",
        };

        for (int i=0; i<DATA.length; i+=3) {
            if (!DATA[i+1].equals(DATA[i+2])) {
                fail("FAIL: " + DATA[i] + " -> " + DATA[i+1] + ", exp " + DATA[i+2]);
            }
        }
    }

    /**
     * SimpleTimeZone allows invalid DOM values.
     */
    @Test
    public void Test4184229() {
        SimpleTimeZone zone = null;
        try {
            zone = new SimpleTimeZone(0, "A", 0, -1, 0, 0, 0, 0, 0, 0);
            fail("Failed. No exception has been thrown for DOM -1 startDay");
        } catch(IllegalArgumentException e) {
            System.out.println("(a) " + e.getMessage());
        }
        try {
            zone = new SimpleTimeZone(0, "A", 0, 0, 0, 0, 0, -1, 0, 0);
            fail("Failed. No exception has been thrown for DOM -1 endDay");
        } catch(IllegalArgumentException e) {
            System.out.println("(b) " + e.getMessage());
        }
        try {
            zone = new SimpleTimeZone(0, "A", 0, -1, 0, 0, 0, 0, 0, 0, 1000);
            fail("Failed. No exception has been thrown for DOM -1 startDay +savings");
        } catch(IllegalArgumentException e) {
            System.out.println("(c) " + e.getMessage());
        }
        try {
            zone = new SimpleTimeZone(0, "A", 0, 0, 0, 0, 0, -1, 0, 0, 1000);
            fail("Failed. No exception has been thrown for DOM -1 endDay +savings");
        } catch(IllegalArgumentException e) {
            System.out.println("(d) " + e.getMessage());
        }
        // Make a valid constructor call for subsequent tests.
        zone = new SimpleTimeZone(0, "A", 0, 1, 0, 0, 0, 1, 0, 0);
        try {
            zone.setStartRule(0, -1, 0, 0);
            fail("Failed. No exception has been thrown for DOM -1 setStartRule +savings");
        } catch(IllegalArgumentException e) {
            System.out.println("(e) " + e.getMessage());
        }
        try {
            zone.setStartRule(0, -1, 0);
            fail("Failed. No exception has been thrown for DOM -1 setStartRule");
        } catch(IllegalArgumentException e) {
            System.out.println("(f) " + e.getMessage());
        }
        try {
            zone.setEndRule(0, -1, 0, 0);
            fail("Failed. No exception has been thrown for DOM -1 setEndRule +savings");
        } catch(IllegalArgumentException e) {
            System.out.println("(g) " + e.getMessage());
        }
        try {
            zone.setEndRule(0, -1, 0);
            fail("Failed. No exception has been thrown for DOM -1 setEndRule");
        } catch(IllegalArgumentException e) {
            System.out.println("(h) " + e.getMessage());
        }
    }

    /**
     * SimpleTimeZone.getOffset() throws IllegalArgumentException when to get
     * of 2/29/1996 (leap day).
     */
    @Test
    public void Test4208960 () {
        // test both SimpleTimeZone and ZoneInfo objects.
        // @since 1.4
        sub4208960(getPST());
        sub4208960(TimeZone.getTimeZone("PST"));
    }

    private void sub4208960(TimeZone tz) {
        try {
            int offset = tz.getOffset(GregorianCalendar.AD, 1996, Calendar.FEBRUARY, 29,
                                      Calendar.THURSDAY, 0);
        } catch (IllegalArgumentException e) {
            fail("FAILED: to get TimeZone.getOffset(2/29/96)");
        }
        try {
            int offset = tz.getOffset(GregorianCalendar.AD, 1997, Calendar.FEBRUARY, 29,
                                      Calendar.THURSDAY, 0);
            fail("FAILED: TimeZone.getOffset(2/29/97) expected to throw Exception.");
        } catch (IllegalArgumentException e) {
            System.out.println("got IllegalArgumentException");
        }
    }

    /**
     * 4966229: java.util.Date methods may works incorrect.
     * sun.util.calendar.ZoneInfo doesn't clone properly.
     */
    @SuppressWarnings("deprecation")
    @Test
    public void Test4966229() {
        TimeZone savedTZ = TimeZone.getDefault();
        try {
            TimeZone.setDefault(TimeZone.getTimeZone("GMT"));
            Date d = new Date(2100-1900, 5, 1); // specify year >2037
            TimeZone tz = TimeZone.getTimeZone("America/Los_Angeles");

            Calendar cal = new GregorianCalendar(tz);
            cal.setTime(d);

            // Change the raw offset in tz
            int offset = tz.getRawOffset();
            tz.setRawOffset(0);

            TimeZone tz2 = (TimeZone) tz.clone();
            Calendar cal2 = new GregorianCalendar(tz2);
            cal2.setTime(d);
            int expectedHourOfDay = cal2.get(cal.HOUR_OF_DAY);

            // Restore the GMT offset in tz which shouldn't affect tz2
            tz.setRawOffset(offset);
            cal2.setTime(d);
            int hourOfDay = cal2.get(cal.HOUR_OF_DAY);
            if (hourOfDay != expectedHourOfDay) {
                fail("wrong hour of day: got: " + hourOfDay
                      + ", expected: " + expectedHourOfDay);
            }
        } finally {
            TimeZone.setDefault(savedTZ);
        }
    }

    /**
     * 6433179: (tz) Incorrect DST end for America/Winnipeg and Canada/Central in 2038+
     */
    @Test
    public void Test6433179() {
        // Use the old America/Winnipeg rule for testing. Note that
        // startMode is WALL_TIME for testing. It's actually
        // STANDARD_TIME, though.
        //Rule  Winn    1966    2005    -       Oct     lastSun 2:00s   0       S
        //Rule  Winn    1987    2005    -       Apr     Sun>=1  2:00s   1:00    D
        TimeZone tz = new SimpleTimeZone(-6*ONE_HOUR, "America/Winnipeg",
          Calendar.APRIL, 1, -Calendar.SUNDAY, 2*ONE_HOUR, SimpleTimeZone.WALL_TIME,
          Calendar.OCTOBER, -1, Calendar.SUNDAY, 2*ONE_HOUR, SimpleTimeZone.STANDARD_TIME,
          1*ONE_HOUR);
        Calendar cal = Calendar.getInstance(tz, Locale.US);
        cal.clear();
        cal.set(2039, Calendar.OCTOBER, 1);
        cal.getTime();
        cal.set(cal.DAY_OF_WEEK, cal.SUNDAY);
        cal.set(cal.DAY_OF_WEEK_IN_MONTH, -1);
        cal.add(Calendar.HOUR_OF_DAY, 2);
        if (cal.get(cal.DST_OFFSET) == 0) {
            fail("Should still be in DST.");
        }
    }

    private static final int ONE_HOUR = 60 * 60 * 1000;
    /**
     * Returns an instance of SimpleTimeZone for
     * "PST". (TimeZone.getTimeZone() no longer returns a
     * SimpleTimeZone object.)
     * @since 1.4
     */
    private SimpleTimeZone getPST() {
        return new SimpleTimeZone(-8*ONE_HOUR, "PST",
                                  Calendar.APRIL, 1, -Calendar.SUNDAY, 2*ONE_HOUR,
                                  Calendar.OCTOBER, -1, Calendar.SUNDAY, 2*ONE_HOUR,
                                  1*ONE_HOUR);
    }
}
//eof
