1/* GENERATED SOURCE. DO NOT MODIFY. */
2// © 2016 and later: Unicode, Inc. and others.
3// License & terms of use: http://www.unicode.org/copyright.html#License
4/*
5 *******************************************************************************
6 * Copyright (C) 2012, International Business Machines Corporation and         *
7 * others. All Rights Reserved.                                                *
8 *******************************************************************************
9 */
10package android.icu.dev.test.calendar;
11import java.util.Date;
12
13import org.junit.Test;
14
15import android.icu.text.DateFormat;
16import android.icu.util.Calendar;
17import android.icu.util.DangiCalendar;
18import android.icu.util.GregorianCalendar;
19import android.icu.util.TimeZone;
20import android.icu.util.ULocale;
21
22public class DangiTest extends CalendarTestFmwk {
23    /**
24     * Test basic mapping to and from Gregorian.
25     */
26    @Test
27    public void TestMapping() {
28        final int[] DATA = {
29            // (Note: months are 1-based)
30            // Gregorian    Korean (Dan-gi)
31            1964,  9,  4,   4297,  7,0, 28,
32            1964,  9,  5,   4297,  7,0, 29,
33            1964,  9,  6,   4297,  8,0,  1,
34            1964,  9,  7,   4297,  8,0,  2,
35            1961, 12, 25,   4294, 11,0, 18,
36            1999,  6,  4,   4332,  4,0, 21,
37
38            1990,  5, 23,   4323,  4,0, 29,
39            1990,  5, 24,   4323,  5,0,  1,
40            1990,  6, 22,   4323,  5,0, 30,
41            1990,  6, 23,   4323,  5,1,  1,
42            1990,  7, 20,   4323,  5,1, 28,
43            1990,  7, 21,   4323,  5,1, 29,
44            1990,  7, 22,   4323,  6,0,  1,
45
46            // Some tricky dates (where GMT+8 doesn't agree with GMT+9)
47            //
48            // The list is from http://www.math.snu.ac.kr/~kye/others/lunar.html ('kye ref').
49            // However, for some dates disagree with the above reference so KASI's
50            // calculation was cross-referenced:
51            //  http://astro.kasi.re.kr/Life/ConvertSolarLunarForm.aspx?MenuID=115
52            1880, 11,  3,   4213, 10,0,  1, // astronomer's GMT+8 / KASI disagrees with the kye ref
53            1882, 12, 10,   4215, 11,0,  1,
54            1883,  7, 4,    4216,  6,0,  1,
55            1884,  4, 25,   4217,  4,0,  1,
56            1885,  5, 14,   4218,  4,0,  1,
57            1891,  1, 10,   4223, 12,0,  1,
58            1893,  4, 16,   4226,  3,0,  1,
59            1894,  5,  5,   4227,  4,0,  1,
60            1897,  7, 29,   4230,  7,0,  1, // astronomer's GMT+8 disagrees with all other ref (looks like our astronomer's error, see ad hoc fix at ChineseCalendar::getTimezoneOffset)
61            1903, 10, 20,   4236,  9,0,  1,
62            1904,  1, 17,   4236, 12,0,  1,
63            1904, 11,  7,   4237, 10,0,  1,
64            1905,  5,  4,   4238,  4,0,  1,
65            1907,  7, 10,   4240,  6,0,  1,
66            1908,  4, 30,   4241,  4,0,  1,
67            1908,  9, 25,   4241,  9,0,  1,
68            1909,  9, 14,   4242,  8,0,  1,
69            1911, 12, 20,   4244, 11,0,  1,
70            1976, 11, 22,   4309, 10,0,  1,
71        };
72
73        Calendar cal = Calendar.getInstance(new ULocale("ko_KR@calendar=dangi"));
74        StringBuilder buf = new StringBuilder();
75
76        logln("Gregorian -> Korean Lunar (Dangi)");
77
78        Calendar grego = Calendar.getInstance();
79        grego.clear();
80        for (int i = 0; i < DATA.length;) {
81            grego.set(DATA[i++], DATA[i++] - 1, DATA[i++]);
82            Date date = grego.getTime();
83            cal.setTime(date);
84            int y = cal.get(Calendar.EXTENDED_YEAR);
85            int m = cal.get(Calendar.MONTH) + 1; // 0-based -> 1-based
86            int L = cal.get(Calendar.IS_LEAP_MONTH);
87            int d = cal.get(Calendar.DAY_OF_MONTH);
88            int yE = DATA[i++]; // Expected y, m, isLeapMonth, d
89            int mE = DATA[i++]; // 1-based
90            int LE = DATA[i++];
91            int dE = DATA[i++];
92            buf.setLength(0);
93            buf.append(date + " -> ");
94            buf.append(y + "/" + m + (L == 1 ? "(leap)" : "") + "/" + d);
95            if (y == yE && m == mE && L == LE && d == dE) {
96                logln("OK: " + buf.toString());
97            } else {
98                errln("Fail: " + buf.toString() + ", expected " + yE + "/" + mE + (LE == 1 ? "(leap)" : "") + "/" + dE);
99            }
100        }
101
102        logln("Korean Lunar (Dangi) -> Gregorian");
103        for (int i = 0; i < DATA.length;) {
104            grego.set(DATA[i++], DATA[i++] - 1, DATA[i++]);
105            Date dexp = grego.getTime();
106            int cyear = DATA[i++];
107            int cmonth = DATA[i++];
108            int cisleapmonth = DATA[i++];
109            int cdayofmonth = DATA[i++];
110            cal.clear();
111            cal.set(Calendar.EXTENDED_YEAR, cyear);
112            cal.set(Calendar.MONTH, cmonth - 1);
113            cal.set(Calendar.IS_LEAP_MONTH, cisleapmonth);
114            cal.set(Calendar.DAY_OF_MONTH, cdayofmonth);
115            Date date = cal.getTime();
116            buf.setLength(0);
117            buf.append(cyear + "/" + cmonth + (cisleapmonth == 1 ? "(leap)" : "") + "/" + cdayofmonth);
118            buf.append(" -> " + date);
119            if (date.equals(dexp)) {
120                logln("OK: " + buf.toString());
121            } else {
122                errln("Fail: " + buf.toString() + ", expected " + dexp);
123            }
124        }
125    }
126
127    /**
128     * Make sure no Gregorian dates map to Chinese 1-based day of
129     * month zero.  This was a problem with some of the astronomical
130     * new moon determinations.
131     */
132    @Test
133    public void TestZeroDOM() {
134        Calendar cal = Calendar.getInstance(new ULocale("ko_KR@calendar=dangi"));
135        GregorianCalendar greg = new GregorianCalendar(1989, Calendar.SEPTEMBER, 1);
136        logln("Start: " + greg.getTime());
137        for (int i=0; i<1000; ++i) {
138            cal.setTimeInMillis(greg.getTimeInMillis());
139            if (cal.get(Calendar.DAY_OF_MONTH) == 0) {
140                errln("Fail: " + greg.getTime() + " -> " +
141                      cal.get(Calendar.YEAR) + "/" +
142                      cal.get(Calendar.MONTH) +
143                      (cal.get(Calendar.IS_LEAP_MONTH)==1?"(leap)":"") +
144                      "/" + cal.get(Calendar.DAY_OF_MONTH));
145            }
146            greg.add(Calendar.DAY_OF_YEAR, 1);
147        }
148        logln("End: " + greg.getTime());
149    }
150
151    /**
152     * Test minimum and maximum functions.
153     */
154    @Test
155    public void TestLimits() {
156        // The number of days and the start date can be adjusted
157        // arbitrarily to either speed up the test or make it more
158        // thorough, but try to test at least a full year, preferably a
159        // full non-leap and a full leap year.
160
161        // Final parameter is either number of days, if > 0, or test
162        // duration in seconds, if < 0.
163        Calendar tempcal = Calendar.getInstance();
164        tempcal.clear();
165        tempcal.set(1989, Calendar.NOVEMBER, 1);
166        Calendar dangi = Calendar.getInstance(new ULocale("ko_KR@calendar=dangi"));
167        doLimitsTest(dangi, null, tempcal.getTime());
168        doTheoreticalLimitsTest(dangi, true);
169    }
170
171    /**
172     * Make sure IS_LEAP_MONTH participates in field resolution.
173     */
174    @Test
175    public void TestResolution() {
176        Calendar cal = Calendar.getInstance(new ULocale("ko_KR@calendar=dangi"));
177        DateFormat fmt = DateFormat.getDateInstance(cal, DateFormat.DEFAULT);
178
179        // May 22 4334 = y4334 m4 d30 doy119
180        // May 23 4334 = y4334 m4* d1 doy120
181
182        final int THE_YEAR = 4334;
183        final int END = -1;
184
185        int[] DATA = {
186            // Format:
187            // (field, value)+, END, exp.month, exp.isLeapMonth, exp.DOM
188            // Note: exp.month is ONE-BASED
189
190            // If we set DAY_OF_YEAR only, that should be used
191            Calendar.DAY_OF_YEAR, 1,
192            END,
193            1,0,1, // Expect 1-1
194
195            // If we set MONTH only, that should be used
196            Calendar.IS_LEAP_MONTH, 1,
197            Calendar.DAY_OF_MONTH, 1,
198            Calendar.MONTH, 3,
199            END,
200            4,1,1, // Expect 4*-1
201
202            // If we set the DOY last, that should take precedence
203            Calendar.MONTH, 1, // Should ignore
204            Calendar.IS_LEAP_MONTH, 1, // Should ignore
205            Calendar.DAY_OF_MONTH, 1, // Should ignore
206            Calendar.DAY_OF_YEAR, 121,
207            END,
208            4,1,2, // Expect 4*-2
209
210            // If we set IS_LEAP_MONTH last, that should take precedence
211            Calendar.MONTH, 3,
212            Calendar.DAY_OF_MONTH, 1,
213            Calendar.DAY_OF_YEAR, 5, // Should ignore
214            Calendar.IS_LEAP_MONTH, 1,
215            END,
216            4,1,1, // Expect 4*-1
217        };
218
219        StringBuilder buf = new StringBuilder();
220        for (int i=0; i<DATA.length; ) {
221            cal.clear();
222            cal.set(Calendar.EXTENDED_YEAR, THE_YEAR);
223            buf.setLength(0);
224            buf.append("EXTENDED_YEAR=" + THE_YEAR);
225            while (DATA[i] != END) {
226                cal.set(DATA[i++], DATA[i++]);
227                buf.append(" " + fieldName(DATA[i-2]) + "=" + DATA[i-1]);
228            }
229            ++i; // Skip over END mark
230            int expMonth = DATA[i++]-1;
231            int expIsLeapMonth = DATA[i++];
232            int expDOM = DATA[i++];
233            int month = cal.get(Calendar.MONTH);
234            int isLeapMonth = cal.get(Calendar.IS_LEAP_MONTH);
235            int dom = cal.get(Calendar.DAY_OF_MONTH);
236            if (expMonth == month && expIsLeapMonth == isLeapMonth &&
237                dom == expDOM) {
238                logln("OK: " + buf + " => " + fmt.format(cal.getTime()));
239            } else {
240                String s = fmt.format(cal.getTime());
241                cal.clear();
242                cal.set(Calendar.EXTENDED_YEAR, THE_YEAR);
243                cal.set(Calendar.MONTH, expMonth);
244                cal.set(Calendar.IS_LEAP_MONTH, expIsLeapMonth);
245                cal.set(Calendar.DAY_OF_MONTH, expDOM);
246                errln("Fail: " + buf + " => " + s +
247                      "=" + (month+1) + "," + isLeapMonth + "," + dom +
248                      ", expected " + fmt.format(cal.getTime()) +
249                      "=" + (expMonth+1) + "," + expIsLeapMonth + "," + expDOM);
250            }
251        }
252    }
253
254    /**
255     * Test the behavior of fields that are out of range.
256     */
257    @Test
258    public void TestOutOfRange() {
259        int[] DATA = new int[] {
260            // Input       Output
261            4334, 13,  1,   4335,  1,  1,
262            4334, 18,  1,   4335,  6,  1,
263            4335,  0,  1,   4334, 12,  1,
264            4335, -6,  1,   4334,  6,  1,
265            4334,  1, 32,   4334,  2,  2, // 1-4334 has 30 days
266            4334,  2, -1,   4334,  1, 29,
267        };
268        Calendar cal = Calendar.getInstance(new ULocale("ko_KR@calendar=dangi"));
269        for (int i = 0; i < DATA.length;) {
270            int y1 = DATA[i++];
271            int m1 = DATA[i++] - 1;
272            int d1 = DATA[i++];
273            int y2 = DATA[i++];
274            int m2 = DATA[i++] - 1;
275            int d2 = DATA[i++];
276            cal.clear();
277            cal.set(Calendar.EXTENDED_YEAR, y1);
278            cal.set(MONTH, m1);
279            cal.set(DATE, d1);
280            int y = cal.get(Calendar.EXTENDED_YEAR);
281            int m = cal.get(MONTH);
282            int d = cal.get(DATE);
283            if (y != y2 || m != m2 || d != d2) {
284                errln("Fail: " + y1 + "/" + (m1 + 1) + "/" + d1 + " resolves to " + y + "/" + (m + 1) + "/" + d
285                        + ", expected " + y2 + "/" + (m2 + 1) + "/" + d2);
286            } else if (isVerbose()) {
287                logln("OK: " + y1 + "/" + (m1 + 1) + "/" + d1 + " resolves to " + y + "/" + (m + 1) + "/" + d);
288            }
289        }
290    }
291
292    /**
293     * Test the behavior of KoreanLunarCalendar.add().  The only real
294     * nastiness with roll is the MONTH field around leap months.
295     */
296    @Test
297    public void TestAdd() {
298        int[][] tests = new int[][] {
299            // MONTHS ARE 1-BASED HERE
300            // input               add           output
301            // year  mon    day    field amount  year  mon    day
302            {  4338,   3,0,  15,   MONTH,   3,   4338,   6,0,  15 }, // normal
303            {  4335,  12,0,  15,   MONTH,   1,   4336,   1,0,  15 }, // across year
304            {  4336,   1,0,  15,   MONTH,  -1,   4335,  12,0,  15 }, // across year
305            {  4334,   3,0,  15,   MONTH,   3,   4334,   5,0,  15 }, // 4=leap
306            {  4334,   3,0,  15,   MONTH,   2,   4334,   4,1,  15 }, // 4=leap
307            {  4334,   4,0,  15,   MONTH,   1,   4334,   4,1,  15 }, // 4=leap
308            {  4334,   4,1,  15,   MONTH,   1,   4334,   5,0,  15 }, // 4=leap
309            {  4334,   3,0,  30,   MONTH,   2,   4334,   4,1,  29 }, // dom should pin
310            {  4334,   3,0,  30,   MONTH,   3,   4334,   5,0,  30 }, // no dom pin
311            {  4334,   3,0,  30,   MONTH,   4,   4334,   6,0,  29 }, // dom should pin
312        };
313
314        Calendar cal = Calendar.getInstance(new ULocale("ko_KR@calendar=dangi"));
315        doRollAddDangi(ADD, cal, tests);
316    }
317
318    /**
319     * Test the behavior of KoreanLunarCalendar.roll().  The only real
320     * nastiness with roll is the MONTH field around leap months.
321     */
322    @Test
323    public void TestRoll() {
324        int[][] tests = new int[][] {
325            // MONTHS ARE 1-BASED HERE
326            // input               add           output
327            // year  mon    day    field amount  year  mon    day
328            {  4338,   3,0,  15,   MONTH,   3,   4338,   6,0,  15 }, // normal
329            {  4338,   3,0,  15,   MONTH,  11,   4338,   2,0,  15 }, // normal
330            {  4335,  12,0,  15,   MONTH,   1,   4335,   1,0,  15 }, // across year
331            {  4336,   1,0,  15,   MONTH,  -1,   4336,  12,0,  15 }, // across year
332            {  4334,   3,0,  15,   MONTH,   3,   4334,   5,0,  15 }, // 4=leap
333            {  4334,   3,0,  15,   MONTH,  16,   4334,   5,0,  15 }, // 4=leap
334            {  4334,   3,0,  15,   MONTH,   2,   4334,   4,1,  15 }, // 4=leap
335            {  4334,   3,0,  15,   MONTH,  28,   4334,   4,1,  15 }, // 4=leap
336            {  4334,   4,0,  15,   MONTH,   1,   4334,   4,1,  15 }, // 4=leap
337            {  4334,   4,0,  15,   MONTH, -12,   4334,   4,1,  15 }, // 4=leap
338            {  4334,   4,1,  15,   MONTH,   1,   4334,   5,0,  15 }, // 4=leap
339            {  4334,   4,1,  15,   MONTH, -25,   4334,   5,0,  15 }, // 4=leap
340            {  4334,   3,0,  30,   MONTH,   2,   4334,   4,1,  29 }, // dom should pin
341            {  4334,   3,0,  30,   MONTH,  15,   4334,   4,1,  29 }, // dom should pin
342            {  4334,   3,0,  30,   MONTH,  16,   4334,   5,0,  30 }, // no dom pin
343            {  4334,   3,0,  30,   MONTH,  -9,   4334,   6,0,  29 }, // dom should pin
344        };
345
346        Calendar cal = Calendar.getInstance(new ULocale("ko_KR@calendar=dangi"));
347        doRollAddDangi(ROLL, cal, tests);
348    }
349
350    void doRollAddDangi(boolean roll, Calendar cal, int[][] tests) {
351        String name = roll ? "rolling" : "adding";
352
353        for (int i = 0; i < tests.length; i++) {
354            int[] test = tests[i];
355
356            cal.clear();
357            cal.set(Calendar.EXTENDED_YEAR, test[0]);
358            cal.set(Calendar.MONTH, test[1] - 1);
359            cal.set(Calendar.IS_LEAP_MONTH, test[2]);
360            cal.set(Calendar.DAY_OF_MONTH, test[3]);
361            if (roll) {
362                cal.roll(test[4], test[5]);
363            } else {
364                cal.add(test[4], test[5]);
365            }
366            if (cal.get(Calendar.EXTENDED_YEAR) != test[6] || cal.get(MONTH) != (test[7] - 1)
367                    || cal.get(Calendar.IS_LEAP_MONTH) != test[8] || cal.get(DATE) != test[9]) {
368                errln("Fail: " + name + " " + ymdToString(test[0], test[1] - 1, test[2], test[3]) + " "
369                        + fieldName(test[4]) + " by " + test[5] + ": expected "
370                        + ymdToString(test[6], test[7] - 1, test[8], test[9]) + ", got " + ymdToString(cal));
371            } else if (isVerbose()) {
372                logln("OK: " + name + " " + ymdToString(test[0], test[1] - 1, test[2], test[3]) + " "
373                        + fieldName(test[4]) + " by " + test[5] + ": got " + ymdToString(cal));
374            }
375        }
376    }
377
378    /**
379     * Convert year,month,day values to the form "year/month/day".
380     * On input the month value is zero-based, but in the result string it is one-based.
381     */
382    static public String ymdToString(int year, int month, int isLeapMonth, int day) {
383        return "" + year + "/" + (month + 1) + ((isLeapMonth != 0) ? "(leap)" : "") + "/" + day;
384    }
385
386    @Test
387    public void TestCoverage() {
388        // DangiCalendar()
389        // DangiCalendar(Date)
390        // DangiCalendar(TimeZone, ULocale)
391        Date d = new Date();
392
393        DangiCalendar cal1 = new DangiCalendar();
394        cal1.setTime(d);
395
396        DangiCalendar cal2 = new DangiCalendar(d);
397
398        DangiCalendar cal3 = new DangiCalendar(TimeZone.getDefault(), ULocale.getDefault());
399        cal3.setTime(d);
400
401        assertEquals("DangiCalendar() and DangiCalendar(Date)", cal1, cal2);
402        assertEquals("DangiCalendar() and DangiCalendar(TimeZone,ULocale)", cal1, cal3);
403
404        // String getType()
405        String type = cal1.getType();
406        assertEquals("getType()", "dangi", type);
407    }
408
409    @Test
410    public void TestInitWithCurrentTime() {
411        // If the chinese calendar current millis isn't called, the default year is wrong.
412        // this test is assuming the 'year' is the current cycle
413        // so when we cross a cycle boundary, the target will need to change
414        // that shouldn't be for awhile yet...
415
416        Calendar cc = Calendar.getInstance(new ULocale("ko_KR@calendar=dangi"));
417        cc.set(Calendar.EXTENDED_YEAR, 4338);
418        cc.set(Calendar.MONTH, 0);
419         // need to set leap month flag off, otherwise, the test case always fails when
420         // current time is in a leap month
421        cc.set(Calendar.IS_LEAP_MONTH, 0);
422        cc.set(Calendar.DATE, 19);
423        cc.set(Calendar.HOUR_OF_DAY, 0);
424        cc.set(Calendar.MINUTE, 0);
425        cc.set(Calendar.SECOND, 0);
426        cc.set(Calendar.MILLISECOND, 0);
427
428        cc.add(Calendar.DATE, 1);
429
430        Calendar cal = new GregorianCalendar(2005, Calendar.FEBRUARY, 28);
431        Date target = cal.getTime();
432        Date result = cc.getTime();
433
434        assertEquals("chinese and gregorian date should match", target, result);
435    }
436}
437