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