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-2016, International Business Machines Corporation and
7 * others. All Rights Reserved.
8 *******************************************************************************
9 */
10package android.icu.dev.test.calendar;
11
12import java.text.FieldPosition;
13import java.text.ParseException;
14import java.util.Date;
15import java.util.Locale;
16import java.util.Set;
17
18import org.junit.Test;
19import org.junit.runner.RunWith;
20import org.junit.runners.JUnit4;
21
22import android.icu.impl.CalendarAstronomer;
23import android.icu.impl.LocaleUtility;
24import android.icu.impl.ZoneMeta;
25import android.icu.text.DateFormat;
26import android.icu.text.DateFormatSymbols;
27import android.icu.text.SimpleDateFormat;
28import android.icu.util.BuddhistCalendar;
29import android.icu.util.Calendar;
30import android.icu.util.ChineseCalendar;
31import android.icu.util.GregorianCalendar;
32import android.icu.util.JapaneseCalendar;
33import android.icu.util.TaiwanCalendar;
34import android.icu.util.TimeZone;
35import android.icu.util.TimeZone.SystemTimeZoneType;
36import android.icu.util.ULocale;
37import android.icu.testsharding.MainTestShard;
38
39/**
40 * @summary Tests of new functionality in IBMCalendar
41 */
42@MainTestShard
43@RunWith(JUnit4.class)
44public class IBMCalendarTest extends CalendarTestFmwk {
45    /**
46     * Test weekend support in IBMCalendar.
47     *
48     * NOTE: This test will have to be updated when the isWeekend() etc.
49     *       API is finalized later.
50     *
51     *       In particular, the test will have to be rewritten to instantiate
52     *       a Calendar in the given locale (using getInstance()) and call
53     *       that Calendar's isWeekend() etc. methods.
54     */
55    @Test
56    public void TestWeekend() {
57        SimpleDateFormat fmt = new SimpleDateFormat("EEE MMM dd yyyy G HH:mm:ss.SSS");
58
59        // NOTE
60        // This test tests for specific locale data.  This is probably okay
61        // as far as US data is concerned, but if the Arabic/Yemen data
62        // changes, this test will have to be updated.
63
64        // Test specific days
65        Object[] DATA1 = {
66            Locale.US, new int[] { // Saturday:Sunday
67                2000, Calendar.MARCH, 17, 23,  0, 0, // Fri 23:00
68                2000, Calendar.MARCH, 18,  0, -1, 0, // Fri 23:59:59.999
69                2000, Calendar.MARCH, 18,  0,  0, 1, // Sat 00:00
70                2000, Calendar.MARCH, 18, 15,  0, 1, // Sat 15:00
71                2000, Calendar.MARCH, 19, 23,  0, 1, // Sun 23:00
72                2000, Calendar.MARCH, 20,  0, -1, 1, // Sun 23:59:59.999
73                2000, Calendar.MARCH, 20,  0,  0, 0, // Mon 00:00
74                2000, Calendar.MARCH, 20,  8,  0, 0, // Mon 08:00
75            },
76            new Locale("ar", "OM"), new int[] { // Friday:Saturday
77                2000, Calendar.MARCH, 15, 23,  0, 0, // Wed 23:00
78                2000, Calendar.MARCH, 16,  0, -1, 0, // Wed 23:59:59.999
79                2000, Calendar.MARCH, 16,  0,  0, 0, // Thu 00:00
80                2000, Calendar.MARCH, 16, 15,  0, 0, // Thu 15:00
81                2000, Calendar.MARCH, 17, 23,  0, 1, // Fri 23:00
82                2000, Calendar.MARCH, 18,  0, -1, 1, // Fri 23:59:59.999
83                2000, Calendar.MARCH, 18,  0,  0, 1, // Sat 00:00
84                2000, Calendar.MARCH, 18,  8,  0, 1, // Sat 08:00
85            },
86        };
87
88        // Test days of the week
89        Object[] DATA2 = {
90            Locale.US, new int[] {
91                Calendar.MONDAY,   Calendar.WEEKDAY,
92                Calendar.FRIDAY,   Calendar.WEEKDAY,
93                Calendar.SATURDAY, Calendar.WEEKEND,
94                Calendar.SUNDAY,   Calendar.WEEKEND,
95            },
96            new Locale("ar", "OM"), new int[] { // Friday:Saturday
97                Calendar.WEDNESDAY,Calendar.WEEKDAY,
98                Calendar.THURSDAY, Calendar.WEEKDAY,
99                Calendar.FRIDAY,   Calendar.WEEKEND,
100                Calendar.SATURDAY, Calendar.WEEKEND,
101            },
102            new Locale("hi", "IN"), new int[] { // Sunday only
103                Calendar.MONDAY,   Calendar.WEEKDAY,
104                Calendar.FRIDAY,   Calendar.WEEKDAY,
105                Calendar.SATURDAY, Calendar.WEEKDAY,
106                Calendar.SUNDAY,   Calendar.WEEKEND,
107            },
108        };
109
110        // We only test the getDayOfWeekType() and isWeekend() APIs.
111        // The getWeekendTransition() API is tested indirectly via the
112        // isWeekend() API, which calls it.
113
114        for (int i1=0; i1<DATA1.length; i1+=2) {
115            Locale loc = (Locale)DATA1[i1];
116            int[] data = (int[]) DATA1[i1+1];
117            Calendar cal = Calendar.getInstance(loc);
118            logln("Locale: " + loc);
119            for (int i=0; i<data.length; i+=6) {
120                cal.clear();
121                cal.set(data[i], data[i+1], data[i+2], data[i+3], 0, 0);
122                if (data[i+4] != 0) {
123                    cal.setTime(new Date(cal.getTime().getTime() + data[i+4]));
124                }
125                boolean isWeekend = cal.isWeekend();
126                boolean ok = isWeekend == (data[i+5] != 0);
127                if (ok) {
128                    logln("Ok:   " + fmt.format(cal.getTime()) + " isWeekend=" + isWeekend);
129                } else {
130                    errln("FAIL: " + fmt.format(cal.getTime()) + " isWeekend=" + isWeekend +
131                          ", expected=" + (!isWeekend));
132                }
133            }
134        }
135
136        for (int i2=0; i2<DATA2.length; i2+=2) {
137            Locale loc = (Locale)DATA2[i2];
138            int[] data = (int[]) DATA2[i2+1];
139            logln("Locale: " + loc);
140            Calendar cal = Calendar.getInstance(loc);
141            for (int i=0; i<data.length; i+=2) {
142                int type = cal.getDayOfWeekType(data[i]);
143                int exp  = data[i+1];
144                if (type == exp) {
145                    logln("Ok:   DOW " + data[i] + " type=" + type);
146                } else {
147                    errln("FAIL: DOW " + data[i] + " type=" + type +
148                          ", expected=" + exp);
149                }
150            }
151        }
152    }
153
154    /**
155     * Run a test of a quasi-Gregorian calendar.  This is a calendar
156     * that behaves like a Gregorian but has different year/era mappings.
157     * The int[] data array should have the format:
158     *
159     * { era, year, gregorianYear, month, dayOfMonth, ... }
160     */
161    void quasiGregorianTest(Calendar cal, int[] data) {
162        // As of JDK 1.4.1_01, using the Sun JDK GregorianCalendar as
163        // a reference throws us off by one hour.  This is most likely
164        // due to the JDK 1.4 incorporation of historical time zones.
165        //java.util.Calendar grego = java.util.Calendar.getInstance();
166        Calendar grego = Calendar.getInstance();
167        for (int i=0; i<data.length; ) {
168            int era = data[i++];
169            int year = data[i++];
170            int gregorianYear = data[i++];
171            int month = data[i++];
172            int dayOfMonth = data[i++];
173
174            grego.clear();
175            grego.set(gregorianYear, month, dayOfMonth);
176            Date D = grego.getTime();
177
178            cal.clear();
179            cal.set(Calendar.ERA, era);
180            cal.set(year, month, dayOfMonth);
181            Date d = cal.getTime();
182            if (d.equals(D)) {
183                logln("OK: " + era + ":" + year + "/" + (month+1) + "/" + dayOfMonth +
184                      " => " + d);
185            } else {
186                errln("Fail: " + era + ":" + year + "/" + (month+1) + "/" + dayOfMonth +
187                      " => " + d + ", expected " + D);
188            }
189
190            cal.clear();
191            cal.setTime(D);
192            int e = cal.get(Calendar.ERA);
193            int y = cal.get(Calendar.YEAR);
194            if (y == year && e == era) {
195                logln("OK: " + D + " => " + cal.get(Calendar.ERA) + ":" +
196                      cal.get(Calendar.YEAR) + "/" +
197                      (cal.get(Calendar.MONTH)+1) + "/" + cal.get(Calendar.DATE));
198            } else {
199                logln("Fail: " + D + " => " + cal.get(Calendar.ERA) + ":" +
200                      cal.get(Calendar.YEAR) + "/" +
201                      (cal.get(Calendar.MONTH)+1) + "/" + cal.get(Calendar.DATE) +
202                      ", expected " + era + ":" + year + "/" + (month+1) + "/" +
203                      dayOfMonth);
204            }
205        }
206    }
207
208    /**
209     * Verify that BuddhistCalendar shifts years to Buddhist Era but otherwise
210     * behaves like GregorianCalendar.
211     */
212    @Test
213    public void TestBuddhist() {
214        quasiGregorianTest(new BuddhistCalendar(),
215                           new int[] {
216                               // BE 2542 == 1999 CE
217                               0, 2542, 1999, Calendar.JUNE, 4
218                           });
219    }
220
221    @Test
222    public void TestBuddhistCoverage() {
223    {
224        // new BuddhistCalendar(ULocale)
225        BuddhistCalendar cal = new BuddhistCalendar(ULocale.getDefault());
226        if(cal == null){
227            errln("could not create BuddhistCalendar with ULocale");
228        }
229    }
230
231    {
232        // new BuddhistCalendar(TimeZone,ULocale)
233        BuddhistCalendar cal = new BuddhistCalendar(TimeZone.getDefault(),ULocale.getDefault());
234        if(cal == null){
235            errln("could not create BuddhistCalendar with TimeZone ULocale");
236        }
237    }
238
239    {
240        // new BuddhistCalendar(TimeZone)
241        BuddhistCalendar cal = new BuddhistCalendar(TimeZone.getDefault());
242        if(cal == null){
243            errln("could not create BuddhistCalendar with TimeZone");
244        }
245    }
246
247    {
248        // new BuddhistCalendar(Locale)
249        BuddhistCalendar cal = new BuddhistCalendar(Locale.getDefault());
250        if(cal == null){
251            errln("could not create BuddhistCalendar with Locale");
252        }
253    }
254
255    {
256        // new BuddhistCalendar(TimeZone, Locale)
257        BuddhistCalendar cal = new BuddhistCalendar(TimeZone.getDefault(), Locale.getDefault());
258        if(cal == null){
259            errln("could not create BuddhistCalendar with TimeZone and Locale");
260        }
261    }
262
263    {
264        // new BuddhistCalendar(Date)
265        BuddhistCalendar cal = new BuddhistCalendar(new Date());
266        if(cal == null){
267            errln("could not create BuddhistCalendar with Date");
268        }
269    }
270
271    {
272        // new BuddhistCalendar(int year, int month, int date)
273        BuddhistCalendar cal = new BuddhistCalendar(2543, Calendar.MAY, 22);
274        if(cal == null){
275            errln("could not create BuddhistCalendar with year,month,data");
276        }
277    }
278
279    {
280        // new BuddhistCalendar(int year, int month, int date, int hour, int minute, int second)
281        BuddhistCalendar cal = new BuddhistCalendar(2543, Calendar.MAY, 22, 1, 1, 1);
282        if(cal == null){
283            errln("could not create BuddhistCalendar with year,month,date,hour,minute,second");
284        }
285    }
286
287    {
288        // data
289        BuddhistCalendar cal = new BuddhistCalendar(2543, Calendar.MAY, 22);
290        Date time = cal.getTime();
291
292        String[] calendarLocales = {
293        "th_TH"
294        };
295
296        String[] formatLocales = {
297        "en", "ar", "hu", "th"
298        };
299
300        for (int i = 0; i < calendarLocales.length; ++i) {
301        String calLocName = calendarLocales[i];
302        Locale calLocale = LocaleUtility.getLocaleFromName(calLocName);
303        cal = new BuddhistCalendar(calLocale);
304
305        for (int j = 0; j < formatLocales.length; ++j) {
306            String locName = formatLocales[j];
307            Locale formatLocale = LocaleUtility.getLocaleFromName(locName);
308            DateFormat format = DateFormat.getDateTimeInstance(cal, DateFormat.FULL, DateFormat.FULL, formatLocale);
309            logln(calLocName + "/" + locName + " --> " + format.format(time));
310        }
311        }
312    }
313    }
314
315    /**
316     * Test limits of the Buddhist calendar.
317     */
318    @Test
319    public void TestBuddhistLimits() {
320        // Final parameter is either number of days, if > 0, or test
321        // duration in seconds, if < 0.
322        Calendar cal = Calendar.getInstance();
323        cal.set(2007, Calendar.JANUARY, 1);
324        BuddhistCalendar buddhist = new BuddhistCalendar();
325        doLimitsTest(buddhist, null, cal.getTime());
326        doTheoreticalLimitsTest(buddhist, false);
327    }
328
329    /**
330     * Default calendar for Thai (Ticket#6302)
331     */
332    @Test
333    public void TestThaiDefault() {
334        // Buddhist calendar is used as the default calendar for
335        // Thai locale
336        Calendar cal = Calendar.getInstance(new ULocale("th_TH"));
337        String type = cal.getType();
338        // Android patch: Force default Gregorian calendar.
339        if (!type.equals("gregorian")) {
340            errln("FAIL: Gregorian calendar is not returned for locale " + cal.toString());
341        }
342        // Android patch end.
343    }
344
345    /**
346     * Verify that TaiwanCalendar shifts years to Minguo Era but otherwise
347     * behaves like GregorianCalendar.
348     */
349    @Test
350    public void TestTaiwan() {
351        quasiGregorianTest(new TaiwanCalendar(),
352                           new int[] {
353                               TaiwanCalendar.BEFORE_MINGUO, 8, 1904, Calendar.FEBRUARY, 29,
354                               TaiwanCalendar.MINGUO, 1, 1912, Calendar.JUNE, 4,
355                               TaiwanCalendar.MINGUO, 3, 1914, Calendar.FEBRUARY, 12,
356                               TaiwanCalendar.MINGUO, 96,2007, Calendar.FEBRUARY, 12,
357                           });
358    }
359
360    /**
361     * Test limits of the Taiwan calendar.
362     */
363    @Test
364    public void TestTaiwanLimits() {
365        // Final parameter is either number of days, if > 0, or test
366        // duration in seconds, if < 0.
367        Calendar cal = Calendar.getInstance();
368        cal.set(2007, Calendar.JANUARY, 1);
369        TaiwanCalendar taiwan = new TaiwanCalendar();
370        doLimitsTest(taiwan, null, cal.getTime());
371        doTheoreticalLimitsTest(taiwan, false);
372    }
373
374    @Test
375    public void TestTaiwanCoverage() {
376    {
377        // new TaiwanCalendar(ULocale)
378        TaiwanCalendar cal = new TaiwanCalendar(ULocale.getDefault());
379        if(cal == null){
380            errln("could not create TaiwanCalendar with ULocale");
381        }
382    }
383
384    {
385        // new TaiwanCalendar(TimeZone,ULocale)
386        TaiwanCalendar cal = new TaiwanCalendar(TimeZone.getDefault(),ULocale.getDefault());
387        if(cal == null){
388            errln("could not create TaiwanCalendar with TimeZone ULocale");
389        }
390    }
391
392    {
393        // new TaiwanCalendar(TimeZone)
394        TaiwanCalendar cal = new TaiwanCalendar(TimeZone.getDefault());
395        if(cal == null){
396            errln("could not create TaiwanCalendar with TimeZone");
397        }
398    }
399
400    {
401        // new TaiwanCalendar(Locale)
402        TaiwanCalendar cal = new TaiwanCalendar(Locale.getDefault());
403        if(cal == null){
404            errln("could not create TaiwanCalendar with Locale");
405        }
406    }
407
408    {
409        // new TaiwanCalendar(TimeZone, Locale)
410        TaiwanCalendar cal = new TaiwanCalendar(TimeZone.getDefault(), Locale.getDefault());
411        if(cal == null){
412            errln("could not create TaiwanCalendar with TimeZone and Locale");
413        }
414    }
415
416    {
417        // new TaiwanCalendar(Date)
418        TaiwanCalendar cal = new TaiwanCalendar(new Date());
419        if(cal == null){
420            errln("could not create TaiwanCalendar with Date");
421        }
422    }
423
424    {
425        // new TaiwanCalendar(int year, int month, int date)
426        TaiwanCalendar cal = new TaiwanCalendar(34, Calendar.MAY, 22);
427        if(cal == null){
428            errln("could not create TaiwanCalendar with year,month,data");
429        }
430    }
431
432    {
433        // new TaiwanCalendar(int year, int month, int date, int hour, int minute, int second)
434        TaiwanCalendar cal = new TaiwanCalendar(34, Calendar.MAY, 22, 1, 1, 1);
435        if(cal == null){
436            errln("could not create TaiwanCalendar with year,month,date,hour,minute,second");
437        }
438    }
439
440    {
441        // data
442        TaiwanCalendar cal = new TaiwanCalendar(34, Calendar.MAY, 22);
443        Date time = cal.getTime();
444
445        String[] calendarLocales = {
446        "en","zh"
447        };
448
449        String[] formatLocales = {
450        "en", "ar", "hu", "th"
451        };
452
453        for (int i = 0; i < calendarLocales.length; ++i) {
454        String calLocName = calendarLocales[i];
455        Locale calLocale = LocaleUtility.getLocaleFromName(calLocName);
456        cal = new TaiwanCalendar(calLocale);
457
458        for (int j = 0; j < formatLocales.length; ++j) {
459            String locName = formatLocales[j];
460            Locale formatLocale = LocaleUtility.getLocaleFromName(locName);
461            DateFormat format = DateFormat.getDateTimeInstance(cal, DateFormat.FULL, DateFormat.FULL, formatLocale);
462            logln(calLocName + "/" + locName + " --> " + format.format(time));
463        }
464        }
465    }
466    }
467
468    /**
469     * Verify that JapaneseCalendar shifts years to Japanese Eras but otherwise
470     * behaves like GregorianCalendar.
471     */
472    @Test
473    public void TestJapanese() {
474        // First make sure this test works for GregorianCalendar
475        int[] control = {
476            GregorianCalendar.AD, 1868, 1868, Calendar.SEPTEMBER, 8,
477            GregorianCalendar.AD, 1868, 1868, Calendar.SEPTEMBER, 9,
478            GregorianCalendar.AD, 1869, 1869, Calendar.JUNE, 4,
479            GregorianCalendar.AD, 1912, 1912, Calendar.JULY, 29,
480            GregorianCalendar.AD, 1912, 1912, Calendar.JULY, 30,
481            GregorianCalendar.AD, 1912, 1912, Calendar.AUGUST, 1,
482        };
483        quasiGregorianTest(new GregorianCalendar(), control);
484
485        int[] data = {
486            JapaneseCalendar.MEIJI, 1, 1868, Calendar.SEPTEMBER, 8,
487            JapaneseCalendar.MEIJI, 1, 1868, Calendar.SEPTEMBER, 9,
488            JapaneseCalendar.MEIJI, 2, 1869, Calendar.JUNE, 4,
489            JapaneseCalendar.MEIJI, 45, 1912, Calendar.JULY, 29,
490            JapaneseCalendar.TAISHO, 1, 1912, Calendar.JULY, 30,
491            JapaneseCalendar.TAISHO, 1, 1912, Calendar.AUGUST, 1,
492        };
493        quasiGregorianTest(new JapaneseCalendar(), data);
494    }
495
496    /**
497     * Test limits of the Gregorian calendar.
498     */
499    @Test
500    public void TestGregorianLimits() {
501        // Final parameter is either number of days, if > 0, or test
502        // duration in seconds, if < 0.
503        Calendar cal = Calendar.getInstance();
504        cal.set(2004, Calendar.JANUARY, 1);
505        GregorianCalendar gregorian = new GregorianCalendar();
506        doLimitsTest(gregorian, null, cal.getTime());
507        doTheoreticalLimitsTest(gregorian, false);
508    }
509
510    /**
511     * Test behavior of fieldDifference around leap years.  Also test a large
512     * field difference to check binary search.
513     */
514    @Test
515    public void TestLeapFieldDifference() {
516        Calendar cal = Calendar.getInstance();
517        cal.set(2004, Calendar.FEBRUARY, 29);
518        Date date2004 = cal.getTime();
519        cal.set(2000, Calendar.FEBRUARY, 29);
520        Date date2000 = cal.getTime();
521        int y = cal.fieldDifference(date2004, Calendar.YEAR);
522        int d = cal.fieldDifference(date2004, Calendar.DAY_OF_YEAR);
523        if (d == 0) {
524            logln("Ok: 2004/Feb/29 - 2000/Feb/29 = " + y + " years, " + d + " days");
525        } else {
526            errln("FAIL: 2004/Feb/29 - 2000/Feb/29 = " + y + " years, " + d + " days");
527        }
528        cal.setTime(date2004);
529        y = cal.fieldDifference(date2000, Calendar.YEAR);
530        d = cal.fieldDifference(date2000, Calendar.DAY_OF_YEAR);
531        if (d == 0) {
532            logln("Ok: 2000/Feb/29 - 2004/Feb/29 = " + y + " years, " + d + " days");
533        } else {
534            errln("FAIL: 2000/Feb/29 - 2004/Feb/29 = " + y + " years, " + d + " days");
535        }
536        // Test large difference
537        cal.set(2001, Calendar.APRIL, 5); // 2452005
538        Date ayl = cal.getTime();
539        cal.set(1964, Calendar.SEPTEMBER, 7); // 2438646
540        Date asl = cal.getTime();
541        d = cal.fieldDifference(ayl, Calendar.DAY_OF_MONTH);
542        cal.setTime(ayl);
543        int d2 = cal.fieldDifference(asl, Calendar.DAY_OF_MONTH);
544        if (d == -d2 && d == 13359) {
545            logln("Ok: large field difference symmetrical " + d);
546        } else {
547            logln("FAIL: large field difference incorrect " + d + ", " + d2 +
548                  ", expect +/- 13359");
549        }
550    }
551
552    /**
553     * Test ms_MY "Malay (Malaysia)" locale.  Bug 1543.
554     */
555    @Test
556    public void TestMalaysianInstance() {
557        Locale loc = new Locale("ms", "MY");  // Malay (Malaysia)
558        Calendar cal = Calendar.getInstance(loc);
559        if(cal == null){
560            errln("could not create Malaysian instance");
561        }
562    }
563
564    /**
565     * setFirstDayOfWeek and setMinimalDaysInFirstWeek may change the
566     * field <=> time mapping, since they affect the interpretation of
567     * the WEEK_OF_MONTH or WEEK_OF_YEAR fields.
568     */
569    @Test
570    public void TestWeekShift() {
571        Calendar cal = new GregorianCalendar(
572                             TimeZone.getTimeZone("America/Los_Angeles"),
573                             new Locale("en", "US"));
574        cal.setTime(new Date(997257600000L)); // Wed Aug 08 01:00:00 PDT 2001
575        // In pass one, change the first day of week so that the weeks
576        // shift in August 2001.  In pass two, change the minimal days
577        // in the first week so that the weeks shift in August 2001.
578        //     August 2001
579        // Su Mo Tu We Th Fr Sa
580        //           1  2  3  4
581        //  5  6  7  8  9 10 11
582        // 12 13 14 15 16 17 18
583        // 19 20 21 22 23 24 25
584        // 26 27 28 29 30 31
585        for (int pass=0; pass<2; ++pass) {
586            if (pass==0) {
587                cal.setFirstDayOfWeek(Calendar.WEDNESDAY);
588                cal.setMinimalDaysInFirstWeek(4);
589            } else {
590                cal.setFirstDayOfWeek(Calendar.SUNDAY);
591                cal.setMinimalDaysInFirstWeek(4);
592            }
593            cal.add(Calendar.DATE, 1); // Force recalc
594            cal.add(Calendar.DATE, -1);
595
596            Date time1 = cal.getTime(); // Get time -- should not change
597
598            // Now change a week parameter and then force a recalc.
599            // The bug is that the recalc should not be necessary --
600            // calendar should do so automatically.
601            if (pass==0) {
602                cal.setFirstDayOfWeek(Calendar.THURSDAY);
603            } else {
604                cal.setMinimalDaysInFirstWeek(5);
605            }
606
607            int woy1 = cal.get(Calendar.WEEK_OF_YEAR);
608            int wom1 = cal.get(Calendar.WEEK_OF_MONTH);
609
610            cal.add(Calendar.DATE, 1); // Force recalc
611            cal.add(Calendar.DATE, -1);
612
613            int woy2 = cal.get(Calendar.WEEK_OF_YEAR);
614            int wom2 = cal.get(Calendar.WEEK_OF_MONTH);
615
616            Date time2 = cal.getTime();
617
618            if (!time1.equals(time2)) {
619                errln("FAIL: shifting week should not alter time");
620            } else {
621                logln(time1.toString());
622            }
623            if (woy1 == woy2 && wom1 == wom2) {
624                logln("Ok: WEEK_OF_YEAR: " + woy1 +
625                      ", WEEK_OF_MONTH: " + wom1);
626            } else {
627                errln("FAIL: WEEK_OF_YEAR: " + woy1 + " => " + woy2 +
628                      ", WEEK_OF_MONTH: " + wom1 + " => " + wom2 +
629                      " after week shift");
630            }
631        }
632    }
633
634    /**
635     * Make sure that when adding a day, we actually wind up in a
636     * different day.  The DST adjustments we use to keep the hour
637     * constant across DST changes can backfire and change the day.
638     */
639    @Test
640    public void TestTimeZoneTransitionAdd() {
641        Locale locale = Locale.US; // could also be CHINA
642        SimpleDateFormat dateFormat =
643            new SimpleDateFormat("MM/dd/yyyy HH:mm z", locale);
644
645        String tz[] = TimeZone.getAvailableIDs();
646
647        for (int z=0; z<tz.length; ++z) {
648            TimeZone t = TimeZone.getTimeZone(tz[z]);
649            dateFormat.setTimeZone(t);
650
651            Calendar cal = Calendar.getInstance(t, locale);
652            cal.clear();
653            // Scan the year 2003, overlapping the edges of the year
654            cal.set(Calendar.YEAR, 2002);
655            cal.set(Calendar.MONTH, Calendar.DECEMBER);
656            cal.set(Calendar.DAY_OF_MONTH, 25);
657
658            for (int i=0; i<365+10; ++i) {
659                Date yesterday = cal.getTime();
660                int yesterday_day = cal.get(Calendar.DAY_OF_MONTH);
661                cal.add(Calendar.DAY_OF_MONTH, 1);
662                if (yesterday_day == cal.get(Calendar.DAY_OF_MONTH)) {
663                    errln(tz[z] + " " +
664                          dateFormat.format(yesterday) + " +1d= " +
665                          dateFormat.format(cal.getTime()));
666                }
667            }
668        }
669    }
670
671    @Test
672    public void TestJB1684() {
673        class TestData {
674            int year;
675            int month;
676            int date;
677            int womyear;
678            int wommon;
679            int wom;
680            int dow;
681            String data;
682            String normalized;
683
684            public TestData(int year, int month, int date,
685                            int womyear, int wommon, int wom, int dow,
686                            String data, String normalized) {
687                this.year = year;
688                this.month = month-1;
689                this.date = date;
690                this.womyear = womyear;
691                this.wommon = wommon-1;
692                this.wom = wom;
693                this.dow = dow;
694                this.data = data; // year, month, week of month, day
695                this.normalized = data;
696                if (normalized != null) this.normalized = normalized;
697            }
698        }
699
700        //      July 2001            August 2001           January 2002
701        // Su Mo Tu We Th Fr Sa  Su Mo Tu We Th Fr Sa  Su Mo Tu We Th Fr Sa
702        //  1  2  3  4  5  6  7            1  2  3  4         1  2  3  4  5
703        //  8  9 10 11 12 13 14   5  6  7  8  9 10 11   6  7  8  9 10 11 12
704        // 15 16 17 18 19 20 21  12 13 14 15 16 17 18  13 14 15 16 17 18 19
705        // 22 23 24 25 26 27 28  19 20 21 22 23 24 25  20 21 22 23 24 25 26
706        // 29 30 31              26 27 28 29 30 31     27 28 29 30 31
707        TestData[] tests = {
708            new TestData(2001, 8,  6,  2001,8,2,Calendar.MONDAY,    "2001 08 02 Mon", null),
709            new TestData(2001, 8,  7,  2001,8,2,Calendar.TUESDAY,   "2001 08 02 Tue", null),
710            new TestData(2001, 8,  5,/*12,*/ 2001,8,2,Calendar.SUNDAY,    "2001 08 02 Sun", null),
711            new TestData(2001, 8,6, /*7,  30,*/ 2001,7,6,Calendar.MONDAY,    "2001 07 06 Mon", "2001 08 02 Mon"),
712            new TestData(2001, 8,7, /*7,  31,*/ 2001,7,6,Calendar.TUESDAY,   "2001 07 06 Tue", "2001 08 02 Tue"),
713            new TestData(2001, 8,  5,  2001,7,6,Calendar.SUNDAY,    "2001 07 06 Sun", "2001 08 02 Sun"),
714            new TestData(2001, 7,  30, 2001,8,1,Calendar.MONDAY,    "2001 08 01 Mon", "2001 07 05 Mon"),
715            new TestData(2001, 7,  31, 2001,8,1,Calendar.TUESDAY,   "2001 08 01 Tue", "2001 07 05 Tue"),
716            new TestData(2001, 7,29, /*8,  5,*/  2001,8,1,Calendar.SUNDAY,    "2001 08 01 Sun", "2001 07 05 Sun"),
717            new TestData(2001, 12, 31, 2001,12,6,Calendar.MONDAY,   "2001 12 06 Mon", null),
718            new TestData(2002, 1,  1,  2002,1,1,Calendar.TUESDAY,   "2002 01 01 Tue", null),
719            new TestData(2002, 1,  2,  2002,1,1,Calendar.WEDNESDAY, "2002 01 01 Wed", null),
720            new TestData(2002, 1,  3,  2002,1,1,Calendar.THURSDAY,  "2002 01 01 Thu", null),
721            new TestData(2002, 1,  4,  2002,1,1,Calendar.FRIDAY,    "2002 01 01 Fri", null),
722            new TestData(2002, 1,  5,  2002,1,1,Calendar.SATURDAY,  "2002 01 01 Sat", null),
723            new TestData(2001,12,30, /*2002, 1,  6,*/  2002,1,1,Calendar.SUNDAY,    "2002 01 01 Sun", "2001 12 06 Sun"),
724        };
725
726        int pass = 0, error = 0, warning = 0;
727
728        final String pattern = "yyyy MM WW EEE";
729        GregorianCalendar cal = new GregorianCalendar();
730        SimpleDateFormat sdf = new SimpleDateFormat(pattern);
731        sdf.setCalendar(cal);
732
733        cal.setFirstDayOfWeek(Calendar.SUNDAY);
734        cal.setMinimalDaysInFirstWeek(1);
735
736        for (int i = 0; i < tests.length; ++i) {
737            TestData test = tests[i];
738            log("\n-----\nTesting round trip of " + test.year +
739                  " " + (test.month + 1) +
740                  " " + test.date +
741                  " (written as) " + test.data);
742
743            cal.clear();
744            cal.set(test.year, test.month, test.date);
745            Date ms = cal.getTime();
746
747            cal.clear();
748            cal.set(Calendar.YEAR, test.womyear);
749            cal.set(Calendar.MONTH, test.wommon);
750            cal.set(Calendar.WEEK_OF_MONTH, test.wom);
751            cal.set(Calendar.DAY_OF_WEEK, test.dow);
752            Date ms2 = cal.getTime();
753
754            if (!ms2.equals(ms)) {
755                log("\nError: GregorianCalendar.DOM gave " + ms +
756                    "\n       GregorianCalendar.WOM gave " + ms2);
757                error++;
758            } else {
759                pass++;
760            }
761
762            ms2 = null;
763            try {
764                ms2 = sdf.parse(test.data);
765            }
766            catch (ParseException e) {
767                errln("parse exception: " + e);
768            }
769
770            if (!ms2.equals(ms)) {
771                log("\nError: GregorianCalendar gave      " + ms +
772                    "\n       SimpleDateFormat.parse gave " + ms2);
773                error++;
774            } else {
775                pass++;
776            }
777
778            String result = sdf.format(ms);
779            if (!result.equals(test.normalized)) {
780                log("\nWarning: format of '" + test.data + "' gave" +
781                    "\n                   '" + result + "'" +
782                    "\n          expected '" + test.normalized + "'");
783                warning++;
784            } else {
785                pass++;
786            }
787
788            Date ms3 = null;
789            try {
790                ms3 = sdf.parse(result);
791            }
792            catch (ParseException e) {
793                errln("parse exception 2: " + e);
794            }
795
796            if (!ms3.equals(ms)) {
797                error++;
798                log("\nError: Re-parse of '" + result + "' gave time of " +
799                    "\n        " + ms3 +
800                    "\n    not " + ms);
801            } else {
802                pass++;
803            }
804        }
805        String info = "\nPassed: " + pass + ", Warnings: " + warning + ", Errors: " + error;
806        if (error > 0) {
807            errln(info);
808        } else {
809            logln(info);
810        }
811    }
812
813    /**
814     * Test the ZoneMeta API.
815     */
816    @Test
817    public void TestZoneMeta() {
818        // Test index by country API
819
820        // Format: {country, zone1, zone2, ..., zoneN}
821        String COUNTRY[][] = { {""},
822                               {"US", "America/Los_Angeles", "PST"} };
823        StringBuffer buf = new StringBuffer();
824        for (int i=0; i<COUNTRY.length; ++i) {
825            Set<String> a = ZoneMeta.getAvailableIDs(SystemTimeZoneType.ANY, COUNTRY[i][0], null);
826            buf.setLength(0);
827            buf.append("Country \"" + COUNTRY[i][0] + "\": [");
828            // Use bitmask to track which of the expected zones we see
829            int mask = 0;
830            boolean first = true;
831            for (String z : a) {
832                if (first) {
833                    first = false;
834                } else {
835                    buf.append(", ");
836                }
837                buf.append(z);
838                for (int k = 1; k < COUNTRY[i].length; ++k) {
839                    if ((mask & (1 << k)) == 0 && z.equals(COUNTRY[i][k])) {
840                        mask |= (1 << k);
841                    }
842                }
843            }
844            buf.append("]");
845            mask >>= 1;
846            // Check bitmask to see if we saw all expected zones
847            if (mask == (1 << (COUNTRY[i].length-1))-1) {
848                logln(buf.toString());
849            } else {
850                errln(buf.toString());
851            }
852        }
853
854        // Test equivalent IDs API
855
856        int n = ZoneMeta.countEquivalentIDs("PST");
857        boolean ok = false;
858        buf.setLength(0);
859        buf.append("Equivalent to PST: ");
860        for (int i=0; i<n; ++i) {
861            String id = ZoneMeta.getEquivalentID("PST", i);
862            if (id.equals("America/Los_Angeles")) {
863                ok = true;
864            }
865            if (i!=0) buf.append(", ");
866            buf.append(id);
867        }
868        if (ok) {
869            logln(buf.toString());
870        } else {
871            errln(buf.toString());
872        }
873    }
874
875    @Test
876    public void TestComparable() {
877    GregorianCalendar c0 = new GregorianCalendar();
878    GregorianCalendar c1 = new GregorianCalendar();
879    c1.add(Calendar.DAY_OF_MONTH, 1);
880    if (c0.compareTo(c1) >= 0) {
881        errln("calendar " + c0 + " not < " + c1);
882    }
883    c0.add(Calendar.MONTH, 1);
884    if (c0.compareTo(c1) <= 0) {
885        errln("calendar " + c0 + " not > " + c1);
886    }
887
888    c0.setTimeInMillis(c1.getTimeInMillis());
889    if (c0.compareTo(c1) != 0) {
890        errln("calendar " + c0 + " not == " + c1);
891    }
892
893    }
894
895    /**
896     * Miscellaneous tests to increase coverage.
897     */
898    @Test
899    public void TestCoverage() {
900        // BuddhistCalendar
901        BuddhistCalendar bcal = new BuddhistCalendar();
902        /*int i =*/ bcal.getMinimum(Calendar.ERA);
903        bcal.add(Calendar.YEAR, 1);
904        bcal.add(Calendar.MONTH, 1);
905        /*Date d = */bcal.getTime();
906
907        // CalendarAstronomer
908        // (This class should probably be made package-private.)
909        CalendarAstronomer astro = new CalendarAstronomer();
910        /*String s = */astro.local(0);
911
912        // ChineseCalendar
913        ChineseCalendar ccal = new ChineseCalendar(TimeZone.getDefault(),
914                                                   Locale.getDefault());
915        ccal.add(Calendar.MONTH, 1);
916        ccal.add(Calendar.YEAR, 1);
917        ccal.roll(Calendar.MONTH, 1);
918        ccal.roll(Calendar.YEAR, 1);
919        ccal.getTime();
920
921        // ICU 2.6
922        Calendar cal = Calendar.getInstance(Locale.US);
923        logln(cal.toString());
924        logln(cal.getDisplayName(Locale.US));
925        int weekendOnset=-1;
926        int weekendCease=-1;
927        for (int i=Calendar.SUNDAY; i<=Calendar.SATURDAY; ++i) {
928            if (cal.getDayOfWeekType(i) == Calendar.WEEKEND_ONSET) {
929                weekendOnset = i;
930            }
931            if (cal.getDayOfWeekType(i) == Calendar.WEEKEND_CEASE) {
932                weekendCease = i;
933            }
934        }
935        // can't call this unless we get a transition day (unusual),
936        // but make the call anyway for coverage reasons
937        try {
938            /*int x=*/ cal.getWeekendTransition(weekendOnset);
939            /*int x=*/ cal.getWeekendTransition(weekendCease);
940        } catch (IllegalArgumentException e) {}
941        /*int x=*/ cal.isWeekend(new Date());
942
943        // new GregorianCalendar(ULocale)
944        GregorianCalendar gcal = new GregorianCalendar(ULocale.getDefault());
945        if(gcal==null){
946            errln("could not create GregorianCalendar with ULocale");
947        } else {
948            logln("Calendar display name: " + gcal.getDisplayName(ULocale.getDefault()));
949        }
950
951        //cover getAvailableULocales
952        final ULocale[] locales = Calendar.getAvailableULocales();
953        long count = locales.length;
954        if (count == 0)
955            errln("getAvailableULocales return empty list");
956        logln("" + count + " available ulocales in Calendar.");
957
958        // Jitterbug 4451, for coverage
959        class StubCalendar extends Calendar{
960            /**
961             * For serialization
962             */
963            private static final long serialVersionUID = -4558903444622684759L;
964
965            @Override
966            protected int handleGetLimit(int field, int limitType) {
967                if (limitType == Calendar.LEAST_MAXIMUM) {
968                    return 1;
969                } else if (limitType == Calendar.GREATEST_MINIMUM) {
970                    return 7;
971                }
972               return -1;
973            }
974            @Override
975            protected int handleComputeMonthStart(int eyear, int month, boolean useMonth) {
976                if (useMonth) {
977                    return eyear * 365 + month * 31;
978                } else {
979                    return eyear * 365;
980                }
981            }
982            @Override
983            protected int handleGetExtendedYear() {return 2017;}
984
985            public void run(){
986                if (Calendar.gregorianPreviousMonthLength(2000,2) != 29){
987                    errln("Year 2000 Feb should have 29 days.");
988                }
989                long millis = Calendar.julianDayToMillis(Calendar.MAX_JULIAN);
990                if(millis != Calendar.MAX_MILLIS){
991                    errln("Did not get the expected value from julianDayToMillis. Got:" + millis);
992                }
993                DateFormat df = handleGetDateFormat("",Locale.getDefault());
994                if (!df.equals(handleGetDateFormat("",ULocale.getDefault()))){
995                    errln ("Calendar.handleGetDateFormat(String, Locale) should delegate to ( ,ULocale)");
996                }
997                if (!getType().equals("unknown")){
998                    errln ("Calendar.getType() should be 'unknown'");
999                }
1000
1001                // Tests for complete coverage of Calendar functions.
1002                int julianDay = Calendar.millisToJulianDay(millis - 1);
1003                assertEquals("Julian max day -1", julianDay, Calendar.MAX_JULIAN - 1);
1004
1005                DateFormat df1 = handleGetDateFormat("GG yyyy-d:MM", "option=xyz", Locale.getDefault());
1006                if (!df1.equals(handleGetDateFormat("GG yyyy-d:MM", "option=xyz", ULocale.getDefault()))){
1007                    errln ("Calendar.handleGetDateFormat(String, Locale) should delegate to ( ,ULocale)");
1008                }
1009
1010                // Prove that the local overrides are used.
1011                int leastMsInDay = handleGetLimit(Calendar.MILLISECONDS_IN_DAY, Calendar.LEAST_MAXIMUM);
1012                assertEquals("getLimit test 1", leastMsInDay, 1);
1013                int maxMsInDay = handleGetLimit(Calendar.WEEK_OF_MONTH, Calendar.GREATEST_MINIMUM);
1014                assertEquals("getLimit test 2", 7, maxMsInDay);
1015
1016                int febLeapLength = handleGetMonthLength(2020, Calendar.FEBRUARY);
1017                assertEquals("handleMonthLength", 31, febLeapLength);
1018                int exYear = handleGetExtendedYear();
1019                assertEquals("handleGetExtendeYear", exYear, 2017);
1020                int monthStart = handleComputeMonthStart(2016, 4, false);
1021                assertEquals("handleComputeMonthStart false", 735840, monthStart);
1022                monthStart = handleComputeMonthStart(2016, 4, true);
1023                assertEquals("handleComputeMonthStart true", 735964, monthStart);
1024
1025                Calendar cal = Calendar.getInstance();
1026                cal.set(1980, 5, 2);
1027                this.setTime(cal.getTime());
1028                assertEquals("handleComputeFields: year set", 1980, get(YEAR));
1029                assertEquals("handleComputeFields: month set", 5, get(MONTH));
1030                assertEquals("handleComputeFields: day set", 2, get(DAY_OF_MONTH));
1031            }
1032        }
1033        StubCalendar stub = new StubCalendar();
1034        stub.run();
1035    }
1036
1037    // Tests for jb 4541
1038    @Test
1039    public void TestJB4541() {
1040        ULocale loc = new ULocale("en_US");
1041
1042        // !!! Shouldn't we have an api like this?
1043        // !!! Question: should this reflect those actually available in this copy of ICU, or
1044        // the list of types we assume is available?
1045        // String[] calTypes = Calendar.getAvailableTypes();
1046        final String[] calTypes = {
1047            "buddhist", "chinese", "coptic", "ethiopic", "gregorian", "hebrew",
1048            "islamic", "islamic-civil", "japanese", "roc"
1049        };
1050
1051        // constructing a DateFormat with a locale indicating a calendar type should construct a
1052        // date format appropriate to that calendar
1053        final Date time = new Date();
1054        for (int i = 0; i < calTypes.length; ++i) {
1055            ULocale aLoc = loc.setKeywordValue("calendar", calTypes[i]);
1056            logln("locale: " + aLoc);
1057
1058            DateFormat df = DateFormat.getDateTimeInstance(DateFormat.FULL,
1059                                                           DateFormat.FULL,
1060                                                           aLoc);
1061
1062            logln("df type: " + df.getClass().getName() + " loc: " + df.getLocale(ULocale.VALID_LOCALE));
1063
1064            Calendar cal = df.getCalendar();
1065            assertEquals("calendar types", cal.getType(), calTypes[i]);
1066            DateFormat df2 = cal.getDateTimeFormat(DateFormat.FULL, DateFormat.FULL, ULocale.US);
1067            logln("df2 type: " + df2.getClass().getName() + " loc: " + df2.getLocale(ULocale.VALID_LOCALE));
1068            assertEquals("format results", df.format(time), df2.format(time));
1069        }
1070
1071        // dateFormat.setCalendar should throw exception if wrong format for calendar
1072        if (false) {
1073            DateFormat df = DateFormat.getDateTimeInstance(DateFormat.FULL,
1074                                                           DateFormat.FULL,
1075                                                           new ULocale("en_US@calendar=chinese"));
1076
1077            logln("dateformat type: " + df.getClass().getName());
1078
1079            Calendar cal = Calendar.getInstance(new ULocale("en_US@calendar=chinese"));
1080
1081            logln("calendar type: " + cal.getClass().getName());
1082        }
1083    }
1084
1085    @Test
1086    public void TestTypes() {
1087        String[] locs = {
1088                "en_US_VALLEYGIRL",
1089                "en_US_VALLEYGIRL@collation=phonebook;calendar=japanese",
1090                "en_US_VALLEYGIRL@collation=phonebook;calendar=gregorian",
1091                "ja_JP@calendar=japanese",
1092                "th_TH@calendar=buddhist",
1093                "th-TH-u-ca-gregory",
1094                "ja_JP_TRADITIONAL",
1095                "th_TH_TRADITIONAL",
1096                "th_TH_TRADITIONAL@calendar=gregorian",
1097                "en_US",
1098                "th_TH",    // Default calendar for th_TH is buddhist
1099                "th",       // th's default region is TH and buddhist is used as default for TH
1100                "en_TH",    // Default calendar for any locales with region TH is buddhist
1101                "th_TH@calendar=iso8601",   // iso8601 calendar type
1102                "fr_CH",
1103                "fr_SA",
1104                "fr_CH@rg=sazzzz",
1105                "fr_CH@calendar=japanese;rg=sazzzz",
1106                "fr_TH@rg=SA",  // ignore malformed rg tag, use buddhist
1107                "th@rg=SA",		// ignore malformed rg tag, use buddhist
1108        };
1109
1110        // Android patch: Force default Gregorian calendar.
1111        String[] types = {
1112                "gregorian",
1113                "japanese",
1114                "gregorian",
1115                "japanese",
1116                "buddhist",
1117                "gregorian",
1118                "japanese",
1119                "buddhist",
1120                "gregorian",
1121                "gregorian",
1122                "gregorian",
1123                "gregorian",
1124                "gregorian",
1125                "gregorian",    // iso8601 is a gregorian sub type
1126                "gregorian",
1127                "gregorian",
1128                "gregorian",
1129                "japanese",
1130                "gregorian",
1131                "gregorian",
1132        };
1133        // Android patch end.
1134
1135        for (int i = 0; i < locs.length; i++) {
1136            Calendar cal = Calendar.getInstance(new ULocale(locs[i]));
1137            if (!cal.getType().equals(types[i])) {
1138                errln(locs[i] + " Calendar type " + cal.getType() + " instead of " + types[i]);
1139            }
1140        }
1141    }
1142
1143    @Test
1144    public void TestISO8601() {
1145        final ULocale[] TEST_LOCALES = {
1146            new ULocale("en_US@calendar=iso8601"),
1147            new ULocale("en_US@calendar=Iso8601"),
1148            new ULocale("th_TH@calendar=iso8601"),
1149            new ULocale("ar_EG@calendar=iso8601")
1150        };
1151
1152        final int[][] TEST_DATA = {
1153            // {<year>, <week# of Jan 1>, <week# year of Jan 1>}
1154            {2008, 1, 2008},
1155            {2009, 1, 2009},
1156            {2010, 53, 2009},
1157            {2011, 52, 2010},
1158            {2012, 52, 2011},
1159            {2013, 1, 2013},
1160            {2014, 1, 2014},
1161        };
1162
1163        for (ULocale locale : TEST_LOCALES) {
1164            Calendar cal = Calendar.getInstance(locale);
1165            // No matter what locale is used, if calendar type is "iso8601",
1166            // calendar type must be Gregorian
1167            if (!cal.getType().equals("gregorian")) {
1168                errln("Error: Gregorian calendar is not used for locale: " + locale);
1169            }
1170
1171            for (int[] data : TEST_DATA) {
1172                cal.set(data[0], Calendar.JANUARY, 1);
1173                int weekNum = cal.get(Calendar.WEEK_OF_YEAR);
1174                int weekYear = cal.get(Calendar.YEAR_WOY);
1175
1176                if (weekNum != data[1] || weekYear != data[2]) {
1177                    errln("Error: Incorrect week of year on January 1st, " + data[0]
1178                            + " for locale " + locale
1179                            + ": Returned [weekNum=" + weekNum + ", weekYear=" + weekYear
1180                            + "], Expected [weekNum=" + data[1] + ", weekYear=" + data[2] + "]");
1181                }
1182            }
1183        }
1184    }
1185
1186    private static class CalFields {
1187        private int year;
1188        private int month;
1189        private int day;
1190        private int hour;
1191        private int min;
1192        private int sec;
1193        private int ms;
1194
1195        CalFields(int year, int month, int day, int hour, int min, int sec) {
1196            this(year, month, day, hour, min, sec, 0);
1197        }
1198
1199        CalFields(int year, int month, int day, int hour, int min, int sec, int ms) {
1200            this.year = year;
1201            this.month = month;
1202            this.day = day;
1203            this.hour = hour;
1204            this.min = min;
1205            this.sec = sec;
1206            this.ms = ms;
1207        }
1208
1209        void setTo(Calendar cal) {
1210            cal.clear();
1211            cal.set(year,  month - 1, day, hour, min, sec);
1212            cal.set(Calendar.MILLISECOND, ms);
1213        }
1214
1215        @Override
1216        public String toString() {
1217            return String.format("%04d-%02d-%02d %02d:%02d:%02d.%03d", year, month, day, hour, min, sec, ms);
1218        }
1219
1220        @Override
1221        public boolean equals(Object other) {
1222            if (other instanceof CalFields) {
1223                CalFields otr = (CalFields)other;
1224                return (year == otr.year
1225                        && month == otr.month
1226                        && day == otr.day
1227                        && hour == otr.hour
1228                        && min == otr.min
1229                        && sec == otr.sec
1230                        && ms == otr.ms);
1231            }
1232            return false;
1233        }
1234
1235        boolean isEquivalentTo(Calendar cal) {
1236            return year == cal.get(Calendar.YEAR)
1237                    && month == cal.get(Calendar.MONTH) + 1
1238                    && day == cal.get(Calendar.DAY_OF_MONTH)
1239                    && hour == cal.get(Calendar.HOUR_OF_DAY)
1240                    && min == cal.get(Calendar.MINUTE)
1241                    && sec == cal.get(Calendar.SECOND)
1242                    && ms == cal.get(Calendar.MILLISECOND);
1243        }
1244
1245        static CalFields createFrom(Calendar cal) {
1246            int year = cal.get(Calendar.YEAR);
1247            int month = cal.get(Calendar.MONTH) + 1;
1248            int day = cal.get(Calendar.DAY_OF_MONTH);
1249            int hour = cal.get(Calendar.HOUR_OF_DAY);
1250            int min = cal.get(Calendar.MINUTE);
1251            int sec = cal.get(Calendar.SECOND);
1252
1253            return new CalFields(year, month, day, hour, min, sec);
1254        }
1255    }
1256
1257    @Test
1258    public void TestAmbiguousWallTimeAPIs() {
1259        Calendar cal = Calendar.getInstance();
1260
1261        assertEquals("Default repeated wall time option", cal.getRepeatedWallTimeOption(), Calendar.WALLTIME_LAST);
1262        assertEquals("Default skipped wall time option", cal.getSkippedWallTimeOption(), Calendar.WALLTIME_LAST);
1263
1264        Calendar cal2 = (Calendar)cal.clone();
1265
1266        assertTrue("Equality", cal2.equals(cal));
1267        assertTrue("Hash code", cal.hashCode() == cal2.hashCode());
1268
1269        cal2.setRepeatedWallTimeOption(Calendar.WALLTIME_FIRST);
1270        cal2.setSkippedWallTimeOption(Calendar.WALLTIME_FIRST);
1271
1272        assertFalse("Equality after mod", cal2.equals(cal));
1273        assertFalse("Hash code after mod", cal.hashCode() == cal2.hashCode());
1274
1275        assertEquals("getRepeatedWallTimeOption after mod", cal2.getRepeatedWallTimeOption(), Calendar.WALLTIME_FIRST);
1276        assertEquals("getSkippedWallTimeOption after mod", cal2.getSkippedWallTimeOption(), Calendar.WALLTIME_FIRST);
1277
1278        try {
1279            cal.setRepeatedWallTimeOption(Calendar.WALLTIME_NEXT_VALID);
1280            errln("IAE expected on setRepeatedWallTimeOption(WALLTIME_NEXT_VALID");
1281        } catch (IllegalArgumentException e) {
1282            // expected
1283        }
1284    }
1285
1286    @Test
1287    public void TestRepeatedWallTime() {
1288        final Object[][] TESTDATA = {
1289            // Time zone            Input wall time                     WALLTIME_LAST in GMT                WALLTIME_FIRST in GMT
1290            {"America/New_York",    new CalFields(2011,11,6,0,59,59),   new CalFields(2011,11,6,4,59,59),   new CalFields(2011,11,6,4,59,59)},
1291            {"America/New_York",    new CalFields(2011,11,6,1,0,0),     new CalFields(2011,11,6,6,0,0),     new CalFields(2011,11,6,5,0,0)},
1292            {"America/New_York",    new CalFields(2011,11,6,1,0,1),     new CalFields(2011,11,6,6,0,1),     new CalFields(2011,11,6,5,0,1)},
1293            {"America/New_York",    new CalFields(2011,11,6,1,30,0),    new CalFields(2011,11,6,6,30,0),    new CalFields(2011,11,6,5,30,0)},
1294            {"America/New_York",    new CalFields(2011,11,6,1,59,59),   new CalFields(2011,11,6,6,59,59),   new CalFields(2011,11,6,5,59,59)},
1295            {"America/New_York",    new CalFields(2011,11,6,2,0,0),     new CalFields(2011,11,6,7,0,0),     new CalFields(2011,11,6,7,0,0)},
1296            {"America/New_York",    new CalFields(2011,11,6,2,0,1),     new CalFields(2011,11,6,7,0,1),     new CalFields(2011,11,6,7,0,1)},
1297
1298            {"Australia/Lord_Howe", new CalFields(2011,4,3,1,29,59),    new CalFields(2011,4,2,14,29,59),   new CalFields(2011,4,2,14,29,59)},
1299            {"Australia/Lord_Howe", new CalFields(2011,4,3,1,30,0),     new CalFields(2011,4,2,15,0,0),     new CalFields(2011,4,2,14,30,0)},
1300            {"Australia/Lord_Howe", new CalFields(2011,4,3,1,45,0),     new CalFields(2011,4,2,15,15,0),    new CalFields(2011,4,2,14,45,0)},
1301            {"Australia/Lord_Howe", new CalFields(2011,4,3,1,59,59),    new CalFields(2011,4,2,15,29,59),   new CalFields(2011,4,2,14,59,59)},
1302            {"Australia/Lord_Howe", new CalFields(2011,4,3,2,0,0),      new CalFields(2011,4,2,15,30,0),    new CalFields(2011,4,2,15,30,0)},
1303            {"Australia/Lord_Howe", new CalFields(2011,4,3,2,0,1),      new CalFields(2011,4,2,15,30,1),    new CalFields(2011,4,2,15,30,1)},
1304        };
1305
1306        Calendar calGMT = Calendar.getInstance(TimeZone.GMT_ZONE);
1307
1308        Calendar calDefault = Calendar.getInstance();
1309        Calendar calLast = Calendar.getInstance();
1310        Calendar calFirst = Calendar.getInstance();
1311
1312        calFirst.setRepeatedWallTimeOption(Calendar.WALLTIME_FIRST);
1313        calLast.setRepeatedWallTimeOption(Calendar.WALLTIME_LAST);
1314
1315        for (Object[] test : TESTDATA) {
1316            String tzid = (String)test[0];
1317            TimeZone tz = TimeZone.getTimeZone(tzid);
1318            CalFields in = (CalFields)test[1];
1319            CalFields expLastGMT = (CalFields)test[2];
1320            CalFields expFirstGMT = (CalFields)test[3];
1321
1322            // WALLTIME_LAST
1323            calLast.setTimeZone(tz);
1324            in.setTo(calLast);
1325            calGMT.setTimeInMillis(calLast.getTimeInMillis());
1326            CalFields outLastGMT = CalFields.createFrom(calGMT);
1327            if (!outLastGMT.equals(expLastGMT)) {
1328                errln("Fail: WALLTIME_LAST " + in + "[" + tzid + "] is parsed as " + outLastGMT + "[GMT]. Expected: " + expLastGMT + "[GMT]");
1329            }
1330
1331            // default
1332            calDefault.setTimeZone(tz);
1333            in.setTo(calDefault);
1334            calGMT.setTimeInMillis(calDefault.getTimeInMillis());
1335            CalFields outDefGMT = CalFields.createFrom(calGMT);
1336            if (!outDefGMT.equals(expLastGMT)) {
1337                errln("Fail: (default) " + in + "[" + tzid + "] is parsed as " + outDefGMT + "[GMT]. Expected: " + expLastGMT + "[GMT]");
1338            }
1339
1340            // WALLTIME_FIRST
1341            calFirst.setTimeZone(tz);
1342            in.setTo(calFirst);
1343            calGMT.setTimeInMillis(calFirst.getTimeInMillis());
1344            CalFields outFirstGMT = CalFields.createFrom(calGMT);
1345            if (!outFirstGMT.equals(expFirstGMT)) {
1346                errln("Fail: WALLTIME_FIRST " + in + "[" + tzid + "] is parsed as " + outFirstGMT + "[GMT]. Expected: " + expFirstGMT + "[GMT]");
1347            }
1348        }
1349    }
1350
1351    @Test
1352    public void TestSkippedWallTime() {
1353        final Object[][] TESTDATA = {
1354            // Time zone            Input wall time                     Valid wall time?
1355            {"America/New_York",    new CalFields(2011,3,13,1,59,59),   true,
1356                //  WALLTIME_LAST in GMT                WALLTIME_FIRST in GMT           WALLTIME_NEXT_VALID in GMT
1357                new CalFields(2011,3,13,6,59,59),   new CalFields(2011,3,13,6,59,59),   new CalFields(2011,3,13,6,59,59)},
1358
1359            {"America/New_York",    new CalFields(2011,3,13,2,0,0),     false,
1360                new CalFields(2011,3,13,7,0,0),     new CalFields(2011,3,13,6,0,0),     new CalFields(2011,3,13,7,0,0)},
1361
1362            {"America/New_York",    new CalFields(2011,3,13,2,1,0),     false,
1363                new CalFields(2011,3,13,7,1,0),     new CalFields(2011,3,13,6,1,0),     new CalFields(2011,3,13,7,0,0)},
1364
1365            {"America/New_York",    new CalFields(2011,3,13,2,30,0),    false,
1366                new CalFields(2011,3,13,7,30,0),    new CalFields(2011,3,13,6,30,0),    new CalFields(2011,3,13,7,0,0)},
1367
1368            {"America/New_York",    new CalFields(2011,3,13,2,59,59),   false,
1369                new CalFields(2011,3,13,7,59,59),   new CalFields(2011,3,13,6,59,59),   new CalFields(2011,3,13,7,0,0)},
1370
1371            {"America/New_York",    new CalFields(2011,3,13,3,0,0),     true,
1372                new CalFields(2011,3,13,7,0,0),     new CalFields(2011,3,13,7,0,0),     new CalFields(2011,3,13,7,0,0)},
1373
1374            {"Pacific/Apia",        new CalFields(2011,12,29,23,59,59), true,
1375                new CalFields(2011,12,30,9,59,59),  new CalFields(2011,12,30,9,59,59),  new CalFields(2011,12,30,9,59,59)},
1376
1377            {"Pacific/Apia",        new CalFields(2011,12,30,0,0,0),    false,
1378                new CalFields(2011,12,30,10,0,0),  new CalFields(2011,12,29,10,0,0),  new CalFields(2011,12,30,10,0,0)},
1379
1380            {"Pacific/Apia",        new CalFields(2011,12,30,12,0,0),   false,
1381                new CalFields(2011,12,30,22,0,0),  new CalFields(2011,12,29,22,0,0),  new CalFields(2011,12,30,10,0,0)},
1382
1383            {"Pacific/Apia",        new CalFields(2011,12,30,23,59,59), false,
1384                new CalFields(2011,12,31,9,59,59), new CalFields(2011,12,30,9,59,59), new CalFields(2011,12,30,10,0,0)},
1385
1386            {"Pacific/Apia",        new CalFields(2011,12,31,0,0,0),    true,
1387                new CalFields(2011,12,30,10,0,0),  new CalFields(2011,12,30,10,0,0),  new CalFields(2011,12,30,10,0,0)},
1388        };
1389
1390        Calendar calGMT = Calendar.getInstance(TimeZone.GMT_ZONE);
1391
1392        Calendar calDefault = Calendar.getInstance();
1393        Calendar calLast = Calendar.getInstance();
1394        Calendar calFirst = Calendar.getInstance();
1395        Calendar calNextAvail = Calendar.getInstance();
1396
1397        calLast.setSkippedWallTimeOption(Calendar.WALLTIME_LAST);
1398        calFirst.setSkippedWallTimeOption(Calendar.WALLTIME_FIRST);
1399        calNextAvail.setSkippedWallTimeOption(Calendar.WALLTIME_NEXT_VALID);
1400
1401        for (Object[] test : TESTDATA) {
1402            String tzid = (String)test[0];
1403            TimeZone tz = TimeZone.getTimeZone(tzid);
1404            CalFields in = (CalFields)test[1];
1405            boolean isValid = (Boolean)test[2];
1406            CalFields expLastGMT = (CalFields)test[3];
1407            CalFields expFirstGMT = (CalFields)test[4];
1408            CalFields expNextAvailGMT = (CalFields)test[5];
1409
1410            for (int i = 0; i < 2; i++) {
1411                boolean bLenient = (i == 0);
1412
1413                // WALLTIME_LAST
1414                calLast.setLenient(bLenient);
1415                calLast.setTimeZone(tz);
1416                try {
1417                    in.setTo(calLast);
1418                    calGMT.setTimeInMillis(calLast.getTimeInMillis());
1419                    CalFields outLastGMT = CalFields.createFrom(calGMT);
1420                    if (!bLenient && !isValid) {
1421                        errln("Fail: IllegalArgumentException expected - " + in + "[" + tzid + "] (WALLTIME_LAST)");
1422                    } else if (!outLastGMT.equals(expLastGMT)) {
1423                        errln("Fail: WALLTIME_LAST " + in + "[" + tzid + "] is parsed as " + outLastGMT + "[GMT]. Expected: " + expLastGMT + "[GMT]");
1424                    }
1425                } catch (IllegalArgumentException e) {
1426                    if (bLenient || isValid) {
1427                        errln("Fail: Unexpected IllegalArgumentException - " + in + "[" + tzid + "] (WALLTIME_LAST)");
1428                    }
1429                }
1430
1431                // default
1432                calDefault.setLenient(bLenient);
1433                calDefault.setTimeZone(tz);
1434                try {
1435                    in.setTo(calDefault);
1436                    calGMT.setTimeInMillis(calDefault.getTimeInMillis());
1437                    CalFields outDefGMT = CalFields.createFrom(calGMT);
1438                    if (!bLenient && !isValid) {
1439                        errln("Fail: IllegalArgumentException expected - " + in + "[" + tzid + "] (default)");
1440                    } else if (!outDefGMT.equals(expLastGMT)) {
1441                        errln("Fail: (default) " + in + "[" + tzid + "] is parsed as " + outDefGMT + "[GMT]. Expected: " + expLastGMT + "[GMT]");
1442                    }
1443                } catch (IllegalArgumentException e) {
1444                    if (bLenient || isValid) {
1445                        errln("Fail: Unexpected IllegalArgumentException - " + in + "[" + tzid + "] (default)");
1446                    }
1447                }
1448
1449                // WALLTIME_FIRST
1450                calFirst.setLenient(bLenient);
1451                calFirst.setTimeZone(tz);
1452                try {
1453                    in.setTo(calFirst);
1454                    calGMT.setTimeInMillis(calFirst.getTimeInMillis());
1455                    CalFields outFirstGMT = CalFields.createFrom(calGMT);
1456                    if (!bLenient && !isValid) {
1457                        errln("Fail: IllegalArgumentException expected - " + in + "[" + tzid + "] (WALLTIME_FIRST)");
1458                    } else if (!outFirstGMT.equals(expFirstGMT)) {
1459                        errln("Fail: WALLTIME_FIRST " + in + "[" + tzid + "] is parsed as " + outFirstGMT + "[GMT]. Expected: " + expFirstGMT + "[GMT]");
1460                    }
1461                } catch (IllegalArgumentException e) {
1462                    if (bLenient || isValid) {
1463                        errln("Fail: Unexpected IllegalArgumentException - " + in + "[" + tzid + "] (WALLTIME_FIRST)");
1464                    }
1465                }
1466
1467                // WALLTIME_NEXT_VALID
1468                calNextAvail.setLenient(bLenient);
1469                calNextAvail.setTimeZone(tz);
1470                try {
1471                    in.setTo(calNextAvail);
1472                    calGMT.setTimeInMillis(calNextAvail.getTimeInMillis());
1473                    CalFields outNextAvailGMT = CalFields.createFrom(calGMT);
1474                    if (!bLenient && !isValid) {
1475                        errln("Fail: IllegalArgumentException expected - " + in + "[" + tzid + "] (WALLTIME_NEXT_VALID)");
1476                    } else if (!outNextAvailGMT.equals(expNextAvailGMT)) {
1477                        errln("Fail: WALLTIME_NEXT_VALID " + in + "[" + tzid + "] is parsed as " + outNextAvailGMT + "[GMT]. Expected: " + expNextAvailGMT + "[GMT]");
1478                    }
1479                } catch (IllegalArgumentException e) {
1480                    if (bLenient || isValid) {
1481                        errln("Fail: Unexpected IllegalArgumentException - " + in + "[" + tzid + "] (WALLTIME_NEXT_VALID)");
1482                    }
1483                }
1484            }
1485        }
1486    }
1487
1488    @Test
1489    public void TestFieldDifference() {
1490        class TFDItem {
1491            public String tzname;
1492            public String locale;
1493            public long start;
1494            public long target;
1495            public boolean progressive; // true to compute progressive difference for each field, false to reset calendar after each call
1496            int yDiff;
1497            int MDiff;
1498            int dDiff;
1499            int HDiff;
1500            int mDiff;
1501            int sDiff; // 0x7FFFFFFF indicates overflow error expected
1502             // Simple constructor
1503            public TFDItem(String tz, String loc, long st, long tg, boolean prg, int yD, int MD, int dD, int HD, int mD, int sD ) {
1504                tzname = tz;
1505                locale = loc;
1506                start = st;
1507                target = tg;
1508                progressive = prg;
1509                yDiff = yD;
1510                MDiff = MD;
1511                dDiff = dD;
1512                HDiff = HD;
1513                mDiff = mD;
1514                sDiff = sD;
1515            }
1516        };
1517        final TFDItem[] tfdItems = {
1518            //           timezobe      locale        start            target            prog   yDf  MDf    dDf     HDf       mDf         sDf
1519            // For these we compute the progressive difference for each field - not resetting the calendar after each call
1520            new TFDItem( "US/Pacific", "en_US",        1267459800000L,  1277772600000L, true,    0,   3,    27,      9,       40,          0 ), // 2010-Mar-01 08:10 -> 2010-Jun-28 17:50
1521            new TFDItem( "US/Pacific", "en_US",        1267459800000L,  1299089280000L, true,    1,   0,     1,      1,       58,          0 ), // 2010-Mar-01 08:10 -> 2011-Mar-02 10:08
1522            // For these we compute the total difference for each field - resetting the calendar after each call
1523            new TFDItem( "GMT",        "en_US",        0,               1073692800000L, false,  34, 408, 12427, 298248, 17894880, 1073692800 ), // 1970-Jan-01 00:00 -> 2004-Jan-10 00:00
1524            new TFDItem( "GMT",        "en_US",        0,               1073779200000L, false,  34, 408, 12428, 298272, 17896320, 1073779200 ), // 1970-Jan-01 00:00 -> 2004-Jan-11 00:00
1525            new TFDItem( "GMT",        "en_US",        0,               2147472000000L, false,  68, 816, 24855, 596520, 35791200, 2147472000 ), // 1970-Jan-01 00:00 -> 2038-Jan-19 00:00
1526//          new TFDItem( "GMT",        "en_US",        0,               2147558400000L, false,  68, 816, 24856, 596544, 35792640, 0x7FFFFFFF ), // 1970-Jan-01 00:00 -> 2038-Jan-20 00:00, seconds overflow => exception in ICU4J
1527            new TFDItem( "GMT",        "en_US",        0,              -1073692800000L, false, -34,-408,-12427,-298248,-17894880,-1073692800 ), // 1970-Jan-01 00:00 -> 1935-Dec-24 00:00
1528            new TFDItem( "GMT",        "en_US",        0,              -1073779200000L, false, -34,-408,-12428,-298272,-17896320,-1073779200 ), // 1970-Jan-01 00:00 -> 1935-Dec-23 00:00
1529            // check fwd/backward on either side of era boundary and across era boundary
1530            new TFDItem( "GMT",        "en_US",      -61978089600000L,-61820409600000L, false,   4,  59,  1825,  43800,  2628000,  157680000 ), // CE   5-Dec-31 00:00 -> CE  10-Dec-30 00:00
1531            new TFDItem( "GMT",        "en_US",      -61820409600000L,-61978089600000L, false,  -4, -59, -1825, -43800, -2628000, -157680000 ), // CE  10-Dec-30 00:00 -> CE   5-Dec-31 00:00
1532            new TFDItem( "GMT",        "en_US",      -62451129600000L,-62293449600000L, false,   4,  59,  1825,  43800,  2628000,  157680000 ), // BCE 10-Jan-04 00:00 -> BCE  5-Jan-03 00:00
1533            new TFDItem( "GMT",        "en_US",      -62293449600000L,-62451129600000L, false,  -4, -59, -1825, -43800, -2628000, -157680000 ), // BCE  5-Jan-03 00:00 -> BCE 10-Jan-04 00:00
1534            new TFDItem( "GMT",        "en_US",      -62293449600000L,-61978089600000L, false,   9, 119,  3650,  87600,  5256000,  315360000 ), // BCE  5-Jan-03 00:00 -> CE   5-Dec-31 00:00
1535            new TFDItem( "GMT",        "en_US",      -61978089600000L,-62293449600000L, false,  -9,-119, -3650, -87600, -5256000, -315360000 ), // CE   5-Dec-31 00:00 -> BCE  5-Jan-03 00:00
1536            new TFDItem( "GMT", "en@calendar=roc",    -1672704000000L, -1515024000000L, false,   4,  59,  1825,  43800,  2628000,  157680000 ), // MG   5-Dec-30 00:00 -> MG  10-Dec-29 00:00
1537            new TFDItem( "GMT", "en@calendar=roc",    -1515024000000L, -1672704000000L, false,  -4, -59, -1825, -43800, -2628000, -157680000 ), // MG  10-Dec-29 00:00 -> MG   5-Dec-30 00:00
1538            new TFDItem( "GMT", "en@calendar=roc",    -2145744000000L, -1988064000000L, false,   4,  59,  1825,  43800,  2628000,  157680000 ), // BMG 10-Jan-03 00:00 -> BMG  5-Jan-02 00:00
1539            new TFDItem( "GMT", "en@calendar=roc",    -1988064000000L, -2145744000000L, false,  -4, -59, -1825, -43800, -2628000, -157680000 ), // BMG  5-Jan-02 00:00 -> BMG 10-Jan-03 00:00
1540            new TFDItem( "GMT", "en@calendar=roc",    -1988064000000L, -1672704000000L, false,   9, 119,  3650,  87600,  5256000,  315360000 ), // BMG  5-Jan-02 00:00 -> MG   5-Dec-30 00:00
1541            new TFDItem( "GMT", "en@calendar=roc",    -1672704000000L, -1988064000000L, false,  -9,-119, -3650, -87600, -5256000, -315360000 ), // MG   5-Dec-30 00:00 -> BMG  5-Jan-02 00:00
1542            new TFDItem( "GMT", "en@calendar=coptic",-53026531200000L,-52868851200000L, false,   4,  64,  1825,  43800,  2628000,  157680000 ), // Er1  5-Nas-05 00:00 -> Er1 10-Nas-04 00:00
1543            new TFDItem( "GMT", "en@calendar=coptic",-52868851200000L,-53026531200000L, false,  -4, -64, -1825, -43800, -2628000, -157680000 ), // Er1 10-Nas-04 00:00 -> Er1  5-Nas-05 00:00
1544            new TFDItem( "GMT", "en@calendar=coptic",-53499571200000L,-53341891200000L, false,   4,  64,  1825,  43800,  2628000,  157680000 ), // Er0 10-Tou-04 00:00 -> Er0  5-Tou-02 00:00
1545            new TFDItem( "GMT", "en@calendar=coptic",-53341891200000L,-53499571200000L, false,  -4, -64, -1825, -43800, -2628000, -157680000 ), // Er0  5-Tou-02 00:00 -> Er0 10-Tou-04 00:00
1546            new TFDItem( "GMT", "en@calendar=coptic",-53341891200000L,-53026531200000L, false,   9, 129,  3650,  87600,  5256000,  315360000 ), // Er0  5-Tou-02 00:00 -> Er1  5-Nas-05 00:00
1547            new TFDItem( "GMT", "en@calendar=coptic",-53026531200000L,-53341891200000L, false,  -9,-129, -3650, -87600, -5256000, -315360000 ), // Er1  5-Nas-05 00:00 -> Er0  5-Tou-02 00:00
1548        };
1549        for (TFDItem tfdItem: tfdItems) {
1550            TimeZone timezone = TimeZone.getFrozenTimeZone(tfdItem.tzname);
1551            Calendar ucal = Calendar.getInstance(timezone, new ULocale(tfdItem.locale));
1552            ucal.setTimeInMillis(tfdItem.target);
1553            Date targetDate = ucal.getTime();
1554            int yDf, MDf, dDf, HDf, mDf, sDf;
1555            if (tfdItem.progressive) {
1556                ucal.setTimeInMillis(tfdItem.start);
1557                yDf = ucal.fieldDifference(targetDate, YEAR);
1558                MDf = ucal.fieldDifference(targetDate, MONTH);
1559                dDf = ucal.fieldDifference(targetDate, DATE);
1560                HDf = ucal.fieldDifference(targetDate, HOUR);
1561                mDf = ucal.fieldDifference(targetDate, MINUTE);
1562                sDf = ucal.fieldDifference(targetDate, SECOND);
1563                if ( yDf != tfdItem.yDiff || MDf != tfdItem.MDiff || dDf != tfdItem.dDiff || HDf != tfdItem.HDiff || mDf != tfdItem.mDiff || sDf != tfdItem.sDiff ) {
1564                    errln("Fail: for locale \"" + tfdItem.locale + "\", start " + tfdItem.start + ", target " +  tfdItem.target + ", expected y-M-d-H-m-s progressive diffs " +
1565                            tfdItem.yDiff +","+ tfdItem.MDiff +","+ tfdItem.dDiff +","+ tfdItem.HDiff +","+ tfdItem.mDiff +","+ tfdItem.sDiff + ", got " +
1566                            yDf +","+ MDf +","+ dDf +","+ HDf +","+ mDf +","+ sDf);
1567                }
1568            } else {
1569                ucal.setTimeInMillis(tfdItem.start);
1570                yDf = ucal.fieldDifference(targetDate, YEAR);
1571                ucal.setTimeInMillis(tfdItem.start);
1572                MDf = ucal.fieldDifference(targetDate, MONTH);
1573                ucal.setTimeInMillis(tfdItem.start);
1574                dDf = ucal.fieldDifference(targetDate, DATE);
1575                ucal.setTimeInMillis(tfdItem.start);
1576                HDf = ucal.fieldDifference(targetDate, HOUR);
1577                ucal.setTimeInMillis(tfdItem.start);
1578                mDf = ucal.fieldDifference(targetDate, MINUTE);
1579                if ( yDf != tfdItem.yDiff || MDf != tfdItem.MDiff || dDf != tfdItem.dDiff || HDf != tfdItem.HDiff || mDf != tfdItem.mDiff ) {
1580                    errln("Fail: for locale \"" + tfdItem.locale + "\", start " + tfdItem.start + ", target " +  tfdItem.target + ", expected y-M-d-H-m total diffs " +
1581                            tfdItem.yDiff +","+ tfdItem.MDiff +","+ tfdItem.dDiff +","+ tfdItem.HDiff +","+ tfdItem.mDiff + ", got " +
1582                            yDf +","+ MDf +","+ dDf +","+ HDf +","+ mDf);
1583                }
1584                ucal.setTimeInMillis(tfdItem.start);
1585                sDf = ucal.fieldDifference(targetDate, SECOND);
1586                if ( sDf != 0x7FFFFFFF && sDf != tfdItem.sDiff ) {
1587                    errln("Fail: for locale \"" + tfdItem.locale + "\", start " + tfdItem.start + ", target " +  tfdItem.target + ", expected seconds total diffs " +
1588                            tfdItem.sDiff + ", got " + sDf);
1589                }
1590            }
1591        }
1592    }
1593
1594    @Test
1595    public void TestAddRollEra0AndEraBounds() {
1596        final String[] localeIDs = {
1597            // calendars with non-modern era 0 that goes backwards, max era == 1
1598            "en@calendar=gregorian",
1599            "en@calendar=roc",
1600            "en@calendar=coptic",
1601            // calendars with non-modern era 0 that goes forwards, max era > 1
1602            "en@calendar=japanese",
1603            "en@calendar=chinese",
1604            // calendars with non-modern era 0 that goes forwards, max era == 1
1605            "en@calendar=ethiopic",
1606            // calendars with only one era  = 0, forwards
1607            "en@calendar=buddhist",
1608            "en@calendar=hebrew",
1609            "en@calendar=islamic",
1610            "en@calendar=indian",
1611            //"en@calendar=persian", // no persian calendar in ICU4J yet
1612            "en@calendar=ethiopic-amete-alem",
1613        };
1614        TimeZone zoneGMT = TimeZone.getFrozenTimeZone("GMT");
1615        for (String localeID : localeIDs) {
1616            Calendar ucalTest = Calendar.getInstance(zoneGMT, new ULocale(localeID));
1617            String calType = ucalTest.getType();
1618            boolean era0YearsGoBackwards = (calType.equals("gregorian") || calType.equals("roc") || calType.equals("coptic"));
1619            int yrBefore, yrAfter, yrMax, eraAfter, eraMax, eraNow;
1620
1621            ucalTest.clear();
1622            ucalTest.set(Calendar.YEAR, 2);
1623            ucalTest.set(Calendar.ERA, 0);
1624            yrBefore = ucalTest.get(Calendar.YEAR);
1625            ucalTest.add(Calendar.YEAR, 1);
1626            yrAfter = ucalTest.get(Calendar.YEAR);
1627            if ( (era0YearsGoBackwards && yrAfter>yrBefore) || (!era0YearsGoBackwards && yrAfter<yrBefore) ) {
1628                errln("Fail: era 0 add 1 year does not move forward in time for " + localeID);
1629            }
1630
1631            ucalTest.clear();
1632            ucalTest.set(Calendar.YEAR, 2);
1633            ucalTest.set(Calendar.ERA, 0);
1634            yrBefore = ucalTest.get(Calendar.YEAR);
1635            ucalTest.roll(Calendar.YEAR, 1);
1636            yrAfter = ucalTest.get(Calendar.YEAR);
1637            if ( (era0YearsGoBackwards && yrAfter>yrBefore) || (!era0YearsGoBackwards && yrAfter<yrBefore) ) {
1638                errln("Fail: era 0 roll 1 year does not move forward in time for " + localeID);
1639            }
1640
1641            ucalTest.clear();
1642            ucalTest.set(Calendar.YEAR, 1);
1643            ucalTest.set(Calendar.ERA, 0);
1644            if (era0YearsGoBackwards) {
1645                ucalTest.roll(Calendar.YEAR, 1);
1646                yrAfter = ucalTest.get(Calendar.YEAR);
1647                eraAfter = ucalTest.get(Calendar.ERA);
1648                if (eraAfter != 0 || yrAfter != 1) {
1649                    errln("Fail: era 0 roll 1 year from year 1 does not stay within era or pin to year 1 for "
1650                            + localeID + " (get era " + eraAfter + " year " + yrAfter + ")");
1651                }
1652            } else {
1653                // roll backward in time to where era 0 years go negative, except for the Chinese
1654                // calendar, which uses negative eras instead of having years outside the range 1-60
1655                ucalTest.roll(Calendar.YEAR, -2);
1656                yrAfter = ucalTest.get(Calendar.YEAR);
1657                eraAfter = ucalTest.get(Calendar.ERA);
1658                if ( !calType.equals("chinese") && (eraAfter != 0 || yrAfter != -1) ) {
1659                    errln("Fail: era 0 roll -2 years from year 1 does not stay within era or produce year -1 for "
1660                            + localeID + " (get era " + eraAfter + " year " + yrAfter + ")");
1661                }
1662            }
1663
1664            ucalTest.clear();
1665            {
1666                int eraMin = ucalTest.getMinimum(Calendar.ERA);
1667                if (eraMin != 0 && calType.compareTo("chinese") != 0) {
1668                    errln("Fail: getMinimum returns minimum era " + eraMin + " (should be 0) for calType " + calType);
1669                }
1670            }
1671
1672            ucalTest.clear();
1673            ucalTest.set(Calendar.YEAR, 1);
1674            ucalTest.set(Calendar.ERA, 0);
1675            eraMax = ucalTest.getMaximum(Calendar.ERA);
1676            if (eraMax > 0) {
1677                // try similar tests for era 1 (if calendar has it), in which years always go forward
1678
1679                ucalTest.clear();
1680                ucalTest.set(Calendar.YEAR, 2);
1681                ucalTest.set(Calendar.ERA, 1);
1682                yrBefore = ucalTest.get(Calendar.YEAR);
1683                ucalTest.add(Calendar.YEAR, 1);
1684                yrAfter = ucalTest.get(Calendar.YEAR);
1685                if ( yrAfter<yrBefore ) {
1686                    errln("Fail: era 1 add 1 year does not move forward in time for " + localeID);
1687                }
1688
1689                ucalTest.clear();
1690                ucalTest.set(Calendar.YEAR, 2);
1691                ucalTest.set(Calendar.ERA, 1);
1692                yrBefore = ucalTest.get(Calendar.YEAR);
1693                ucalTest.roll(Calendar.YEAR, 1);
1694                yrAfter = ucalTest.get(Calendar.YEAR);
1695                if ( yrAfter<yrBefore ) {
1696                    errln("Fail: era 1 roll 1 year does not move forward in time for " + localeID);
1697                }
1698
1699                ucalTest.clear();
1700                ucalTest.set(Calendar.YEAR, 1);
1701                ucalTest.set(Calendar.ERA, 1);
1702                yrMax = ucalTest.getActualMaximum(Calendar.YEAR);
1703                ucalTest.roll(Calendar.YEAR, -1); // roll down which should pin or wrap to end
1704                yrAfter = ucalTest.get(Calendar.YEAR);
1705                eraAfter = ucalTest.get(Calendar.ERA);
1706                // if yrMax is reasonable we should wrap to that, else we should pin at yr 1
1707                if (yrMax >= 32768) {
1708                    if (eraAfter != 1 || yrAfter != 1) {
1709                        errln("Fail: era 1 roll -1 year from year 1 does not stay within era or pin to year 1 for "
1710                                + localeID + " (get era " + eraAfter + " year " + yrAfter + ")");
1711                    }
1712                } else if (eraAfter != 1 || yrAfter != yrMax) {
1713                    errln("Fail: era 1 roll -1 year from year 1 does not stay within era or wrap to year "
1714                            + yrMax + " for " + localeID + " (get era " + eraAfter + " year " + yrAfter + ")");
1715                } else {
1716                    ucalTest.roll(Calendar.YEAR, 1); // now roll up which should wrap to beginning
1717                    yrAfter = ucalTest.get(Calendar.YEAR);
1718                    eraAfter = ucalTest.get(Calendar.ERA);
1719                    if (eraAfter != 1 || yrAfter != 1) {
1720                        errln("Fail: era 1 roll 1 year from year " + yrMax +
1721                                " does not stay within era or wrap to year 1 for "
1722                                + localeID + " (get era " + eraAfter + " year " + yrAfter + ")");
1723                    }
1724                }
1725
1726                // if current era  > 1, try the same roll tests for current era
1727                ucalTest.setTime(new Date());
1728                eraNow = ucalTest.get(Calendar.ERA);
1729                if (eraNow > 1) {
1730                    ucalTest.clear();
1731                    ucalTest.set(Calendar.YEAR, 1);
1732                    ucalTest.set(Calendar.ERA, eraNow);
1733                    yrMax = ucalTest.getActualMaximum(Calendar.YEAR); // max year value for this era
1734                    ucalTest.roll(Calendar.YEAR, -1);
1735                    yrAfter = ucalTest.get(Calendar.YEAR);
1736                    eraAfter = ucalTest.get(Calendar.ERA);
1737                    // if yrMax is reasonable we should wrap to that, else we should pin at yr 1
1738                    if (yrMax >= 32768) {
1739                        if (eraAfter != eraNow || yrAfter != 1) {
1740                            errln("Fail: era " + eraNow +
1741                                    " roll -1 year from year 1 does not stay within era or pin to year 1 for "
1742                                    + localeID + " (get era " + eraAfter + " year " + yrAfter + ")");
1743                        }
1744                    } else if (eraAfter != eraNow || yrAfter != yrMax) {
1745                        errln("Fail: era " + eraNow +
1746                                " roll -1 year from year 1 does not stay within era or wrap to year " + yrMax
1747                                + " for " + localeID + " (get era " + eraAfter + " year " + yrAfter + ")");
1748                    } else {
1749                        ucalTest.roll(Calendar.YEAR, 1); // now roll up which should wrap to beginning
1750                        yrAfter = ucalTest.get(Calendar.YEAR);
1751                        eraAfter = ucalTest.get(Calendar.ERA);
1752                        if (eraAfter != eraNow || yrAfter != 1) {
1753                            errln("Fail: era " + eraNow + " roll 1 year from year " + yrMax +
1754                                    " does not stay within era or wrap to year 1 for "
1755                                    + localeID + " (get era " + eraAfter + " year " + yrAfter + ")");
1756                        }
1757                    }
1758                }
1759            }
1760        }
1761    }
1762
1763    @Test
1764    public void TestWeekData() {
1765        // Each line contains two locales using the same set of week rule data.
1766        final String LOCALE_PAIRS[] = {
1767            "en",       "en_US",
1768            "de",       "de_DE",
1769            "de_DE",    "en_DE",
1770            "en_GB",    "und_GB",
1771            "ar_EG",    "en_EG",
1772            "ar_SA",    "fr_SA",
1773        };
1774
1775        for (int i = 0; i < LOCALE_PAIRS.length; i += 2) {
1776            Calendar cal1 = Calendar.getInstance(new ULocale(LOCALE_PAIRS[i]));
1777            Calendar cal2 = Calendar.getInstance(new ULocale(LOCALE_PAIRS[i + 1]));
1778
1779            // First day of week
1780            int dow1 = cal1.getFirstDayOfWeek();
1781            int dow2 = cal2.getFirstDayOfWeek();
1782            if (dow1 != dow2) {
1783                errln("getFirstDayOfWeek: " + LOCALE_PAIRS[i] + "->" + dow1 + ", " + LOCALE_PAIRS[i + 1] + "->" + dow2);
1784            }
1785
1786            // Minimum days in first week
1787            int minDays1 = cal1.getMinimalDaysInFirstWeek();
1788            int minDays2 = cal2.getMinimalDaysInFirstWeek();
1789            if (minDays1 != minDays2) {
1790                errln("getMinimalDaysInFirstWeek: " + LOCALE_PAIRS[i] + "->" + minDays1 + ", " + LOCALE_PAIRS[i + 1] + "->" + minDays2);
1791            }
1792
1793            // Weekdays and Weekends
1794            for (int d = Calendar.SUNDAY; d <= Calendar.SATURDAY; d++) {
1795                int wdt1 = cal1.getDayOfWeekType(d);
1796                int wdt2 = cal2.getDayOfWeekType(d);
1797                if (wdt1 != wdt2) {
1798                    errln("getDayOfWeekType(" + d + "): " + LOCALE_PAIRS[i] + "->" + wdt1 + ", " + LOCALE_PAIRS[i + 1] + "->" + wdt2);
1799                }
1800            }
1801        }
1802    }
1803
1804    @Test
1805    public void TestAddAcrossZoneTransition() {
1806        class TestData {
1807            String zone;
1808            CalFields base;
1809            int deltaDays;
1810            int skippedWTOpt;
1811            CalFields expected;
1812
1813            TestData(String zone, CalFields base, int deltaDays, int skippedWTOpt, CalFields expected) {
1814                this.zone = zone;
1815                this.base = base;
1816                this.deltaDays = deltaDays;
1817                this.skippedWTOpt = skippedWTOpt;
1818                this.expected = expected;
1819            }
1820        }
1821
1822        TestData[] data = new TestData[] {
1823            // Add 1 day, from the date before DST transition
1824            new TestData("America/Los_Angeles", new CalFields(2014, 3, 8, 1, 59, 59, 999), 1, Calendar.WALLTIME_FIRST,
1825                                                new CalFields(2014, 3, 9, 1, 59, 59, 999)),
1826
1827            new TestData("America/Los_Angeles", new CalFields(2014, 3, 8, 1, 59, 59, 999), 1, Calendar.WALLTIME_LAST,
1828                                                new CalFields(2014, 3, 9, 1, 59, 59, 999)),
1829
1830            new TestData("America/Los_Angeles", new CalFields(2014, 3, 8, 1, 59, 59, 999), 1, Calendar.WALLTIME_NEXT_VALID,
1831                                                new CalFields(2014, 3, 9, 1, 59, 59, 999)),
1832
1833
1834            new TestData("America/Los_Angeles", new CalFields(2014, 3, 8, 2, 0, 0, 0), 1, Calendar.WALLTIME_FIRST,
1835                                                new CalFields(2014, 3, 9, 1, 0, 0, 0)),
1836
1837            new TestData("America/Los_Angeles", new CalFields(2014, 3, 8, 2, 0, 0, 0), 1, Calendar.WALLTIME_LAST,
1838                                                new CalFields(2014, 3, 9, 3, 0, 0, 0)),
1839
1840            new TestData("America/Los_Angeles", new CalFields(2014, 3, 8, 2, 0, 0, 0), 1, Calendar.WALLTIME_NEXT_VALID,
1841                                                new CalFields(2014, 3, 9, 3, 0, 0, 0)),
1842
1843
1844            new TestData("America/Los_Angeles", new CalFields(2014, 3, 8, 2, 30, 0, 0), 1, Calendar.WALLTIME_FIRST,
1845                                                new CalFields(2014, 3, 9, 1, 30, 0, 0)),
1846
1847            new TestData("America/Los_Angeles", new CalFields(2014, 3, 8, 2, 30, 0, 0), 1, Calendar.WALLTIME_LAST,
1848                                                new CalFields(2014, 3, 9, 3, 30, 0, 0)),
1849
1850            new TestData("America/Los_Angeles", new CalFields(2014, 3, 8, 2, 30, 0, 0), 1, Calendar.WALLTIME_NEXT_VALID,
1851                                                new CalFields(2014, 3, 9, 3, 0, 0, 0)),
1852
1853
1854            new TestData("America/Los_Angeles", new CalFields(2014, 3, 8, 3, 0, 0, 0), 1, Calendar.WALLTIME_FIRST,
1855                                                new CalFields(2014, 3, 9, 3, 0, 0, 0)),
1856
1857            new TestData("America/Los_Angeles", new CalFields(2014, 3, 8, 3, 0, 0, 0), 1, Calendar.WALLTIME_LAST,
1858                                                new CalFields(2014, 3, 9, 3, 0, 0, 0)),
1859
1860            new TestData("America/Los_Angeles", new CalFields(2014, 3, 8, 3, 0, 0, 0), 1, Calendar.WALLTIME_NEXT_VALID,
1861                                                new CalFields(2014, 3, 9, 3, 0, 0, 0)),
1862
1863
1864            // Subtract 1 day, from one day after DST transition
1865            new TestData("America/Los_Angeles", new CalFields(2014, 3, 10, 1, 59, 59, 999), -1, Calendar.WALLTIME_FIRST,
1866                                                new CalFields(2014, 3, 9, 1, 59, 59, 999)),
1867
1868            new TestData("America/Los_Angeles", new CalFields(2014, 3, 10, 1, 59, 59, 999), -1, Calendar.WALLTIME_LAST,
1869                                                new CalFields(2014, 3, 9, 1, 59, 59, 999)),
1870
1871            new TestData("America/Los_Angeles", new CalFields(2014, 3, 10, 1, 59, 59, 999), -1, Calendar.WALLTIME_NEXT_VALID,
1872                                                new CalFields(2014, 3, 9, 1, 59, 59, 999)),
1873
1874
1875            new TestData("America/Los_Angeles", new CalFields(2014, 3, 10, 2, 0, 0, 0), -1, Calendar.WALLTIME_FIRST,
1876                                                new CalFields(2014, 3, 9, 1, 0, 0, 0)),
1877
1878            new TestData("America/Los_Angeles", new CalFields(2014, 3, 10, 2, 0, 0, 0), -1, Calendar.WALLTIME_LAST,
1879                                                new CalFields(2014, 3, 9, 3, 0, 0, 0)),
1880
1881            new TestData("America/Los_Angeles", new CalFields(2014, 3, 10, 2, 0, 0, 0), -1, Calendar.WALLTIME_NEXT_VALID,
1882                                                new CalFields(2014, 3, 9, 3, 0, 0, 0)),
1883
1884
1885            new TestData("America/Los_Angeles", new CalFields(2014, 3, 10, 2, 30, 0, 0), -1, Calendar.WALLTIME_FIRST,
1886                                                new CalFields(2014, 3, 9, 1, 30, 0, 0)),
1887
1888            new TestData("America/Los_Angeles", new CalFields(2014, 3, 10, 2, 30, 0, 0), -1, Calendar.WALLTIME_LAST,
1889                                                new CalFields(2014, 3, 9, 3, 30, 0, 0)),
1890
1891            new TestData("America/Los_Angeles", new CalFields(2014, 3, 10, 2, 30, 0, 0), -1, Calendar.WALLTIME_NEXT_VALID,
1892                                                new CalFields(2014, 3, 9, 3, 0, 0, 0)),
1893
1894
1895            new TestData("America/Los_Angeles", new CalFields(2014, 3, 10, 3, 0, 0, 0), -1, Calendar.WALLTIME_FIRST,
1896                                                new CalFields(2014, 3, 9, 3, 0, 0, 0)),
1897
1898            new TestData("America/Los_Angeles", new CalFields(2014, 3, 10, 3, 0, 0, 0), -1, Calendar.WALLTIME_LAST,
1899                                                new CalFields(2014, 3, 9, 3, 0, 0, 0)),
1900
1901            new TestData("America/Los_Angeles", new CalFields(2014, 3, 10, 3, 0, 0, 0), -1, Calendar.WALLTIME_NEXT_VALID,
1902                                                new CalFields(2014, 3, 9, 3, 0, 0, 0)),
1903
1904
1905            // Test case for ticket#10544
1906            new TestData("America/Santiago",    new CalFields(2013, 4, 27, 0, 0, 0, 0), 134, Calendar.WALLTIME_FIRST,
1907                                                new CalFields(2013, 9, 7, 23, 0, 0, 0)),
1908
1909            new TestData("America/Santiago",    new CalFields(2013, 4, 27, 0, 0, 0, 0), 134, Calendar.WALLTIME_LAST,
1910                                                new CalFields(2013, 9, 8, 1, 0, 0, 0)),
1911
1912            new TestData("America/Santiago",    new CalFields(2013, 4, 27, 0, 0, 0, 0), 134, Calendar.WALLTIME_NEXT_VALID,
1913                                                new CalFields(2013, 9, 8, 1, 0, 0, 0)),
1914
1915
1916            new TestData("America/Santiago",    new CalFields(2013, 4, 27, 0, 30, 0, 0), 134, Calendar.WALLTIME_FIRST,
1917                                                new CalFields(2013, 9, 7, 23, 30, 0, 0)),
1918
1919            new TestData("America/Santiago",    new CalFields(2013, 4, 27, 0, 30, 0, 0), 134, Calendar.WALLTIME_LAST,
1920                                                new CalFields(2013, 9, 8, 1, 30, 0, 0)),
1921
1922            new TestData("America/Santiago",    new CalFields(2013, 4, 27, 0, 30, 0, 0), 134, Calendar.WALLTIME_NEXT_VALID,
1923                                                new CalFields(2013, 9, 8, 1, 0, 0, 0)),
1924
1925
1926            // Extreme transition - Pacific/Apia completely skips 2011-12-30
1927            new TestData("Pacific/Apia",        new CalFields(2011, 12, 29, 0, 0, 0, 0), 1, Calendar.WALLTIME_FIRST,
1928                                                new CalFields(2011, 12, 31, 0, 0, 0, 0)),
1929
1930            new TestData("Pacific/Apia",        new CalFields(2011, 12, 29, 0, 0, 0, 0), 1, Calendar.WALLTIME_LAST,
1931                                                new CalFields(2011, 12, 31, 0, 0, 0, 0)),
1932
1933            new TestData("Pacific/Apia",        new CalFields(2011, 12, 29, 0, 0, 0, 0), 1, Calendar.WALLTIME_NEXT_VALID,
1934                                                new CalFields(2011, 12, 31, 0, 0, 0, 0)),
1935
1936
1937            new TestData("Pacific/Apia",        new CalFields(2011, 12, 31, 12, 0, 0, 0), -1, Calendar.WALLTIME_FIRST,
1938                                                new CalFields(2011, 12, 29, 12, 0, 0, 0)),
1939
1940            new TestData("Pacific/Apia",        new CalFields(2011, 12, 31, 12, 0, 0, 0), -1, Calendar.WALLTIME_LAST,
1941                                                new CalFields(2011, 12, 29, 12, 0, 0, 0)),
1942
1943            new TestData("Pacific/Apia",        new CalFields(2011, 12, 31, 12, 0, 0, 0), -1, Calendar.WALLTIME_NEXT_VALID,
1944                                                new CalFields(2011, 12, 29, 12, 0, 0, 0)),
1945
1946
1947            // 30 minutes DST - Australia/Lord_Howe
1948            new TestData("Australia/Lord_Howe", new CalFields(2013, 10, 5, 2, 15, 0, 0), 1, Calendar.WALLTIME_FIRST,
1949                                                new CalFields(2013, 10, 6, 1, 45, 0, 0)),
1950
1951            new TestData("Australia/Lord_Howe", new CalFields(2013, 10, 5, 2, 15, 0, 0), 1, Calendar.WALLTIME_LAST,
1952                                                new CalFields(2013, 10, 6, 2, 45, 0, 0)),
1953
1954            new TestData("Australia/Lord_Howe", new CalFields(2013, 10, 5, 2, 15, 0, 0), 1, Calendar.WALLTIME_NEXT_VALID,
1955                                                new CalFields(2013, 10, 6, 2, 30, 0, 0)),
1956        };
1957
1958        Calendar cal = Calendar.getInstance();
1959        for (TestData d : data) {
1960            cal.setTimeZone(TimeZone.getTimeZone(d.zone));
1961            cal.setSkippedWallTimeOption(d.skippedWTOpt);
1962            d.base.setTo(cal);
1963            cal.add(Calendar.DATE, d.deltaDays);
1964
1965            if (!d.expected.isEquivalentTo(cal)) {
1966                CalFields res = CalFields.createFrom(cal);
1967                String optDisp = d.skippedWTOpt == Calendar.WALLTIME_FIRST ? "FIRST" :
1968                    d.skippedWTOpt == Calendar.WALLTIME_LAST ? "LAST" : "NEXT_VALID";
1969                errln("Error: base:" + d.base.toString() + ", tz:" + d.zone
1970                        + ", delta:" + d.deltaDays + " day(s), opt:" + optDisp
1971                        + ", result:" + res.toString() + " - expected:" + d.expected.toString());
1972            }
1973        }
1974    }
1975
1976    @Test
1977    public void TestSimpleDateFormatCoverage() {
1978
1979        class StubSimpleDateFormat extends SimpleDateFormat {
1980            private static final long serialVersionUID = 1L;
1981
1982            public StubSimpleDateFormat(String pattern, Locale loc) {
1983                new SimpleDateFormat(pattern, loc);
1984            }
1985
1986            public void run(){
1987                Calendar cal = Calendar.getInstance(Locale.US);
1988                cal.clear();
1989                cal.set(2000, Calendar.MARCH, 18, 15,  0, 1); // Sat 15:00
1990
1991                DateFormatSymbols theseSymbols = this.getSymbols();
1992                String shouldBeMonday = theseSymbols.getWeekdays()[Calendar.MONDAY];
1993                assertEquals("Should be Monday", "Monday", shouldBeMonday);
1994
1995                String [] matchData = {"16", "2016", "2016AD", "Monday", "lunes"};
1996                int matchIndex =  matchString("Monday March 28, 2016", 0, Calendar.DAY_OF_WEEK, matchData, cal);
1997                assertEquals("matchData for Monday", 6, matchIndex); // Position of the pointer after the matched string.
1998                matchIndex =  matchString("Monday March 28, 2016 AD", 17, Calendar.YEAR, matchData, cal);
1999                assertEquals("matchData for 2016", 21, matchIndex); // Position of the pointer after the matched string.
2000
2001                char ch = 'y';
2002                int count = 4;
2003                int beginOffset = 0;
2004                cal.set(Calendar.YEAR, 2000);  // Reset this
2005                assertEquals("calendar year reset", 2000, cal.get(Calendar.YEAR));
2006                FieldPosition pos = new FieldPosition(java.text.DateFormat.YEAR_FIELD);
2007                String subFormatResult = subFormat(ch, count, beginOffset,
2008                        pos, theseSymbols, cal);
2009                assertEquals("subFormat result", "2000", subFormatResult);
2010
2011                String testParseString = "some text with a date 2017-03-15";
2012                int start = 22;
2013                boolean obeyCount = true;
2014                boolean allowNegative = false;
2015                boolean ambiguousYear[] = {true, false, true};
2016                int subParseResult = subParse(testParseString, start, ch, count,
2017                        obeyCount, allowNegative, ambiguousYear, cal);
2018                assertEquals("subParseResult result", 26, subParseResult);
2019                assertEquals("parsed year", 2017, cal.get(Calendar.YEAR));
2020            }
2021        }
2022        StubSimpleDateFormat stub = new StubSimpleDateFormat("EEE MMM dd yyyy G HH:mm:ss.SSS", Locale.US);
2023        stub.run();
2024    }
2025}
2026