1/* GENERATED SOURCE. DO NOT MODIFY. */
2// © 2016 and later: Unicode, Inc. and others.
3// License & terms of use: http://www.unicode.org/copyright.html#License
4/**
5 *******************************************************************************
6 * Copyright (C) 2000-2015, International Business Machines Corporation and
7 * others. All Rights Reserved.
8 *******************************************************************************
9 */
10package android.icu.dev.test.timezone;
11import java.util.Date;
12
13import org.junit.Test;
14import org.junit.runner.RunWith;
15import org.junit.runners.JUnit4;
16
17import android.icu.dev.test.TestFmwk;
18import android.icu.dev.test.TestUtil;
19import android.icu.dev.test.TestUtil.JavaVendor;
20import android.icu.text.DateFormat;
21import android.icu.util.Calendar;
22import android.icu.util.SimpleTimeZone;
23import android.icu.util.TimeZone;
24import android.icu.testsharding.MainTestShard;
25
26/**
27 * A test which discovers the boundaries of DST programmatically and verifies
28 * that they are correct.
29 */
30@MainTestShard
31@RunWith(JUnit4.class)
32public class TimeZoneBoundaryTest extends TestFmwk
33{
34    static final int ONE_SECOND = 1000;
35    static final int ONE_MINUTE = 60*ONE_SECOND;
36    static final int ONE_HOUR = 60*ONE_MINUTE;
37    static final long ONE_DAY = 24*ONE_HOUR;
38    static final long ONE_YEAR = (long)(365.25 * ONE_DAY);
39    static final long SIX_MONTHS = ONE_YEAR / 2;
40
41    static final int MONTH_LENGTH[] = {31,29,31,30,31,30,31,31,30,31,30,31};
42
43    // These values are empirically determined to be correct
44    static final long PST_1997_BEG  = 860320800000L;
45    static final long PST_1997_END  = 877856400000L;
46
47    // Minimum interval for binary searches in ms; should be no larger
48    // than 1000.
49    static final long INTERVAL = 10; // Milliseconds
50
51    // [3Jan01 Liu] Updated for 2000f data
52    static final String AUSTRALIA = "Australia/Adelaide";
53    static final long AUSTRALIA_1997_BEG = 877797000000L;
54    static final long AUSTRALIA_1997_END = 859653000000L;
55
56    /**
57     * Date.toString().substring() Boundary Test
58     * Look for a DST changeover to occur within 6 months of the given Date.
59     * The initial Date.toString() should yield a string containing the
60     * startMode as a SUBSTRING.  The boundary will be tested to be
61     * at the expectedBoundary value.
62     */
63    void findDaylightBoundaryUsingDate(Date d, String startMode, long expectedBoundary)
64    {
65        // Given a date with a year start, find the Daylight onset
66        // and end.  The given date should be 1/1/xx in some year.
67
68        if (d.toString().indexOf(startMode) == -1)
69        {
70            logln("Error: " + startMode + " not present in " + d);
71        }
72
73        // Use a binary search, assuming that we have a Standard
74        // time at the midpoint.
75        long min = d.getTime();
76        long max = min + SIX_MONTHS;
77
78        while ((max - min) >  INTERVAL)
79        {
80            long mid = (min + max) >> 1;
81            String s = new Date(mid).toString();
82            // logln(s);
83            if (s.indexOf(startMode) != -1)
84            {
85                min = mid;
86            }
87            else
88            {
89                max = mid;
90            }
91        }
92
93        logln("Date Before: " + showDate(min));
94        logln("Date After:  " + showDate(max));
95        long mindelta = expectedBoundary - min;
96        // not used long maxdelta = max - expectedBoundary;
97        if (mindelta >= 0 && mindelta <= INTERVAL &&
98            mindelta >= 0 && mindelta <= INTERVAL)
99            logln("PASS: Expected boundary at " + expectedBoundary);
100        else
101            errln("FAIL: Expected boundary at " + expectedBoundary);
102    }
103
104    // This test cannot be compiled until the inDaylightTime() method of GregorianCalendar
105    // becomes public.
106    //    static void findDaylightBoundaryUsingCalendar(Date d, boolean startsInDST)
107    //    {
108    //  // Given a date with a year start, find the Daylight onset
109    //  // and end.  The given date should be 1/1/xx in some year.
110    //
111    //  GregorianCalendar cal = new GregorianCalendar();
112    //  cal.setTime(d);
113    //  if (cal.inDaylightTime() != startsInDST)
114    //  {
115    //      logln("Error: inDaylightTime(" + d + ") != " + startsInDST);
116    //  }
117    //
118    //  // Use a binary search, assuming that we have a Standard
119    //  // time at the midpoint.
120    //  long min = d.getTime();
121    //  long max = min + (long)(365.25 / 2 * 24*60*60*1000);
122    //
123    //  while ((max - min) >  INTERVAL)
124    //  {
125    //      long mid = (min + max) >> 1;
126    //      cal.setTime(new Date(mid));
127    //      if (cal.inDaylightTime() == startsInDST)
128    //      {
129    //      min = mid;
130    //      }
131    //      else
132    //      {
133    //      max = mid;
134    //      }
135    //  }
136    //
137    //  logln("Calendar Before: " + showDate(min));
138    //  logln("Calendar After:  " + showDate(max));
139    //    }
140
141    void findDaylightBoundaryUsingTimeZone(Date d, boolean startsInDST, long expectedBoundary)
142    {
143        findDaylightBoundaryUsingTimeZone(d, startsInDST, expectedBoundary,
144                                          TimeZone.getDefault());
145    }
146
147    void findDaylightBoundaryUsingTimeZone(Date d, boolean startsInDST,
148                                           long expectedBoundary, TimeZone tz)
149    {
150        // Given a date with a year start, find the Daylight onset
151        // and end.  The given date should be 1/1/xx in some year.
152
153        // Use a binary search, assuming that we have a Standard
154        // time at the midpoint.
155        long min = d.getTime();
156        long max = min + SIX_MONTHS;
157
158        if (tz.inDaylightTime(d) != startsInDST)
159        {
160            errln("FAIL: " + tz.getID() + " inDaylightTime(" +
161                  d + ") != " + startsInDST);
162            startsInDST = !startsInDST; // Flip over; find the apparent value
163        }
164
165        if (tz.inDaylightTime(new Date(max)) == startsInDST)
166        {
167            errln("FAIL: " + tz.getID() + " inDaylightTime(" +
168                  (new Date(max)) + ") != " + (!startsInDST));
169            return;
170        }
171
172        while ((max - min) >  INTERVAL)
173        {
174            long mid = (min + max) >> 1;
175            boolean isIn = tz.inDaylightTime(new Date(mid));
176            if (isIn == startsInDST)
177            {
178                min = mid;
179            }
180            else
181            {
182                max = mid;
183            }
184        }
185
186        logln(tz.getID() + " Before: " + showDate(min, tz));
187        logln(tz.getID() + " After:  " + showDate(max, tz));
188
189        long mindelta = expectedBoundary - min;
190        // not used long maxdelta = max - expectedBoundary;
191        DateFormat fmt = DateFormat.getDateTimeInstance(DateFormat.LONG, DateFormat.LONG);
192        fmt.setTimeZone(tz);
193        if (mindelta >= 0 && mindelta <= INTERVAL &&
194            mindelta >= 0 && mindelta <= INTERVAL)
195            logln("PASS: Expected boundary at " + expectedBoundary + " = " + fmt.format(new Date(expectedBoundary)));
196        else
197            errln("FAIL: Expected boundary at " + expectedBoundary + " = " + fmt.format(new Date(expectedBoundary)));
198    }
199
200    private static String showDate(long l)
201    {
202        return showDate(new Date(l));
203    }
204
205    private static String showDate(Date d)
206    {
207        java.util.Calendar cal = java.util.Calendar.getInstance();
208        cal.setTime(d);
209        return "" + (cal.get(Calendar.YEAR) - 1900) + "/" +
210               showNN(cal.get(Calendar.MONTH) + 1) + "/" +
211               showNN(cal.get(Calendar.DAY_OF_MONTH)) + " " +
212               showNN(cal.get(Calendar.HOUR_OF_DAY)) + ":"
213               + showNN(cal.get(Calendar.MINUTE)) + " \"" + d + "\" = " +
214               d.getTime();
215    }
216
217    private static String showDate(long l, TimeZone z)
218    {
219        return showDate(new Date(l), z);
220    }
221
222    private static String showDate(Date d, TimeZone zone)
223    {
224        DateFormat fmt = DateFormat.getDateTimeInstance(DateFormat.LONG, DateFormat.LONG);
225        fmt.setTimeZone(zone);
226        java.util.Calendar cal = java.util.Calendar.getInstance();
227        cal.setTime(d);
228        return "" + (cal.get(Calendar.YEAR) - 1900) + "/" +
229               showNN(cal.get(Calendar.MONTH) + 1) + "/" +
230               showNN(cal.get(Calendar.DAY_OF_MONTH)) + " " +
231               showNN(cal.get(Calendar.HOUR_OF_DAY)) + ":" +
232               showNN(cal.get(Calendar.MINUTE)) + " \"" + d + "\" = " +
233               fmt.format(d) + " = " + d.getTime();
234    }
235
236    private static String showNN(int n)
237    {
238        return ((n < 10) ? "0" : "") + n;
239    }
240
241    /**
242     * Given a date, a TimeZone, and expected values for inDaylightTime,
243     * useDaylightTime, zone and DST offset, verify that this is the case.
244     */
245    void verifyDST(String tag, Calendar cal, TimeZone time_zone,
246                   boolean expUseDaylightTime, boolean expInDaylightTime,
247                   int expRawOffset, int expOffset)
248    {
249        Date d = cal.getTime();
250
251        logln("-- " + tag + ": " + d +
252              " in zone " + time_zone.getID() + " (" +
253              d.getTime()/3600000.0 + ")");
254
255        if (time_zone.inDaylightTime(d) == expInDaylightTime)
256            logln("PASS: inDaylightTime = " + time_zone.inDaylightTime(d));
257        else
258            errln("FAIL: inDaylightTime = " + time_zone.inDaylightTime(d));
259
260        if (time_zone.useDaylightTime() == expUseDaylightTime)
261            logln("PASS: useDaylightTime = " + time_zone.useDaylightTime());
262        else
263            errln("FAIL: useDaylightTime = " + time_zone.useDaylightTime());
264
265        if (time_zone.getRawOffset() == expRawOffset)
266            logln("PASS: getRawOffset() = " + expRawOffset/(double)ONE_HOUR);
267        else
268            errln("FAIL: getRawOffset() = " + time_zone.getRawOffset()/(double)ONE_HOUR +
269                  "; expected " + expRawOffset/(double)ONE_HOUR);
270
271        //GregorianCalendar gc = new GregorianCalendar(time_zone);
272        //gc.setTime(d);
273        int offset = time_zone.getOffset(cal.get(Calendar.ERA), cal.get(Calendar.YEAR), cal.get(Calendar.MONTH),
274                                         cal.get(Calendar.DAY_OF_MONTH), cal.get(Calendar.DAY_OF_WEEK),
275                                         ((cal.get(Calendar.HOUR_OF_DAY) * 60 +
276                                           cal.get(Calendar.MINUTE)) * 60 +
277                                          cal.get(Calendar.SECOND)) * 1000 +
278                                         cal.get(Calendar.MILLISECOND));
279        if (offset == expOffset)
280            logln("PASS: getOffset() = " + offset/(double)ONE_HOUR);
281        else {
282            logln("era=" + cal.get(Calendar.ERA) +
283                  ", year=" + cal.get(Calendar.YEAR) +
284                  ", month=" + cal.get(Calendar.MONTH) +
285                  ", dom=" + cal.get(Calendar.DAY_OF_MONTH) +
286                  ", dow=" + cal.get(Calendar.DAY_OF_WEEK) +
287                  ", time-of-day=" + (((cal.get(Calendar.HOUR_OF_DAY) * 60 +
288                               cal.get(Calendar.MINUTE)) * 60 +
289                              cal.get(Calendar.SECOND)) * 1000 +
290                             cal.get(Calendar.MILLISECOND)) / 3600000.0 +
291                            " hours");
292            errln("FAIL: getOffset() = " + offset/(double)ONE_HOUR +
293                  "; expected " + expOffset/(double)ONE_HOUR);
294        }
295    }
296
297    /**
298     * Check that the given year/month/dom/hour maps to and from the
299     * given epochHours.  This verifies the functioning of the
300     * calendar and time zone in conjunction with one another,
301     * including the calendar time->fields and fields->time and
302     * the time zone getOffset method.
303     *
304     * @param epochHours hours after Jan 1 1970 0:00 GMT.
305     */
306    void verifyMapping(Calendar cal, int year, int month, int dom, int hour,
307                       double epochHours) {
308        double H = 3600000.0;
309        cal.clear();
310        cal.set(year, month, dom, hour, 0, 0);
311        Date d = cal.getTime();
312        double e = d.getTime() / H;
313        Date ed = new Date((long)(epochHours * H));
314        if (e == epochHours) {
315            logln("Ok: " + year + "/" + (month+1) + "/" + dom + " " + hour + ":00 => " +
316                  e + " (" + ed + ")");
317        } else {
318            errln("FAIL: " + year + "/" + (month+1) + "/" + dom + " " + hour + ":00 => " +
319                  e + " (" + new Date((long)(e * H)) + ")" +
320                  ", expected " + epochHours + " (" + ed + ")");
321        }
322        cal.setTime(ed);
323        if (cal.get(Calendar.YEAR) == year &&
324            cal.get(Calendar.MONTH) == month &&
325            cal.get(Calendar.DATE) == dom &&
326            cal.get(Calendar.MILLISECONDS_IN_DAY) == hour * 3600000) {
327            logln("Ok: " + epochHours + " (" + ed + ") => " +
328                  cal.get(Calendar.YEAR) + "/" +
329                  (cal.get(Calendar.MONTH)+1) + "/" +
330                  cal.get(Calendar.DATE) + " " +
331                  cal.get(Calendar.MILLISECONDS_IN_DAY)/H);
332        } else {
333            errln("FAIL: " + epochHours + " (" + ed + ") => " +
334                  cal.get(Calendar.YEAR) + "/" +
335                  (cal.get(Calendar.MONTH)+1) + "/" +
336                  cal.get(Calendar.DATE) + " " +
337                  cal.get(Calendar.MILLISECONDS_IN_DAY)/H +
338                  ", expected " + year + "/" + (month+1) + "/" + dom +
339                  " " + hour);
340        }
341    }
342
343// NOTE: Enable this code to check the behavior of the underlying JDK,
344// using a JDK Calendar object.
345//
346//    int millisInDay(java.util.Calendar cal) {
347//        return ((cal.get(Calendar.HOUR_OF_DAY) * 60 +
348//                 cal.get(Calendar.MINUTE)) * 60 +
349//                cal.get(Calendar.SECOND)) * 1000 +
350//            cal.get(Calendar.MILLISECOND);
351//    }
352//
353//    void verifyMapping(java.util.Calendar cal, int year, int month, int dom, int hour,
354//                       double epochHours) {
355//        cal.clear();
356//        cal.set(year, month, dom, hour, 0, 0);
357//        Date d = cal.getTime();
358//        double e = d.getTime() / 3600000.0;
359//        Date ed = new Date((long)(epochHours * 3600000));
360//        if (e == epochHours) {
361//            logln("Ok: " + year + "/" + (month+1) + "/" + dom + " " + hour + ":00 => " +
362//                  e + " (" + ed + ")");
363//        } else {
364//            errln("FAIL: " + year + "/" + (month+1) + "/" + dom + " " + hour + ":00 => " +
365//                  e + " (" + new Date((long)(e * 3600000)) + ")" +
366//                  ", expected " + epochHours + " (" + ed + ")");
367//        }
368//        cal.setTime(ed);
369//        if (cal.get(Calendar.YEAR) == year &&
370//            cal.get(Calendar.MONTH) == month &&
371//            cal.get(Calendar.DATE) == dom &&
372//            millisInDay(cal) == hour * 3600000) {
373//            logln("Ok: " + epochHours + " (" + ed + ") => " +
374//                  cal.get(Calendar.YEAR) + "/" +
375//                  (cal.get(Calendar.MONTH)+1) + "/" +
376//                  cal.get(Calendar.DATE) + " " +
377//                  millisInDay(cal)/3600000.0);
378//        } else {
379//            errln("FAIL: " + epochHours + " (" + ed + ") => " +
380//                  cal.get(Calendar.YEAR) + "/" +
381//                  (cal.get(Calendar.MONTH)+1) + "/" +
382//                  cal.get(Calendar.DATE) + " " +
383//                  millisInDay(cal)/3600000.0 +
384//                  ", expected " + year + "/" + (month+1) + "/" + dom +
385//                  " " + hour);
386//        }
387//    }
388
389    @Test
390    public void TestBoundaries()
391    {
392        TimeZone save = TimeZone.getDefault();
393
394        // Check basic mappings.  We had a problem with this for ICU
395        // 2.8 after migrating to using pass-through time zones.  The
396        // problem appeared only on JDK 1.3.
397        TimeZone pst = safeGetTimeZone("PST");
398        Calendar tempcal = Calendar.getInstance(pst);
399        verifyMapping(tempcal, 1997, Calendar.APRIL, 3,  0, 238904.0);
400        verifyMapping(tempcal, 1997, Calendar.APRIL, 4,  0, 238928.0);
401        verifyMapping(tempcal, 1997, Calendar.APRIL, 5,  0, 238952.0);
402        verifyMapping(tempcal, 1997, Calendar.APRIL, 5, 23, 238975.0);
403        verifyMapping(tempcal, 1997, Calendar.APRIL, 6,  0, 238976.0);
404        verifyMapping(tempcal, 1997, Calendar.APRIL, 6,  1, 238977.0);
405        verifyMapping(tempcal, 1997, Calendar.APRIL, 6,  3, 238978.0);
406
407        TimeZone utc = safeGetTimeZone("UTC");
408        Calendar utccal = Calendar.getInstance(utc);
409        verifyMapping(utccal, 1997, Calendar.APRIL, 6, 0, 238968.0);
410
411// NOTE: Enable this code to check the behavior of the underlying JDK,
412// using a JDK Calendar object.
413//
414//        java.util.TimeZone jdkpst = java.util.TimeZone.getTimeZone("PST");
415//        java.util.Calendar jdkcal = java.util.Calendar.getInstance(jdkpst);
416//        verifyMapping(jdkcal, 1997, Calendar.APRIL, 5,  0, 238952.0);
417//        verifyMapping(jdkcal, 1997, Calendar.APRIL, 5, 23, 238975.0);
418//        verifyMapping(jdkcal, 1997, Calendar.APRIL, 6,  0, 238976.0);
419//        verifyMapping(jdkcal, 1997, Calendar.APRIL, 6,  1, 238977.0);
420//        verifyMapping(jdkcal, 1997, Calendar.APRIL, 6,  3, 238978.0);
421
422        tempcal.clear();
423        tempcal.set(1997, Calendar.APRIL, 6);
424        Date d = tempcal.getTime();
425
426        try {
427            TimeZone.setDefault(pst);
428
429            // DST changeover for PST is 4/6/1997 at 2 hours past midnight
430            // at 238978.0 epoch hours.
431
432            // i is minutes past midnight standard time
433            for (int i=-120; i<=180; i+=60)
434            {
435                boolean inDST = (i >= 120);
436                tempcal.setTimeInMillis(d.getTime() + i*60*1000);
437                verifyDST("hour=" + i/60,
438                          tempcal, pst, true, inDST, -8*ONE_HOUR,
439                          inDST ? -7*ONE_HOUR : -8*ONE_HOUR);
440            }
441        } finally {
442            TimeZone.setDefault(save);
443        }
444
445        // We no longer use ICU TimeZone implementation for Java
446        // default TimeZone.  Java 1.3 or older version do not
447        // support historic transitions, therefore, the test below
448        // will fail on such environment (with the latest TimeZone
449        // patch for US 2007+ rule).
450        if (TestUtil.getJavaVendor() == JavaVendor.Android || TestUtil.getJavaVersion() > 3) {
451            // This only works in PST/PDT
452            TimeZone.setDefault(safeGetTimeZone("PST"));
453            logln("========================================");
454            tempcal.set(1997, 0, 1);
455            findDaylightBoundaryUsingDate(tempcal.getTime(), "PST", PST_1997_BEG);
456            logln("========================================");
457            tempcal.set(1997, 6, 1);
458            findDaylightBoundaryUsingDate(tempcal.getTime(), "PDT", PST_1997_END);
459        }
460
461        //  if (true)
462        //  {
463        //      logln("========================================");
464        //      findDaylightBoundaryUsingCalendar(new Date(97,0,1), false);
465        //      logln("========================================");
466        //      findDaylightBoundaryUsingCalendar(new Date(97,6,1), true);
467        //  }
468
469        if (true)
470        {
471            // Southern hemisphere test
472            logln("========================================");
473            TimeZone z = safeGetTimeZone(AUSTRALIA);
474            tempcal.set(1997, 0, 1);
475            findDaylightBoundaryUsingTimeZone(tempcal.getTime(), true, AUSTRALIA_1997_END, z);
476            logln("========================================");
477            tempcal.set(1997, 6, 1);
478            findDaylightBoundaryUsingTimeZone(tempcal.getTime(), false, AUSTRALIA_1997_BEG, z);
479        }
480
481        if (true)
482        {
483            logln("========================================");
484            tempcal.set(1997, 0, 1);
485            findDaylightBoundaryUsingTimeZone(tempcal.getTime(), false, PST_1997_BEG);
486            logln("========================================");
487            tempcal.set(1997, 6, 1);
488            findDaylightBoundaryUsingTimeZone(tempcal.getTime(), true, PST_1997_END);
489        }
490
491        // This just shows the offset for April 4-7 in 1997.  This is redundant
492        // with a test above, so we disable it.
493        if (false)
494        {
495            TimeZone z = TimeZone.getDefault();
496            tempcal.set(1997, 3, 4);
497            logln(z.getOffset(1, 97, 3, 4, 6, 0) + " " + tempcal.getTime());
498            tempcal.set(1997, 3, 5);
499            logln(z.getOffset(1, 97, 3, 5, 7, 0) + " " + tempcal.getTime());
500            tempcal.set(1997, 3, 6);
501            logln(z.getOffset(1, 97, 3, 6, 1, 0) + " " + tempcal.getTime());
502            tempcal.set(1997, 3, 7);
503            logln(z.getOffset(1, 97, 3, 7, 2, 0) + " " + tempcal.getTime());
504        }
505    }
506
507
508    //----------------------------------------------------------------------
509    // Can't do any of these without a public inDaylightTime in GC
510    //----------------------------------------------------------------------
511
512
513    //    static GregorianCalendar cal = new GregorianCalendar();
514    //
515    //    static void _testUsingBinarySearch(Date d, boolean startsInDST)
516    //    {
517    //  // Given a date with a year start, find the Daylight onset
518    //  // and end.  The given date should be 1/1/xx in some year.
519    //
520    //  // Use a binary search, assuming that we have a Standard
521    //  // time at the midpoint.
522    //  long min = d.getTime();
523    //  long max = min + (long)(365.25 / 2 * ONE_DAY);
524    //
525    //  // First check the max
526    //  cal.setTime(new Date(max));
527    //  if (cal.inDaylightTime() == startsInDST)
528    //  {
529    //      logln("Error: inDaylightTime(" + (new Date(max)) + ") != " + (!startsInDST));
530    //  }
531    //
532    //  cal.setTime(d);
533    //  if (cal.inDaylightTime() != startsInDST)
534    //  {
535    //      logln("Error: inDaylightTime(" + d + ") != " + startsInDST);
536    //  }
537    //
538    //  while ((max - min) >  INTERVAL)
539    //  {
540    //      long mid = (min + max) >> 1;
541    //      cal.setTime(new Date(mid));
542    //      if (cal.inDaylightTime() == startsInDST)
543    //      {
544    //      min = mid;
545    //      }
546    //      else
547    //      {
548    //      max = mid;
549    //      }
550    //  }
551    //
552    //  logln("Binary Search Before: " + showDate(min));
553    //  logln("Binary Search After:  " + showDate(max));
554    //    }
555    //
556    //    static void _testUsingMillis(Date d, boolean startsInDST)
557    //    {
558    //  long millis = d.getTime();
559    //  long max = millis + (long)(370 * ONE_DAY); // A year plus extra
560    //
561    //  boolean lastDST = startsInDST;
562    //  while (millis < max)
563    //  {
564    //      cal.setTime(new Date(millis));
565    //      boolean inDaylight = cal.inDaylightTime();
566    //
567    //      if (inDaylight != lastDST)
568    //      {
569    //      logln("Switch " + (inDaylight ? "into" : "out of")
570    //                 + " DST at " + (new Date(millis)));
571    //      lastDST = inDaylight;
572    //      }
573    //
574    //      millis += 15*ONE_MINUTE;
575    //  }
576    //    }
577    //
578    //    static void _testUsingFields(int y, boolean startsInDST)
579    //    {
580    //  boolean lastDST = startsInDST;
581    //  for (int m = 0; m < 12; ++m)
582    //  {
583    //      for (int d = 1; d <= MONTH_LENGTH[m]; ++d)
584    //      {
585    //      for (int h = 0; h < 24; ++h)
586    //      {
587    //          for (int min = 0; min < 60; min += 15)
588    //          {
589    //          cal.clear();
590    //          cal.set(y, m, d, h, min);
591    //          boolean inDaylight = cal.inDaylightTime();
592    //          if (inDaylight != lastDST)
593    //          {
594    //              lastDST = inDaylight;
595    //              log("Switch " + (lastDST ? "into" : "out of")
596    //                       + " DST at " + y + "/" + (m+1) + "/" + d
597    //                       + " " + showNN(h) + ":" + showNN(min));
598    //              logln(" " + cal.getTime());
599    //
600    //              cal.set(y, m, d, h-1, 45);
601    //              log("Before = "
602    //+ y + "/" + (m+1) + "/" + d
603    //+ " " + showNN(h-1) + ":" + showNN(45));
604    //              logln(" " + cal.getTime());
605    //          }
606    //          }
607    //      }
608    //      }
609    //  }
610    //    }
611    //
612    //    public void Test1()
613    //    {
614    //  logln(Locale.getDefault().getDisplayName());
615    //  logln(TimeZone.getDefault().getID());
616    //  logln(new Date(0));
617    //
618    //  if (true)
619    //  {
620    //      logln("========================================");
621    //      _testUsingBinarySearch(new Date(97,0,1), false);
622    //      logln("========================================");
623    //      _testUsingBinarySearch(new Date(97,6,1), true);
624    //  }
625    //
626    //  if (true)
627    //  {
628    //      logln("========================================");
629    //      logln("Stepping using millis");
630    //      _testUsingMillis(new Date(97,0,1), false);
631    //  }
632    //
633    //  if (true)
634    //  {
635    //      logln("========================================");
636    //      logln("Stepping using fields");
637    //      _testUsingFields(1997, false);
638    //  }
639    //
640    //  if (false)
641    //  {
642    //      cal.clear();
643    //      cal.set(1997, 3, 5, 10, 0);
644    //      //  cal.inDaylightTime();
645    //      logln("Date = " + cal.getTime());
646    //      logln("Millis = " + cal.getTime().getTime()/3600000);
647    //  }
648    //    }
649
650    //----------------------------------------------------------------------
651    //----------------------------------------------------------------------
652    //----------------------------------------------------------------------
653
654    void _testUsingBinarySearch(SimpleTimeZone tz, Date d, long expectedBoundary)
655    {
656        // Given a date with a year start, find the Daylight onset
657        // and end.  The given date should be 1/1/xx in some year.
658
659        // Use a binary search, assuming that we have a Standard
660        // time at the midpoint.
661        long min = d.getTime();
662        long max = min + (long)(365.25 / 2 * ONE_DAY);
663
664        // First check the boundaries
665        boolean startsInDST = tz.inDaylightTime(d);
666
667        if (tz.inDaylightTime(new Date(max)) == startsInDST)
668        {
669            errln("Error: inDaylightTime(" + (new Date(max)) + ") != " + (!startsInDST));
670        }
671
672        while ((max - min) >  INTERVAL)
673        {
674            long mid = (min + max) >> 1;
675            if (tz.inDaylightTime(new Date(mid)) == startsInDST)
676            {
677                min = mid;
678            }
679            else
680            {
681                max = mid;
682            }
683        }
684
685        logln("Binary Search Before: " + showDate(min));
686        logln("Binary Search After:  " + showDate(max));
687
688        long mindelta = expectedBoundary - min;
689        // not used long maxdelta = max - expectedBoundary;
690        if (mindelta >= 0 && mindelta <= INTERVAL &&
691            mindelta >= 0 && mindelta <= INTERVAL)
692            logln("PASS: Expected boundary at " + expectedBoundary);
693        else
694            errln("FAIL: Expected boundary at " + expectedBoundary);
695    }
696
697    /*
698      static void _testUsingMillis(Date d, boolean startsInDST)
699      {
700      long millis = d.getTime();
701      long max = millis + (long)(370 * ONE_DAY); // A year plus extra
702
703      boolean lastDST = startsInDST;
704      while (millis < max)
705      {
706      cal.setTime(new Date(millis));
707      boolean inDaylight = cal.inDaylightTime();
708
709      if (inDaylight != lastDST)
710      {
711      logln("Switch " + (inDaylight ? "into" : "out of")
712      + " DST at " + (new Date(millis)));
713      lastDST = inDaylight;
714      }
715
716      millis += 15*ONE_MINUTE;
717      }
718      }
719      */
720
721    /**
722     * Test new rule formats.
723     */
724    @Test
725    public void TestNewRules()
726    {
727        //logln(Locale.getDefault().getDisplayName());
728        //logln(TimeZone.getDefault().getID());
729        //logln(new Date(0));
730
731        if (true)
732        {
733            // Doesn't matter what the default TimeZone is here, since we
734            // are creating our own TimeZone objects.
735
736            SimpleTimeZone tz;
737            java.util.Calendar tempcal = java.util.Calendar.getInstance();
738            tempcal.clear();
739
740            logln("-----------------------------------------------------------------");
741            logln("Aug 2ndTues .. Mar 15");
742            tz = new SimpleTimeZone(-8*ONE_HOUR, "Test_1",
743                                    Calendar.AUGUST, 2, Calendar.TUESDAY, 2*ONE_HOUR,
744                                    Calendar.MARCH, 15, 0, 2*ONE_HOUR);
745            //logln(tz.toString());
746            logln("========================================");
747            tempcal.set(1997, 0, 1);
748            _testUsingBinarySearch(tz, tempcal.getTime(), 858416400000L);
749            logln("========================================");
750            tempcal.set(1997, 6, 1);
751            _testUsingBinarySearch(tz, tempcal.getTime(), 871380000000L);
752
753            logln("-----------------------------------------------------------------");
754            logln("Apr Wed>=14 .. Sep Sun<=20");
755            tz = new SimpleTimeZone(-8*ONE_HOUR, "Test_2",
756                                    Calendar.APRIL, 14, -Calendar.WEDNESDAY, 2*ONE_HOUR,
757                                    Calendar.SEPTEMBER, -20, -Calendar.SUNDAY, 2*ONE_HOUR);
758            //logln(tz.toString());
759            logln("========================================");
760            tempcal.set(1997, 0, 1);
761            _testUsingBinarySearch(tz, tempcal.getTime(), 861184800000L);
762            logln("========================================");
763            tempcal.set(1997, 6, 1);
764            _testUsingBinarySearch(tz, tempcal.getTime(), 874227600000L);
765        }
766
767        /*
768          if (true)
769          {
770          logln("========================================");
771          logln("Stepping using millis");
772          _testUsingMillis(new Date(97,0,1), false);
773          }
774
775          if (true)
776          {
777          logln("========================================");
778          logln("Stepping using fields");
779          _testUsingFields(1997, false);
780          }
781
782          if (false)
783          {
784          cal.clear();
785          cal.set(1997, 3, 5, 10, 0);
786          //    cal.inDaylightTime();
787          logln("Date = " + cal.getTime());
788          logln("Millis = " + cal.getTime().getTime()/3600000);
789          }
790          */
791    }
792
793    //----------------------------------------------------------------------
794    //----------------------------------------------------------------------
795    //----------------------------------------------------------------------
796    // Long Bug
797    //----------------------------------------------------------------------
798    //----------------------------------------------------------------------
799    //----------------------------------------------------------------------
800
801    //public void Test3()
802    //{
803    //    findDaylightBoundaryUsingTimeZone(new Date(97,6,1), true);
804    //}
805
806    /**
807     * Find boundaries by stepping.
808     */
809    void findBoundariesStepwise(int year, long interval, TimeZone z, int expectedChanges)
810    {
811        java.util.Calendar tempcal = java.util.Calendar.getInstance();
812        tempcal.clear();
813        tempcal.set(year, Calendar.JANUARY, 1);
814        Date d = tempcal.getTime();
815        long time = d.getTime(); // ms
816        long limit = time + ONE_YEAR + ONE_DAY;
817        boolean lastState = z.inDaylightTime(d);
818        int changes = 0;
819        logln("-- Zone " + z.getID() + " starts in " + year + " with DST = " + lastState);
820        logln("useDaylightTime = " + z.useDaylightTime());
821        while (time < limit)
822        {
823            d.setTime(time);
824            boolean state = z.inDaylightTime(d);
825            if (state != lastState)
826            {
827                logln((state ? "Entry " : "Exit ") +
828                      "at " + d);
829                lastState = state;
830                ++changes;
831            }
832            time += interval;
833        }
834        if (changes == 0)
835        {
836            if (!lastState && !z.useDaylightTime()) logln("No DST");
837            else errln("FAIL: DST all year, or no DST with true useDaylightTime");
838        }
839        else if (changes != 2)
840        {
841            errln("FAIL: " + changes + " changes seen; should see 0 or 2");
842        }
843        else if (!z.useDaylightTime())
844        {
845            errln("FAIL: useDaylightTime false but 2 changes seen");
846        }
847        if (changes != expectedChanges)
848        {
849            errln("FAIL: " + changes + " changes seen; expected " + expectedChanges);
850        }
851    }
852
853    @Test
854    public void TestStepwise()
855    {
856        findBoundariesStepwise(1997, ONE_DAY, safeGetTimeZone("America/New_York"), 2);
857        // disabled Oct 2003 aliu; ACT could mean anything, depending on the underlying JDK, as of 2.8
858        // findBoundariesStepwise(1997, ONE_DAY, safeGetTimeZone("ACT"), 2);
859        findBoundariesStepwise(1997, ONE_DAY, safeGetTimeZone("America/Phoenix"), 0); // Added 3Jan01
860        findBoundariesStepwise(1997, ONE_DAY, safeGetTimeZone(AUSTRALIA), 2);
861    }
862}
863