1 /* 2 ******************************************************************************* 3 * Copyright (C) 1996-2010, International Business Machines Corporation and * 4 * others. All Rights Reserved. * 5 ******************************************************************************* 6 */ 7 8 package com.ibm.icu.dev.test.calendar; 9 10 import java.util.Date; 11 import java.util.HashMap; 12 import java.util.Locale; 13 import java.util.Map; 14 15 import com.ibm.icu.dev.test.TestFmwk; 16 import com.ibm.icu.text.DateFormat; 17 import com.ibm.icu.text.SimpleDateFormat; 18 import com.ibm.icu.util.Calendar; 19 import com.ibm.icu.util.ChineseCalendar; 20 import com.ibm.icu.util.GregorianCalendar; 21 import com.ibm.icu.util.SimpleTimeZone; 22 23 /** 24 * A base class for classes that test individual Calendar subclasses. 25 * Defines various useful utility methods and constants 26 */ 27 public class CalendarTest extends TestFmwk { 28 29 // Constants for use by subclasses, solely to save typing 30 public final static int SUN = Calendar.SUNDAY; 31 public final static int MON = Calendar.MONDAY; 32 public final static int TUE = Calendar.TUESDAY; 33 public final static int WED = Calendar.WEDNESDAY; 34 public final static int THU = Calendar.THURSDAY; 35 public final static int FRI = Calendar.FRIDAY; 36 public final static int SAT = Calendar.SATURDAY; 37 38 public final static int ERA = Calendar.ERA; 39 public final static int YEAR = Calendar.YEAR; 40 public final static int MONTH = Calendar.MONTH; 41 public final static int DATE = Calendar.DATE; 42 public final static int HOUR = Calendar.HOUR; 43 public final static int MINUTE = Calendar.MINUTE; 44 public final static int SECOND = Calendar.SECOND; 45 public final static int DOY = Calendar.DAY_OF_YEAR; 46 public final static int WOY = Calendar.WEEK_OF_YEAR; 47 public final static int WOM = Calendar.WEEK_OF_MONTH; 48 public final static int DOW = Calendar.DAY_OF_WEEK; 49 public final static int DOWM = Calendar.DAY_OF_WEEK_IN_MONTH; 50 51 public final static SimpleTimeZone UTC = new SimpleTimeZone(0, "GMT"); 52 53 private static final String[] FIELD_NAME = { 54 "ERA", "YEAR", "MONTH", "WEEK_OF_YEAR", "WEEK_OF_MONTH", 55 "DAY_OF_MONTH", "DAY_OF_YEAR", "DAY_OF_WEEK", 56 "DAY_OF_WEEK_IN_MONTH", "AM_PM", "HOUR", "HOUR_OF_DAY", 57 "MINUTE", "SECOND", "MILLISECOND", "ZONE_OFFSET", 58 "DST_OFFSET", "YEAR_WOY", "DOW_LOCAL", "EXTENDED_YEAR", 59 "JULIAN_DAY", "MILLISECONDS_IN_DAY", 60 "IS_LEAP_MONTH" // (ChineseCalendar only) 61 }; 62 fieldName(int f)63 public static final String fieldName(int f) { 64 return (f>=0 && f<FIELD_NAME.length) ? 65 FIELD_NAME[f] : ("<Field " + f + ">"); 66 } 67 68 /** 69 * Iterates through a list of calendar <code>TestCase</code> objects and 70 * makes sure that the time-to-fields and fields-to-time calculations work 71 * correnctly for the values in each test case. 72 */ doTestCases(TestCase[] cases, Calendar cal)73 public void doTestCases(TestCase[] cases, Calendar cal) 74 { 75 cal.setTimeZone(UTC); 76 77 // Get a format to use for printing dates in the calendar system we're testing 78 DateFormat format = DateFormat.getDateTimeInstance(cal, DateFormat.SHORT, -1, Locale.getDefault()); 79 80 final String pattern = (cal instanceof ChineseCalendar) ? 81 "E MMl/dd/y G HH:mm:ss.S z" : 82 "E, MM/dd/yyyy G HH:mm:ss.S z"; 83 84 ((SimpleDateFormat)format).applyPattern(pattern); 85 86 // This format is used for printing Gregorian dates. 87 DateFormat gregFormat = new SimpleDateFormat(pattern); 88 gregFormat.setTimeZone(UTC); 89 90 GregorianCalendar pureGreg = new GregorianCalendar(UTC); 91 pureGreg.setGregorianChange(new Date(Long.MIN_VALUE)); 92 DateFormat pureGregFmt = new SimpleDateFormat("E M/d/yyyy G"); 93 pureGregFmt.setCalendar(pureGreg); 94 95 // Now iterate through the test cases and see what happens 96 for (int i = 0; i < cases.length; i++) 97 { 98 logln("\ntest case: " + i); 99 TestCase test = cases[i]; 100 101 // 102 // First we want to make sure that the millis -> fields calculation works 103 // test.applyTime will call setTime() on the calendar object, and 104 // test.fieldsEqual will retrieve all of the field values and make sure 105 // that they're the same as the ones in the testcase 106 // 107 test.applyTime(cal); 108 if (!test.fieldsEqual(cal, this)) { 109 errln("Fail: (millis=>fields) " + 110 gregFormat.format(test.getTime()) + " => " + 111 format.format(cal.getTime()) + 112 ", expected " + test); 113 } 114 115 // 116 // If that was OK, check the fields -> millis calculation 117 // test.applyFields will set all of the calendar's fields to 118 // match those in the test case. 119 // 120 cal.clear(); 121 test.applyFields(cal); 122 if (!test.equals(cal)) { 123 errln("Fail: (fields=>millis) " + test + " => " + 124 pureGregFmt.format(cal.getTime()) + 125 ", expected " + pureGregFmt.format(test.getTime())); 126 } 127 } 128 } 129 130 static public final boolean ROLL = true; 131 static public final boolean ADD = false; 132 133 /** 134 * Process test cases for <code>add</code> and <code>roll</code> methods. 135 * Each test case is an array of integers, as follows: 136 * <ul> 137 * <li>0: input year 138 * <li>1: month (zero-based) 139 * <li>2: day 140 * <li>3: field to roll or add to 141 * <li>4: amount to roll or add 142 * <li>5: result year 143 * <li>6: month (zero-based) 144 * <li>7: day 145 * </ul> 146 * For example: 147 * <pre> 148 * // input add by output 149 * // year month day field amount year month day 150 * { 5759, HESHVAN, 2, MONTH, 1, 5759, KISLEV, 2 }, 151 * </pre> 152 * 153 * @param roll <code>true</code> or <code>ROLL</code> to test the <code>roll</code> method; 154 * <code>false</code> or <code>ADD</code> to test the <code>add</code method 155 */ doRollAdd(boolean roll, Calendar cal, int[][] tests)156 public void doRollAdd(boolean roll, Calendar cal, int[][] tests) 157 { 158 String name = roll ? "rolling" : "adding"; 159 160 for (int i = 0; i < tests.length; i++) { 161 int[] test = tests[i]; 162 163 cal.clear(); 164 if (cal instanceof ChineseCalendar) { 165 cal.set(Calendar.EXTENDED_YEAR, test[0]); 166 cal.set(Calendar.MONTH, test[1]); 167 cal.set(Calendar.DAY_OF_MONTH, test[2]); 168 } else { 169 cal.set(test[0], test[1], test[2]); 170 } 171 double day0 = getJulianDay(cal); 172 if (roll) { 173 cal.roll(test[3], test[4]); 174 } else { 175 cal.add(test[3], test[4]); 176 } 177 int y = cal.get(cal instanceof ChineseCalendar ? 178 Calendar.EXTENDED_YEAR : YEAR); 179 if (y != test[5] || cal.get(MONTH) != test[6] 180 || cal.get(DATE) != test[7]) 181 { 182 errln("Fail: " + name + " "+ ymdToString(test[0], test[1], test[2]) 183 + " (" + day0 + ")" 184 + " " + FIELD_NAME[test[3]] + " by " + test[4] 185 + ": expected " + ymdToString(test[5], test[6], test[7]) 186 + ", got " + ymdToString(cal)); 187 } else if (isVerbose()) { 188 logln("OK: " + name + " "+ ymdToString(test[0], test[1], test[2]) 189 + " (" + day0 + ")" 190 + " " + FIELD_NAME[test[3]] + " by " + test[4] 191 + ": got " + ymdToString(cal)); 192 } 193 } 194 } 195 196 /** 197 * Test the functions getXxxMinimum() and getXxxMaximum() by marching a 198 * test calendar 'cal' through 'numberOfDays' sequential days starting 199 * with 'startDate'. For each date, read a field value along with its 200 * reported actual minimum and actual maximum. These values are 201 * checked against one another as well as against getMinimum(), 202 * getGreatestMinimum(), getLeastMaximum(), and getMaximum(). We 203 * expect to see: 204 * 205 * 1. minimum <= actualMinimum <= greatestMinimum <= 206 * leastMaximum <= actualMaximum <= maximum 207 * 208 * 2. actualMinimum <= value <= actualMaximum 209 * 210 * Note: In addition to outright failures, this test reports some 211 * results as warnings. These are not generally of concern, but they 212 * should be evaluated by a human. To see these, run this test in 213 * verbose mode. 214 * @param cal the calendar to be tested 215 * @param fieldsToTest an array of field values to be tested, e.g., new 216 * int[] { Calendar.MONTH, Calendar.DAY_OF_MONTH }. It only makes 217 * sense to test the day fields; the time fields are not tested by this 218 * method. If null, then test all standard fields. 219 * @param startDate the first date to test 220 * @param testDuration if positive, the number of days to be tested. 221 * If negative, the number of seconds to run the test. 222 */ doLimitsTest(Calendar cal, int[] fieldsToTest, Date startDate, int testDuration)223 public void doLimitsTest(Calendar cal, int[] fieldsToTest, 224 Date startDate, int testDuration) { 225 GregorianCalendar greg = new GregorianCalendar(); 226 greg.setTime(startDate); 227 logln("Start: " + startDate); 228 229 if (fieldsToTest == null) { 230 fieldsToTest = new int[] { 231 Calendar.ERA, Calendar.YEAR, Calendar.MONTH, 232 Calendar.WEEK_OF_YEAR, Calendar.WEEK_OF_MONTH, 233 Calendar.DAY_OF_MONTH, Calendar.DAY_OF_YEAR, 234 Calendar.DAY_OF_WEEK_IN_MONTH, Calendar.YEAR_WOY, 235 Calendar.EXTENDED_YEAR 236 }; 237 } 238 239 // Keep a record of minima and maxima that we actually see. 240 // These are kept in an array of arrays of hashes. 241 Map[][] limits = new Map[fieldsToTest.length][2]; 242 Object nub = new Object(); // Meaningless placeholder 243 244 // This test can run for a long time; show progress. 245 long millis = System.currentTimeMillis(); 246 long mark = millis + 5000; // 5 sec 247 millis -= testDuration * 1000; // stop time if testDuration<0 248 249 for (int i=0; 250 testDuration>0 ? i<testDuration 251 : System.currentTimeMillis()<millis; 252 ++i) { 253 if (System.currentTimeMillis() >= mark) { 254 logln("(" + i + " days)"); 255 mark += 5000; // 5 sec 256 } 257 cal.setTimeInMillis(greg.getTimeInMillis()); 258 for (int j=0; j<fieldsToTest.length; ++j) { 259 int f = fieldsToTest[j]; 260 int v = cal.get(f); 261 int minActual = cal.getActualMinimum(f); 262 int maxActual = cal.getActualMaximum(f); 263 int minLow = cal.getMinimum(f); 264 int minHigh = cal.getGreatestMinimum(f); 265 int maxLow = cal.getLeastMaximum(f); 266 int maxHigh = cal.getMaximum(f); 267 268 // Fetch the hash for this field and keep track of the 269 // minima and maxima. 270 Map[] h = limits[j]; 271 if (h[0] == null) { 272 h[0] = new HashMap(); 273 h[1] = new HashMap(); 274 } 275 h[0].put(new Integer(minActual), nub); 276 h[1].put(new Integer(maxActual), nub); 277 278 if (minActual < minLow || minActual > minHigh) { 279 errln("Fail: " + ymdToString(cal) + 280 " Range for min of " + FIELD_NAME[f] + "(" + f + 281 ")=" + minLow + ".." + minHigh + 282 ", actual_min=" + minActual); 283 } 284 if (maxActual < maxLow || maxActual > maxHigh) { 285 errln("Fail: " + ymdToString(cal) + 286 " Range for max of " + FIELD_NAME[f] + "(" + f + 287 ")=" + maxLow + ".." + maxHigh + 288 ", actual_max=" + maxActual); 289 } 290 if (v < minActual || v > maxActual) { 291 errln("Fail: " + ymdToString(cal) + 292 " " + FIELD_NAME[f] + "(" + f + ")=" + v + 293 ", actual range=" + minActual + ".." + maxActual + 294 ", allowed=(" + minLow + ".." + minHigh + ")..(" + 295 maxLow + ".." + maxHigh + ")"); 296 } 297 } 298 greg.add(Calendar.DAY_OF_YEAR, 1); 299 } 300 301 // Check actual maxima and minima seen against ranges returned 302 // by API. 303 StringBuffer buf = new StringBuffer(); 304 for (int j=0; j<fieldsToTest.length; ++j) { 305 int f = fieldsToTest[j]; 306 buf.setLength(0); 307 buf.append(FIELD_NAME[f]); 308 Map[] h = limits[j]; 309 boolean fullRangeSeen = true; 310 for (int k=0; k<2; ++k) { 311 int rangeLow = (k==0) ? 312 cal.getMinimum(f) : cal.getLeastMaximum(f); 313 int rangeHigh = (k==0) ? 314 cal.getGreatestMinimum(f) : cal.getMaximum(f); 315 // If either the top of the range or the bottom was never 316 // seen, then there may be a problem. 317 if (h[k].get(new Integer(rangeLow)) == null || 318 h[k].get(new Integer(rangeHigh)) == null) { 319 fullRangeSeen = false; 320 } 321 buf.append(k==0 ? " minima seen=(" : "; maxima seen=("); 322 for (Object v : h[k].keySet()) { 323 buf.append(" " + v); 324 } 325 buf.append(") range=" + rangeLow + ".." + rangeHigh); 326 } 327 if (fullRangeSeen) { 328 logln("OK: " + buf.toString()); 329 } else { 330 // This may or may not be an error -- if the range of dates 331 // we scan over doesn't happen to contain a minimum or 332 // maximum, it doesn't mean some other range won't. 333 logln("Warning: " + buf.toString()); 334 } 335 } 336 337 logln("End: " + greg.getTime()); 338 } 339 340 /** 341 * doLimitsTest with default test duration 342 */ doLimitsTest(Calendar cal, int[] fieldsToTest, Date startDate)343 public void doLimitsTest(Calendar cal, int[] fieldsToTest, Date startDate) { 344 int testTime = getInclusion() <= 5 ? -3 : -120; // in seconds 345 doLimitsTest(cal, fieldsToTest, startDate, testTime); 346 } 347 348 /** 349 * Test the functions getMaximum/getGeratestMinimum logically correct. 350 * This method assumes day of week cycle is consistent. 351 * @param cal The calendar instance to be tested. 352 * @param leapMonth true if the calendar system has leap months 353 */ doTheoreticalLimitsTest(Calendar cal, boolean leapMonth)354 public void doTheoreticalLimitsTest(Calendar cal, boolean leapMonth) { 355 int nDOW = cal.getMaximum(Calendar.DAY_OF_WEEK); 356 int maxDOY = cal.getMaximum(Calendar.DAY_OF_YEAR); 357 int lmaxDOW = cal.getLeastMaximum(Calendar.DAY_OF_YEAR); 358 int maxWOY = cal.getMaximum(Calendar.WEEK_OF_YEAR); 359 int lmaxWOY = cal.getLeastMaximum(Calendar.WEEK_OF_YEAR); 360 int maxM = cal.getMaximum(Calendar.MONTH) + 1; 361 int lmaxM = cal.getLeastMaximum(Calendar.MONTH) + 1; 362 int maxDOM = cal.getMaximum(Calendar.DAY_OF_MONTH); 363 int lmaxDOM = cal.getLeastMaximum(Calendar.DAY_OF_MONTH); 364 int maxDOWIM = cal.getMaximum(Calendar.DAY_OF_WEEK_IN_MONTH); 365 int lmaxDOWIM = cal.getLeastMaximum(Calendar.DAY_OF_WEEK_IN_MONTH); 366 int maxWOM = cal.getMaximum(Calendar.WEEK_OF_MONTH); 367 int lmaxWOM = cal.getLeastMaximum(Calendar.WEEK_OF_MONTH); 368 int minDaysInFirstWeek = cal.getMinimalDaysInFirstWeek(); 369 370 // Day of year 371 int expected; 372 if (!leapMonth) { 373 expected = maxM*maxDOM; 374 if (maxDOY > expected) { 375 errln("FAIL: Maximum value of DAY_OF_YEAR is too big: " + maxDOY + "/expected: <=" + expected); 376 } 377 expected = lmaxM*lmaxDOM; 378 if (lmaxDOW < expected) { 379 errln("FAIL: Least maximum value of DAY_OF_YEAR is too small: " + lmaxDOW + "/expected: >=" + expected); 380 } 381 } 382 383 // Week of year 384 expected = maxDOY/nDOW + 1; 385 if (maxWOY > expected) { 386 errln("FAIL: Maximum value of WEEK_OF_YEAR is too big: " + maxWOY + "/expected: <=" + expected); 387 } 388 expected = lmaxDOW/nDOW; 389 if (lmaxWOY < expected) { 390 errln("FAIL: Least maximum value of WEEK_OF_YEAR is too small: " + lmaxWOY + "/expected >=" + expected); 391 } 392 393 // Day of week in month 394 expected = (maxDOM + nDOW - 1)/nDOW; 395 if (maxDOWIM != expected) { 396 errln("FAIL: Maximum value of DAY_OF_WEEK_IN_MONTH is incorrect: " + maxDOWIM + "/expected: " + expected); 397 } 398 expected = (lmaxDOM + nDOW - 1)/nDOW; 399 if (lmaxDOWIM != expected) { 400 errln("FAIL: Least maximum value of DAY_OF_WEEK_IN_MONTH is incorrect: " + lmaxDOWIM + "/expected: " + expected); 401 } 402 403 // Week of month 404 expected = (maxDOM + (nDOW - 1) + (nDOW - minDaysInFirstWeek)) / nDOW; 405 if (maxWOM != expected) { 406 errln("FAIL: Maximum value of WEEK_OF_MONTH is incorrect: " + maxWOM + "/expected: " + expected); 407 } 408 expected = (lmaxDOM + (nDOW - minDaysInFirstWeek)) / nDOW; 409 if (lmaxWOM != expected) { 410 errln("FAIL: Least maximum value of WEEK_OF_MONTH is incorrect: " + lmaxWOM + "/expected: " + expected); 411 } 412 } 413 414 /** 415 * Convert year,month,day values to the form "year/month/day". 416 * On input the month value is zero-based, but in the result string it is one-based. 417 */ ymdToString(int year, int month, int day)418 static public String ymdToString(int year, int month, int day) { 419 return "" + year + "/" + (month+1) + "/" + day; 420 } 421 422 /** 423 * Convert year,month,day values to the form "year/month/day". 424 */ ymdToString(Calendar cal)425 static public String ymdToString(Calendar cal) { 426 double day = getJulianDay(cal); 427 if (cal instanceof ChineseCalendar) { 428 return "" + cal.get(Calendar.EXTENDED_YEAR) + "/" + 429 (cal.get(Calendar.MONTH)+1) + 430 (cal.get(Calendar.IS_LEAP_MONTH)==1?"(leap)":"") + "/" + 431 cal.get(Calendar.DATE) + " (" + day + ", time=" + cal.getTimeInMillis() + ")"; 432 } 433 return ymdToString(cal.get(Calendar.EXTENDED_YEAR), 434 cal.get(MONTH), cal.get(DATE)) + 435 " (" + day + ", time=" + cal.getTimeInMillis() + ")"; 436 } 437 getJulianDay(Calendar cal)438 static double getJulianDay(Calendar cal) { 439 return (cal.getTime().getTime() - JULIAN_EPOCH) / DAY_MS; 440 } 441 442 static final double DAY_MS = 24*60*60*1000.0; 443 static final long JULIAN_EPOCH = -210866760000000L; // 1/1/4713 BC 12:00 444 } 445