1/* 2 ******************************************************************************* 3 * Copyright (C) 2012, International Business Machines Corporation and * 4 * others. All Rights Reserved. * 5 ******************************************************************************* 6 */ 7package com.ibm.icu.dev.test.calendar; 8import java.util.Date; 9 10import com.ibm.icu.text.DateFormat; 11import com.ibm.icu.util.Calendar; 12import com.ibm.icu.util.DangiCalendar; 13import com.ibm.icu.util.GregorianCalendar; 14import com.ibm.icu.util.TimeZone; 15import com.ibm.icu.util.ULocale; 16 17public class DangiTest extends CalendarTest { 18 19 public static void main(String args[]) throws Exception { 20 new DangiTest().run(args); 21 } 22 23 /** 24 * Test basic mapping to and from Gregorian. 25 */ 26 public void TestMapping() { 27 final int[] DATA = { 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, 36 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, 44 45 // Some tricky dates (where GMT+8 doesn't agree with GMT+9) 46 // 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, 70 }; 71 72 Calendar cal = Calendar.getInstance(new ULocale("ko_KR@calendar=dangi")); 73 StringBuilder buf = new StringBuilder(); 74 75 logln("Gregorian -> Korean Lunar (Dangi)"); 76 77 Calendar grego = Calendar.getInstance(); 78 grego.clear(); 79 for (int i = 0; i < DATA.length;) { 80 grego.set(DATA[i++], DATA[i++] - 1, DATA[i++]); 81 Date date = grego.getTime(); 82 cal.setTime(date); 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 89 int LE = DATA[i++]; 90 int dE = DATA[i++]; 91 buf.setLength(0); 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()); 96 } else { 97 errln("Fail: " + buf.toString() + ", expected " + yE + "/" + mE + (LE == 1 ? "(leap)" : "") + "/" + dE); 98 } 99 } 100 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++]; 109 cal.clear(); 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(); 115 buf.setLength(0); 116 buf.append(cyear + "/" + cmonth + (cisleapmonth == 1 ? "(leap)" : "") + "/" + cdayofmonth); 117 buf.append(" -> " + date); 118 if (date.equals(dexp)) { 119 logln("OK: " + buf.toString()); 120 } else { 121 errln("Fail: " + buf.toString() + ", expected " + dexp); 122 } 123 } 124 } 125 126 /** 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. 130 */ 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)); 143 } 144 greg.add(Calendar.DAY_OF_YEAR, 1); 145 } 146 logln("End: " + greg.getTime()); 147 } 148 149 /** 150 * Test minimum and maximum functions. 151 */ 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. 157 158 // Final parameter is either number of days, if > 0, or test 159 // duration in seconds, if < 0. 160 Calendar tempcal = Calendar.getInstance(); 161 tempcal.clear(); 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); 166 } 167 168 /** 169 * Make sure IS_LEAP_MONTH participates in field resolution. 170 */ 171 public void TestResolution() { 172 Calendar cal = Calendar.getInstance(new ULocale("ko_KR@calendar=dangi")); 173 DateFormat fmt = DateFormat.getDateInstance(cal, DateFormat.DEFAULT); 174 175 // May 22 4334 = y4334 m4 d30 doy119 176 // May 23 4334 = y4334 m4* d1 doy120 177 178 final int THE_YEAR = 4334; 179 final int END = -1; 180 181 int[] DATA = { 182 // Format: 183 // (field, value)+, END, exp.month, exp.isLeapMonth, exp.DOM 184 // Note: exp.month is ONE-BASED 185 186 // If we set DAY_OF_YEAR only, that should be used 187 Calendar.DAY_OF_YEAR, 1, 188 END, 189 1,0,1, // Expect 1-1 190 191 // If we set MONTH only, that should be used 192 Calendar.IS_LEAP_MONTH, 1, 193 Calendar.DAY_OF_MONTH, 1, 194 Calendar.MONTH, 3, 195 END, 196 4,1,1, // Expect 4*-1 197 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, 203 END, 204 4,1,2, // Expect 4*-2 205 206 // If we set IS_LEAP_MONTH last, that should take precedence 207 Calendar.MONTH, 3, 208 Calendar.DAY_OF_MONTH, 1, 209 Calendar.DAY_OF_YEAR, 5, // Should ignore 210 Calendar.IS_LEAP_MONTH, 1, 211 END, 212 4,1,1, // Expect 4*-1 213 }; 214 215 StringBuilder buf = new StringBuilder(); 216 for (int i=0; i<DATA.length; ) { 217 cal.clear(); 218 cal.set(Calendar.EXTENDED_YEAR, THE_YEAR); 219 buf.setLength(0); 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]); 224 } 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 && 233 dom == expDOM) { 234 logln("OK: " + buf + " => " + fmt.format(cal.getTime())); 235 } else { 236 String s = fmt.format(cal.getTime()); 237 cal.clear(); 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); 246 } 247 } 248 } 249 250 /** 251 * Test the behavior of fields that are out of range. 252 */ 253 public void TestOutOfRange() { 254 int[] DATA = new int[] { 255 // Input Output 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, 262 }; 263 Calendar cal = Calendar.getInstance(new ULocale("ko_KR@calendar=dangi")); 264 for (int i = 0; i < DATA.length;) { 265 int y1 = DATA[i++]; 266 int m1 = DATA[i++] - 1; 267 int d1 = DATA[i++]; 268 int y2 = DATA[i++]; 269 int m2 = DATA[i++] - 1; 270 int d2 = DATA[i++]; 271 cal.clear(); 272 cal.set(Calendar.EXTENDED_YEAR, y1); 273 cal.set(MONTH, m1); 274 cal.set(DATE, d1); 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); 283 } 284 } 285 } 286 287 /** 288 * Test the behavior of KoreanLunarCalendar.add(). The only real 289 * nastiness with roll is the MONTH field around leap months. 290 */ 291 public void TestAdd() { 292 int[][] tests = new int[][] { 293 // MONTHS ARE 1-BASED HERE 294 // input add output 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 306 }; 307 308 Calendar cal = Calendar.getInstance(new ULocale("ko_KR@calendar=dangi")); 309 doRollAddDangi(ADD, cal, tests); 310 } 311 312 /** 313 * Test the behavior of KoreanLunarCalendar.roll(). The only real 314 * nastiness with roll is the MONTH field around leap months. 315 */ 316 public void TestRoll() { 317 int[][] tests = new int[][] { 318 // MONTHS ARE 1-BASED HERE 319 // input add output 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 337 }; 338 339 Calendar cal = Calendar.getInstance(new ULocale("ko_KR@calendar=dangi")); 340 doRollAddDangi(ROLL, cal, tests); 341 } 342 343 void doRollAddDangi(boolean roll, Calendar cal, int[][] tests) { 344 String name = roll ? "rolling" : "adding"; 345 346 for (int i = 0; i < tests.length; i++) { 347 int[] test = tests[i]; 348 349 cal.clear(); 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]); 354 if (roll) { 355 cal.roll(test[4], test[5]); 356 } else { 357 cal.add(test[4], test[5]); 358 } 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)); 367 } 368 } 369 } 370 371 /** 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. 374 */ 375 static public String ymdToString(int year, int month, int isLeapMonth, int day) { 376 return "" + year + "/" + (month + 1) + ((isLeapMonth != 0) ? "(leap)" : "") + "/" + day; 377 } 378 379 public void TestCoverage() { 380 // DangiCalendar() 381 // DangiCalendar(Date) 382 // DangiCalendar(TimeZone, ULocale) 383 Date d = new Date(); 384 385 DangiCalendar cal1 = new DangiCalendar(); 386 cal1.setTime(d); 387 388 DangiCalendar cal2 = new DangiCalendar(d); 389 390 DangiCalendar cal3 = new DangiCalendar(TimeZone.getDefault(), ULocale.getDefault()); 391 cal3.setTime(d); 392 393 assertEquals("DangiCalendar() and DangiCalendar(Date)", cal1, cal2); 394 assertEquals("DangiCalendar() and DangiCalendar(TimeZone,ULocale)", cal1, cal3); 395 396 // String getType() 397 String type = cal1.getType(); 398 assertEquals("getType()", "dangi", type); 399 } 400 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... 406 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); 418 419 cc.add(Calendar.DATE, 1); 420 421 Calendar cal = new GregorianCalendar(2005, Calendar.FEBRUARY, 28); 422 Date target = cal.getTime(); 423 Date result = cc.getTime(); 424 425 assertEquals("chinese and gregorian date should match", target, result); 426 } 427} 428