2 *******************************************************************************
3 * Copyright (C) 1996-2010, International Business Machines Corporation and *
4 * others. All Rights Reserved. *
5 *******************************************************************************
8 package com.ibm.icu.dev.test.calendar;
10 import java.util.Date;
11 import java.util.HashMap;
12 import java.util.Locale;
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;
24 * A base class for classes that test individual Calendar subclasses.
25 * Defines various useful utility methods and constants
27 public class CalendarTest extends TestFmwk {
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;
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;
51 public final static SimpleTimeZone UTC = new SimpleTimeZone(0, "GMT");
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)
63 public static final String fieldName(int f) {
64 return (f>=0 && f<FIELD_NAME.length) ?
65 FIELD_NAME[f] : ("<Field " + f + ">");
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.
73 public void doTestCases(TestCase[] cases, Calendar cal)
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());
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";
84 ((SimpleDateFormat)format).applyPattern(pattern);
86 // This format is used for printing Gregorian dates.
87 DateFormat gregFormat = new SimpleDateFormat(pattern);
88 gregFormat.setTimeZone(UTC);
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);
95 // Now iterate through the test cases and see what happens
96 for (int i = 0; i < cases.length; i++)
98 logln("\ntest case: " + i);
99 TestCase test = cases[i];
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
108 if (!test.fieldsEqual(cal, this)) {
109 errln("Fail: (millis=>fields) " +
110 gregFormat.format(test.getTime()) + " => " +
111 format.format(cal.getTime()) +
112 ", expected " + test);
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.
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()));
130 static public final boolean ROLL = true;
131 static public final boolean ADD = false;
134 * Process test cases for <code>add</code> and <code>roll</code> methods.
135 * Each test case is an array of integers, as follows:
138 * <li>1: month (zero-based)
140 * <li>3: field to roll or add to
141 * <li>4: amount to roll or add
143 * <li>6: month (zero-based)
148 * // input add by output
149 * // year month day field amount year month day
150 * { 5759, HESHVAN, 2, MONTH, 1, 5759, KISLEV, 2 },
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
156 public void doRollAdd(boolean roll, Calendar cal, int[][] tests)
158 String name = roll ? "rolling" : "adding";
160 for (int i = 0; i < tests.length; i++) {
161 int[] test = tests[i];
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]);
169 cal.set(test[0], test[1], test[2]);
171 double day0 = getJulianDay(cal);
173 cal.roll(test[3], test[4]);
175 cal.add(test[3], test[4]);
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])
182 errln("Fail: " + name + " "+ ymdToString(test[0], test[1], test[2])
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])
190 + " " + FIELD_NAME[test[3]] + " by " + test[4]
191 + ": got " + ymdToString(cal));
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
205 * 1. minimum <= actualMinimum <= greatestMinimum <=
206 * leastMaximum <= actualMaximum <= maximum
208 * 2. actualMinimum <= value <= actualMaximum
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
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.
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);
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
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
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
250 testDuration>0 ? i<testDuration
251 : System.currentTimeMillis()<millis;
253 if (System.currentTimeMillis() >= mark) {
254 logln("(" + i + " days)");
255 mark += 5000; // 5 sec
257 cal.setTimeInMillis(greg.getTimeInMillis());
258 for (int j=0; j<fieldsToTest.length; ++j) {
259 int f = fieldsToTest[j];
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);
268 // Fetch the hash for this field and keep track of the
269 // minima and maxima.
272 h[0] = new HashMap();
273 h[1] = new HashMap();
275 h[0].put(new Integer(minActual), nub);
276 h[1].put(new Integer(maxActual), nub);
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);
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);
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 + ")");
298 greg.add(Calendar.DAY_OF_YEAR, 1);
301 // Check actual maxima and minima seen against ranges returned
303 StringBuffer buf = new StringBuffer();
304 for (int j=0; j<fieldsToTest.length; ++j) {
305 int f = fieldsToTest[j];
307 buf.append(FIELD_NAME[f]);
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;
321 buf.append(k==0 ? " minima seen=(" : "; maxima seen=(");
322 for (Object v : h[k].keySet()) {
325 buf.append(") range=" + rangeLow + ".." + rangeHigh);
328 logln("OK: " + buf.toString());
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());
337 logln("End: " + greg.getTime());
341 * doLimitsTest with default test duration
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);
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
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();
373 expected = maxM*maxDOM;
374 if (maxDOY > expected) {
375 errln("FAIL: Maximum value of DAY_OF_YEAR is too big: " + maxDOY + "/expected: <=" + expected);
377 expected = lmaxM*lmaxDOM;
378 if (lmaxDOW < expected) {
379 errln("FAIL: Least maximum value of DAY_OF_YEAR is too small: " + lmaxDOW + "/expected: >=" + expected);
384 expected = maxDOY/nDOW + 1;
385 if (maxWOY > expected) {
386 errln("FAIL: Maximum value of WEEK_OF_YEAR is too big: " + maxWOY + "/expected: <=" + expected);
388 expected = lmaxDOW/nDOW;
389 if (lmaxWOY < expected) {
390 errln("FAIL: Least maximum value of WEEK_OF_YEAR is too small: " + lmaxWOY + "/expected >=" + expected);
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);
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);
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);
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);
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.
418 static public String ymdToString(int year, int month, int day) {
419 return "" + year + "/" + (month+1) + "/" + day;
423 * Convert year,month,day values to the form "year/month/day".
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() + ")";
433 return ymdToString(cal.get(Calendar.EXTENDED_YEAR),
434 cal.get(MONTH), cal.get(DATE)) +
435 " (" + day + ", time=" + cal.getTimeInMillis() + ")";
438 static double getJulianDay(Calendar cal) {
439 return (cal.getTime().getTime() - JULIAN_EPOCH) / DAY_MS;
442 static final double DAY_MS = 24*60*60*1000.0;
443 static final long JULIAN_EPOCH = -210866760000000L; // 1/1/4713 BC 12:00