2 *******************************************************************************
3 * Copyright (C) 2012, International Business Machines Corporation and *
4 * others. All Rights Reserved. *
5 *******************************************************************************
7 package com.ibm.icu.dev.test.calendar;
10 import com.ibm.icu.text.DateFormat;
11 import com.ibm.icu.util.Calendar;
12 import com.ibm.icu.util.DangiCalendar;
13 import com.ibm.icu.util.GregorianCalendar;
14 import com.ibm.icu.util.TimeZone;
15 import com.ibm.icu.util.ULocale;
17 public class DangiTest extends CalendarTest {
19 public static void main(String args[]) throws Exception {
20 new DangiTest().run(args);
24 * Test basic mapping to and from Gregorian.
26 public void TestMapping() {
28 // (Note: months are 1-based)
29 // Gregorian Korean (Dan-gi)
30 1964, 9, 4, 4297, 7,0, 28,
31 1964, 9, 5, 4297, 7,0, 29,
32 1964, 9, 6, 4297, 8,0, 1,
33 1964, 9, 7, 4297, 8,0, 2,
34 1961, 12, 25, 4294, 11,0, 18,
35 1999, 6, 4, 4332, 4,0, 21,
37 1990, 5, 23, 4323, 4,0, 29,
38 1990, 5, 24, 4323, 5,0, 1,
39 1990, 6, 22, 4323, 5,0, 30,
40 1990, 6, 23, 4323, 5,1, 1,
41 1990, 7, 20, 4323, 5,1, 28,
42 1990, 7, 21, 4323, 5,1, 29,
43 1990, 7, 22, 4323, 6,0, 1,
45 // Some tricky dates (where GMT+8 doesn't agree with GMT+9)
47 // The list is from http://www.math.snu.ac.kr/~kye/others/lunar.html ('kye ref').
48 // However, for some dates disagree with the above reference so KASI's
49 // calculation was cross-referenced:
50 // http://astro.kasi.re.kr/Life/ConvertSolarLunarForm.aspx?MenuID=115
51 1880, 11, 3, 4213, 10,0, 1, // astronomer's GMT+8 / KASI disagrees with the kye ref
52 1882, 12, 10, 4215, 11,0, 1,
53 1883, 7, 4, 4216, 6,0, 1,
54 1884, 4, 25, 4217, 4,0, 1,
55 1885, 5, 14, 4218, 4,0, 1,
56 1891, 1, 10, 4223, 12,0, 1,
57 1893, 4, 16, 4226, 3,0, 1,
58 1894, 5, 5, 4227, 4,0, 1,
59 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)
60 1903, 10, 20, 4236, 9,0, 1,
61 1904, 1, 17, 4236, 12,0, 1,
62 1904, 11, 7, 4237, 10,0, 1,
63 1905, 5, 4, 4238, 4,0, 1,
64 1907, 7, 10, 4240, 6,0, 1,
65 1908, 4, 30, 4241, 4,0, 1,
66 1908, 9, 25, 4241, 9,0, 1,
67 1909, 9, 14, 4242, 8,0, 1,
68 1911, 12, 20, 4244, 11,0, 1,
69 1976, 11, 22, 4309, 10,0, 1,
72 Calendar cal = Calendar.getInstance(new ULocale("ko_KR@calendar=dangi"));
73 StringBuilder buf = new StringBuilder();
75 logln("Gregorian -> Korean Lunar (Dangi)");
77 Calendar grego = Calendar.getInstance();
79 for (int i = 0; i < DATA.length;) {
80 grego.set(DATA[i++], DATA[i++] - 1, DATA[i++]);
81 Date date = grego.getTime();
83 int y = cal.get(Calendar.EXTENDED_YEAR);
84 int m = cal.get(Calendar.MONTH) + 1; // 0-based -> 1-based
85 int L = cal.get(Calendar.IS_LEAP_MONTH);
86 int d = cal.get(Calendar.DAY_OF_MONTH);
87 int yE = DATA[i++]; // Expected y, m, isLeapMonth, d
88 int mE = DATA[i++]; // 1-based
92 buf.append(date + " -> ");
93 buf.append(y + "/" + m + (L == 1 ? "(leap)" : "") + "/" + d);
94 if (y == yE && m == mE && L == LE && d == dE) {
95 logln("OK: " + buf.toString());
97 errln("Fail: " + buf.toString() + ", expected " + yE + "/" + mE + (LE == 1 ? "(leap)" : "") + "/" + dE);
101 logln("Korean Lunar (Dangi) -> Gregorian");
102 for (int i = 0; i < DATA.length;) {
103 grego.set(DATA[i++], DATA[i++] - 1, DATA[i++]);
104 Date dexp = grego.getTime();
105 int cyear = DATA[i++];
106 int cmonth = DATA[i++];
107 int cisleapmonth = DATA[i++];
108 int cdayofmonth = DATA[i++];
110 cal.set(Calendar.EXTENDED_YEAR, cyear);
111 cal.set(Calendar.MONTH, cmonth - 1);
112 cal.set(Calendar.IS_LEAP_MONTH, cisleapmonth);
113 cal.set(Calendar.DAY_OF_MONTH, cdayofmonth);
114 Date date = cal.getTime();
116 buf.append(cyear + "/" + cmonth + (cisleapmonth == 1 ? "(leap)" : "") + "/" + cdayofmonth);
117 buf.append(" -> " + date);
118 if (date.equals(dexp)) {
119 logln("OK: " + buf.toString());
121 errln("Fail: " + buf.toString() + ", expected " + dexp);
127 * Make sure no Gregorian dates map to Chinese 1-based day of
128 * month zero. This was a problem with some of the astronomical
129 * new moon determinations.
131 public void TestZeroDOM() {
132 Calendar cal = Calendar.getInstance(new ULocale("ko_KR@calendar=dangi"));
133 GregorianCalendar greg = new GregorianCalendar(1989, Calendar.SEPTEMBER, 1);
134 logln("Start: " + greg.getTime());
135 for (int i=0; i<1000; ++i) {
136 cal.setTimeInMillis(greg.getTimeInMillis());
137 if (cal.get(Calendar.DAY_OF_MONTH) == 0) {
138 errln("Fail: " + greg.getTime() + " -> " +
139 cal.get(Calendar.YEAR) + "/" +
140 cal.get(Calendar.MONTH) +
141 (cal.get(Calendar.IS_LEAP_MONTH)==1?"(leap)":"") +
142 "/" + cal.get(Calendar.DAY_OF_MONTH));
144 greg.add(Calendar.DAY_OF_YEAR, 1);
146 logln("End: " + greg.getTime());
150 * Test minimum and maximum functions.
152 public void TestLimits() {
153 // The number of days and the start date can be adjusted
154 // arbitrarily to either speed up the test or make it more
155 // thorough, but try to test at least a full year, preferably a
156 // full non-leap and a full leap year.
158 // Final parameter is either number of days, if > 0, or test
159 // duration in seconds, if < 0.
160 Calendar tempcal = Calendar.getInstance();
162 tempcal.set(1989, Calendar.NOVEMBER, 1);
163 Calendar dangi = Calendar.getInstance(new ULocale("ko_KR@calendar=dangi"));
164 doLimitsTest(dangi, null, tempcal.getTime());
165 doTheoreticalLimitsTest(dangi, true);
169 * Make sure IS_LEAP_MONTH participates in field resolution.
171 public void TestResolution() {
172 Calendar cal = Calendar.getInstance(new ULocale("ko_KR@calendar=dangi"));
173 DateFormat fmt = DateFormat.getDateInstance(cal, DateFormat.DEFAULT);
175 // May 22 4334 = y4334 m4 d30 doy119
176 // May 23 4334 = y4334 m4* d1 doy120
178 final int THE_YEAR = 4334;
183 // (field, value)+, END, exp.month, exp.isLeapMonth, exp.DOM
184 // Note: exp.month is ONE-BASED
186 // If we set DAY_OF_YEAR only, that should be used
187 Calendar.DAY_OF_YEAR, 1,
191 // If we set MONTH only, that should be used
192 Calendar.IS_LEAP_MONTH, 1,
193 Calendar.DAY_OF_MONTH, 1,
196 4,1,1, // Expect 4*-1
198 // If we set the DOY last, that should take precedence
199 Calendar.MONTH, 1, // Should ignore
200 Calendar.IS_LEAP_MONTH, 1, // Should ignore
201 Calendar.DAY_OF_MONTH, 1, // Should ignore
202 Calendar.DAY_OF_YEAR, 121,
204 4,1,2, // Expect 4*-2
206 // If we set IS_LEAP_MONTH last, that should take precedence
208 Calendar.DAY_OF_MONTH, 1,
209 Calendar.DAY_OF_YEAR, 5, // Should ignore
210 Calendar.IS_LEAP_MONTH, 1,
212 4,1,1, // Expect 4*-1
215 StringBuilder buf = new StringBuilder();
216 for (int i=0; i<DATA.length; ) {
218 cal.set(Calendar.EXTENDED_YEAR, THE_YEAR);
220 buf.append("EXTENDED_YEAR=" + THE_YEAR);
221 while (DATA[i] != END) {
222 cal.set(DATA[i++], DATA[i++]);
223 buf.append(" " + fieldName(DATA[i-2]) + "=" + DATA[i-1]);
225 ++i; // Skip over END mark
226 int expMonth = DATA[i++]-1;
227 int expIsLeapMonth = DATA[i++];
228 int expDOM = DATA[i++];
229 int month = cal.get(Calendar.MONTH);
230 int isLeapMonth = cal.get(Calendar.IS_LEAP_MONTH);
231 int dom = cal.get(Calendar.DAY_OF_MONTH);
232 if (expMonth == month && expIsLeapMonth == isLeapMonth &&
234 logln("OK: " + buf + " => " + fmt.format(cal.getTime()));
236 String s = fmt.format(cal.getTime());
238 cal.set(Calendar.EXTENDED_YEAR, THE_YEAR);
239 cal.set(Calendar.MONTH, expMonth);
240 cal.set(Calendar.IS_LEAP_MONTH, expIsLeapMonth);
241 cal.set(Calendar.DAY_OF_MONTH, expDOM);
242 errln("Fail: " + buf + " => " + s +
243 "=" + (month+1) + "," + isLeapMonth + "," + dom +
244 ", expected " + fmt.format(cal.getTime()) +
245 "=" + (expMonth+1) + "," + expIsLeapMonth + "," + expDOM);
251 * Test the behavior of fields that are out of range.
253 public void TestOutOfRange() {
254 int[] DATA = new int[] {
256 4334, 13, 1, 4335, 1, 1,
257 4334, 18, 1, 4335, 6, 1,
258 4335, 0, 1, 4334, 12, 1,
259 4335, -6, 1, 4334, 6, 1,
260 4334, 1, 32, 4334, 2, 2, // 1-4334 has 30 days
261 4334, 2, -1, 4334, 1, 29,
263 Calendar cal = Calendar.getInstance(new ULocale("ko_KR@calendar=dangi"));
264 for (int i = 0; i < DATA.length;) {
266 int m1 = DATA[i++] - 1;
269 int m2 = DATA[i++] - 1;
272 cal.set(Calendar.EXTENDED_YEAR, y1);
275 int y = cal.get(Calendar.EXTENDED_YEAR);
276 int m = cal.get(MONTH);
277 int d = cal.get(DATE);
278 if (y != y2 || m != m2 || d != d2) {
279 errln("Fail: " + y1 + "/" + (m1 + 1) + "/" + d1 + " resolves to " + y + "/" + (m + 1) + "/" + d
280 + ", expected " + y2 + "/" + (m2 + 1) + "/" + d2);
281 } else if (isVerbose()) {
282 logln("OK: " + y1 + "/" + (m1 + 1) + "/" + d1 + " resolves to " + y + "/" + (m + 1) + "/" + d);
288 * Test the behavior of KoreanLunarCalendar.add(). The only real
289 * nastiness with roll is the MONTH field around leap months.
291 public void TestAdd() {
292 int[][] tests = new int[][] {
293 // MONTHS ARE 1-BASED HERE
295 // year mon day field amount year mon day
296 { 4338, 3,0, 15, MONTH, 3, 4338, 6,0, 15 }, // normal
297 { 4335, 12,0, 15, MONTH, 1, 4336, 1,0, 15 }, // across year
298 { 4336, 1,0, 15, MONTH, -1, 4335, 12,0, 15 }, // across year
299 { 4334, 3,0, 15, MONTH, 3, 4334, 5,0, 15 }, // 4=leap
300 { 4334, 3,0, 15, MONTH, 2, 4334, 4,1, 15 }, // 4=leap
301 { 4334, 4,0, 15, MONTH, 1, 4334, 4,1, 15 }, // 4=leap
302 { 4334, 4,1, 15, MONTH, 1, 4334, 5,0, 15 }, // 4=leap
303 { 4334, 3,0, 30, MONTH, 2, 4334, 4,1, 29 }, // dom should pin
304 { 4334, 3,0, 30, MONTH, 3, 4334, 5,0, 30 }, // no dom pin
305 { 4334, 3,0, 30, MONTH, 4, 4334, 6,0, 29 }, // dom should pin
308 Calendar cal = Calendar.getInstance(new ULocale("ko_KR@calendar=dangi"));
309 doRollAddDangi(ADD, cal, tests);
313 * Test the behavior of KoreanLunarCalendar.roll(). The only real
314 * nastiness with roll is the MONTH field around leap months.
316 public void TestRoll() {
317 int[][] tests = new int[][] {
318 // MONTHS ARE 1-BASED HERE
320 // year mon day field amount year mon day
321 { 4338, 3,0, 15, MONTH, 3, 4338, 6,0, 15 }, // normal
322 { 4338, 3,0, 15, MONTH, 11, 4338, 2,0, 15 }, // normal
323 { 4335, 12,0, 15, MONTH, 1, 4335, 1,0, 15 }, // across year
324 { 4336, 1,0, 15, MONTH, -1, 4336, 12,0, 15 }, // across year
325 { 4334, 3,0, 15, MONTH, 3, 4334, 5,0, 15 }, // 4=leap
326 { 4334, 3,0, 15, MONTH, 16, 4334, 5,0, 15 }, // 4=leap
327 { 4334, 3,0, 15, MONTH, 2, 4334, 4,1, 15 }, // 4=leap
328 { 4334, 3,0, 15, MONTH, 28, 4334, 4,1, 15 }, // 4=leap
329 { 4334, 4,0, 15, MONTH, 1, 4334, 4,1, 15 }, // 4=leap
330 { 4334, 4,0, 15, MONTH, -12, 4334, 4,1, 15 }, // 4=leap
331 { 4334, 4,1, 15, MONTH, 1, 4334, 5,0, 15 }, // 4=leap
332 { 4334, 4,1, 15, MONTH, -25, 4334, 5,0, 15 }, // 4=leap
333 { 4334, 3,0, 30, MONTH, 2, 4334, 4,1, 29 }, // dom should pin
334 { 4334, 3,0, 30, MONTH, 15, 4334, 4,1, 29 }, // dom should pin
335 { 4334, 3,0, 30, MONTH, 16, 4334, 5,0, 30 }, // no dom pin
336 { 4334, 3,0, 30, MONTH, -9, 4334, 6,0, 29 }, // dom should pin
339 Calendar cal = Calendar.getInstance(new ULocale("ko_KR@calendar=dangi"));
340 doRollAddDangi(ROLL, cal, tests);
343 void doRollAddDangi(boolean roll, Calendar cal, int[][] tests) {
344 String name = roll ? "rolling" : "adding";
346 for (int i = 0; i < tests.length; i++) {
347 int[] test = tests[i];
350 cal.set(Calendar.EXTENDED_YEAR, test[0]);
351 cal.set(Calendar.MONTH, test[1] - 1);
352 cal.set(Calendar.IS_LEAP_MONTH, test[2]);
353 cal.set(Calendar.DAY_OF_MONTH, test[3]);
355 cal.roll(test[4], test[5]);
357 cal.add(test[4], test[5]);
359 if (cal.get(Calendar.EXTENDED_YEAR) != test[6] || cal.get(MONTH) != (test[7] - 1)
360 || cal.get(Calendar.IS_LEAP_MONTH) != test[8] || cal.get(DATE) != test[9]) {
361 errln("Fail: " + name + " " + ymdToString(test[0], test[1] - 1, test[2], test[3]) + " "
362 + fieldName(test[4]) + " by " + test[5] + ": expected "
363 + ymdToString(test[6], test[7] - 1, test[8], test[9]) + ", got " + ymdToString(cal));
364 } else if (isVerbose()) {
365 logln("OK: " + name + " " + ymdToString(test[0], test[1] - 1, test[2], test[3]) + " "
366 + fieldName(test[4]) + " by " + test[5] + ": got " + ymdToString(cal));
372 * Convert year,month,day values to the form "year/month/day".
373 * On input the month value is zero-based, but in the result string it is one-based.
375 static public String ymdToString(int year, int month, int isLeapMonth, int day) {
376 return "" + year + "/" + (month + 1) + ((isLeapMonth != 0) ? "(leap)" : "") + "/" + day;
379 public void TestCoverage() {
381 // DangiCalendar(Date)
382 // DangiCalendar(TimeZone, ULocale)
385 DangiCalendar cal1 = new DangiCalendar();
388 DangiCalendar cal2 = new DangiCalendar(d);
390 DangiCalendar cal3 = new DangiCalendar(TimeZone.getDefault(), ULocale.getDefault());
393 assertEquals("DangiCalendar() and DangiCalendar(Date)", cal1, cal2);
394 assertEquals("DangiCalendar() and DangiCalendar(TimeZone,ULocale)", cal1, cal3);
397 String type = cal1.getType();
398 assertEquals("getType()", "dangi", type);
401 public void TestInitWithCurrentTime() {
402 // If the chinese calendar current millis isn't called, the default year is wrong.
403 // this test is assuming the 'year' is the current cycle
404 // so when we cross a cycle boundary, the target will need to change
405 // that shouldn't be for awhile yet...
407 Calendar cc = Calendar.getInstance(new ULocale("ko_KR@calendar=dangi"));
408 cc.set(Calendar.EXTENDED_YEAR, 4338);
409 cc.set(Calendar.MONTH, 0);
410 // need to set leap month flag off, otherwise, the test case always fails when
411 // current time is in a leap month
412 cc.set(Calendar.IS_LEAP_MONTH, 0);
413 cc.set(Calendar.DATE, 19);
414 cc.set(Calendar.HOUR_OF_DAY, 0);
415 cc.set(Calendar.MINUTE, 0);
416 cc.set(Calendar.SECOND, 0);
417 cc.set(Calendar.MILLISECOND, 0);
419 cc.add(Calendar.DATE, 1);
421 Calendar cal = new GregorianCalendar(2005, Calendar.FEBRUARY, 28);
422 Date target = cal.getTime();
423 Date result = cc.getTime();
425 assertEquals("chinese and gregorian date should match", target, result);