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