001: /*
002: *******************************************************************************
003: * Copyright (C) 1996-2005, International Business Machines Corporation and *
004: * others. All Rights Reserved. *
005: *******************************************************************************
006: */
007:
008: package com.ibm.icu.dev.test.calendar;
009:
010: import com.ibm.icu.dev.test.*;
011: import com.ibm.icu.text.DateFormat;
012: import com.ibm.icu.text.SimpleDateFormat;
013: import java.util.Date;
014: import java.util.Locale;
015: import java.util.Hashtable;
016: import java.util.Enumeration;
017:
018: import com.ibm.icu.util.*;
019:
020: /**
021: * A base class for classes that test individual Calendar subclasses.
022: * Defines various useful utility methods and constants
023: */
024: public class CalendarTest extends TestFmwk {
025:
026: // Constants for use by subclasses, solely to save typing
027: public final static int SUN = Calendar.SUNDAY;
028: public final static int MON = Calendar.MONDAY;
029: public final static int TUE = Calendar.TUESDAY;
030: public final static int WED = Calendar.WEDNESDAY;
031: public final static int THU = Calendar.THURSDAY;
032: public final static int FRI = Calendar.FRIDAY;
033: public final static int SAT = Calendar.SATURDAY;
034:
035: public final static int ERA = Calendar.ERA;
036: public final static int YEAR = Calendar.YEAR;
037: public final static int MONTH = Calendar.MONTH;
038: public final static int DATE = Calendar.DATE;
039: public final static int HOUR = Calendar.HOUR;
040: public final static int MINUTE = Calendar.MINUTE;
041: public final static int SECOND = Calendar.SECOND;
042: public final static int DOY = Calendar.DAY_OF_YEAR;
043: public final static int WOY = Calendar.WEEK_OF_YEAR;
044: public final static int WOM = Calendar.WEEK_OF_MONTH;
045: public final static int DOW = Calendar.DAY_OF_WEEK;
046: public final static int DOWM = Calendar.DAY_OF_WEEK_IN_MONTH;
047:
048: public final static SimpleTimeZone UTC = new SimpleTimeZone(0,
049: "GMT");
050:
051: private static final String[] FIELD_NAME = { "ERA", "YEAR",
052: "MONTH", "WEEK_OF_YEAR", "WEEK_OF_MONTH", "DAY_OF_MONTH",
053: "DAY_OF_YEAR", "DAY_OF_WEEK", "DAY_OF_WEEK_IN_MONTH",
054: "AM_PM", "HOUR", "HOUR_OF_DAY", "MINUTE", "SECOND",
055: "MILLISECOND", "ZONE_OFFSET", "DST_OFFSET", "YEAR_WOY",
056: "DOW_LOCAL", "EXTENDED_YEAR", "JULIAN_DAY",
057: "MILLISECONDS_IN_DAY", "IS_LEAP_MONTH" // (ChineseCalendar only)
058: };
059:
060: public static final String fieldName(int f) {
061: return (f >= 0 && f < FIELD_NAME.length) ? FIELD_NAME[f]
062: : ("<Field " + f + ">");
063: }
064:
065: /**
066: * Iterates through a list of calendar <code>TestCase</code> objects and
067: * makes sure that the time-to-fields and fields-to-time calculations work
068: * correnctly for the values in each test case.
069: */
070: public void doTestCases(TestCase[] cases, Calendar cal) {
071: cal.setTimeZone(UTC);
072:
073: // Get a format to use for printing dates in the calendar system we're testing
074: DateFormat format = DateFormat.getDateTimeInstance(cal,
075: DateFormat.SHORT, -1, Locale.getDefault());
076:
077: final String pattern = (cal instanceof ChineseCalendar) ? "E MMl/dd/y G HH:mm:ss.S z"
078: : "E, MM/dd/yyyy G HH:mm:ss.S z";
079:
080: ((SimpleDateFormat) format).applyPattern(pattern);
081:
082: // This format is used for printing Gregorian dates.
083: DateFormat gregFormat = new SimpleDateFormat(pattern);
084: gregFormat.setTimeZone(UTC);
085:
086: GregorianCalendar pureGreg = new GregorianCalendar(UTC);
087: pureGreg.setGregorianChange(new Date(Long.MIN_VALUE));
088: DateFormat pureGregFmt = new SimpleDateFormat("E M/d/yyyy G");
089: pureGregFmt.setCalendar(pureGreg);
090:
091: // Now iterate through the test cases and see what happens
092: for (int i = 0; i < cases.length; i++) {
093: logln("\ntest case: " + i);
094: TestCase test = cases[i];
095:
096: //
097: // First we want to make sure that the millis -> fields calculation works
098: // test.applyTime will call setTime() on the calendar object, and
099: // test.fieldsEqual will retrieve all of the field values and make sure
100: // that they're the same as the ones in the testcase
101: //
102: test.applyTime(cal);
103: if (!test.fieldsEqual(cal, this )) {
104: errln("Fail: (millis=>fields) "
105: + gregFormat.format(test.getTime()) + " => "
106: + format.format(cal.getTime()) + ", expected "
107: + test);
108: }
109:
110: //
111: // If that was OK, check the fields -> millis calculation
112: // test.applyFields will set all of the calendar's fields to
113: // match those in the test case.
114: //
115: cal.clear();
116: test.applyFields(cal);
117: if (!test.equals(cal)) {
118: errln("Fail: (fields=>millis) " + test + " => "
119: + pureGregFmt.format(cal.getTime())
120: + ", expected "
121: + pureGregFmt.format(test.getTime()));
122: }
123: }
124: }
125:
126: static public final boolean ROLL = true;
127: static public final boolean ADD = false;
128:
129: /**
130: * Process test cases for <code>add</code> and <code>roll</code> methods.
131: * Each test case is an array of integers, as follows:
132: * <ul>
133: * <li>0: input year
134: * <li>1: month (zero-based)
135: * <li>2: day
136: * <li>3: field to roll or add to
137: * <li>4: amount to roll or add
138: * <li>5: result year
139: * <li>6: month (zero-based)
140: * <li>7: day
141: * </ul>
142: * For example:
143: * <pre>
144: * // input add by output
145: * // year month day field amount year month day
146: * { 5759, HESHVAN, 2, MONTH, 1, 5759, KISLEV, 2 },
147: * </pre>
148: *
149: * @param roll <code>true</code> or <code>ROLL</code> to test the <code>roll</code> method;
150: * <code>false</code> or <code>ADD</code> to test the <code>add</code method
151: */
152: public void doRollAdd(boolean roll, Calendar cal, int[][] tests) {
153: String name = roll ? "rolling" : "adding";
154:
155: for (int i = 0; i < tests.length; i++) {
156: int[] test = tests[i];
157:
158: cal.clear();
159: if (cal instanceof ChineseCalendar) {
160: cal.set(Calendar.EXTENDED_YEAR, test[0]);
161: cal.set(Calendar.MONTH, test[1]);
162: cal.set(Calendar.DAY_OF_MONTH, test[2]);
163: } else {
164: cal.set(test[0], test[1], test[2]);
165: }
166: double day0 = getJulianDay(cal);
167: if (roll) {
168: cal.roll(test[3], test[4]);
169: } else {
170: cal.add(test[3], test[4]);
171: }
172: int y = cal
173: .get(cal instanceof ChineseCalendar ? Calendar.EXTENDED_YEAR
174: : YEAR);
175: if (y != test[5] || cal.get(MONTH) != test[6]
176: || cal.get(DATE) != test[7]) {
177: errln("Fail: " + name + " "
178: + ymdToString(test[0], test[1], test[2]) + " ("
179: + day0 + ")" + " " + FIELD_NAME[test[3]]
180: + " by " + test[4] + ": expected "
181: + ymdToString(test[5], test[6], test[7])
182: + ", got " + ymdToString(cal));
183: } else if (isVerbose()) {
184: logln("OK: " + name + " "
185: + ymdToString(test[0], test[1], test[2]) + " ("
186: + day0 + ")" + " " + FIELD_NAME[test[3]]
187: + " by " + test[4] + ": got "
188: + ymdToString(cal));
189: }
190: }
191: }
192:
193: /**
194: * Test the functions getXxxMinimum() and getXxxMaximum() by marching a
195: * test calendar 'cal' through 'numberOfDays' sequential days starting
196: * with 'startDate'. For each date, read a field value along with its
197: * reported actual minimum and actual maximum. These values are
198: * checked against one another as well as against getMinimum(),
199: * getGreatestMinimum(), getLeastMaximum(), and getMaximum(). We
200: * expect to see:
201: *
202: * 1. minimum <= actualMinimum <= greatestMinimum <=
203: * leastMaximum <= actualMaximum <= maximum
204: *
205: * 2. actualMinimum <= value <= actualMaximum
206: *
207: * Note: In addition to outright failures, this test reports some
208: * results as warnings. These are not generally of concern, but they
209: * should be evaluated by a human. To see these, run this test in
210: * verbose mode.
211: * @param cal the calendar to be tested
212: * @param fieldsToTest an array of field values to be tested, e.g., new
213: * int[] { Calendar.MONTH, Calendar.DAY_OF_MONTH }. It only makes
214: * sense to test the day fields; the time fields are not tested by this
215: * method. If null, then test all standard fields.
216: * @param startDate the first date to test
217: * @param testDuration if positive, the number of days to be tested.
218: * If negative, the number of seconds to run the test.
219: */
220: protected void doLimitsTest(Calendar cal, int[] fieldsToTest,
221: Date startDate, int testDuration) {
222: GregorianCalendar greg = new GregorianCalendar();
223: greg.setTime(startDate);
224: logln("Start: " + startDate);
225:
226: if (fieldsToTest == null) {
227: fieldsToTest = new int[] { Calendar.ERA, Calendar.YEAR,
228: Calendar.MONTH, Calendar.WEEK_OF_YEAR,
229: Calendar.WEEK_OF_MONTH, Calendar.DAY_OF_MONTH,
230: Calendar.DAY_OF_YEAR,
231: Calendar.DAY_OF_WEEK_IN_MONTH, Calendar.YEAR_WOY,
232: Calendar.EXTENDED_YEAR };
233: }
234:
235: // Keep a record of minima and maxima that we actually see.
236: // These are kept in an array of arrays of hashes.
237: Hashtable[][] limits = new Hashtable[fieldsToTest.length][2];
238: Object nub = new Object(); // Meaningless placeholder
239:
240: // This test can run for a long time; show progress.
241: long millis = System.currentTimeMillis();
242: long mark = millis + 5000; // 5 sec
243: millis -= testDuration * 1000; // stop time if testDuration<0
244:
245: for (int i = 0; testDuration > 0 ? i < testDuration : System
246: .currentTimeMillis() < millis; ++i) {
247: if (System.currentTimeMillis() >= mark) {
248: logln("(" + i + " days)");
249: mark += 5000; // 5 sec
250: }
251: cal.setTimeInMillis(greg.getTimeInMillis());
252: for (int j = 0; j < fieldsToTest.length; ++j) {
253: int f = fieldsToTest[j];
254: int v = cal.get(f);
255: int minActual = cal.getActualMinimum(f);
256: int maxActual = cal.getActualMaximum(f);
257: int minLow = cal.getMinimum(f);
258: int minHigh = cal.getGreatestMinimum(f);
259: int maxLow = cal.getLeastMaximum(f);
260: int maxHigh = cal.getMaximum(f);
261:
262: // Fetch the hash for this field and keep track of the
263: // minima and maxima.
264: Hashtable[] h = limits[j];
265: if (h[0] == null) {
266: h[0] = new Hashtable();
267: h[1] = new Hashtable();
268: }
269: h[0].put(new Integer(minActual), nub);
270: h[1].put(new Integer(maxActual), nub);
271:
272: if (minActual < minLow || minActual > minHigh) {
273: errln("Fail: " + ymdToString(cal)
274: + " Range for min of " + FIELD_NAME[f]
275: + "=" + minLow + ".." + minHigh
276: + ", actual_min=" + minActual);
277: }
278: if (maxActual < maxLow || maxActual > maxHigh) {
279: errln("Fail: " + ymdToString(cal)
280: + " Range for max of " + FIELD_NAME[f]
281: + "=" + maxLow + ".." + maxHigh
282: + ", actual_max=" + maxActual);
283: }
284: if (v < minActual || v > maxActual) {
285: errln("Fail: " + ymdToString(cal) + " "
286: + FIELD_NAME[f] + "=" + v
287: + ", actual range=" + minActual + ".."
288: + maxActual + ", allowed=(" + minLow + ".."
289: + minHigh + ")..(" + maxLow + ".."
290: + maxHigh + ")");
291: }
292: }
293: greg.add(Calendar.DAY_OF_YEAR, 1);
294: }
295:
296: // Check actual maxima and minima seen against ranges returned
297: // by API.
298: StringBuffer buf = new StringBuffer();
299: for (int j = 0; j < fieldsToTest.length; ++j) {
300: int f = fieldsToTest[j];
301: buf.setLength(0);
302: buf.append(FIELD_NAME[f]);
303: Hashtable[] h = limits[j];
304: boolean fullRangeSeen = true;
305: for (int k = 0; k < 2; ++k) {
306: int rangeLow = (k == 0) ? cal.getMinimum(f) : cal
307: .getLeastMaximum(f);
308: int rangeHigh = (k == 0) ? cal.getGreatestMinimum(f)
309: : cal.getMaximum(f);
310: // If either the top of the range or the bottom was never
311: // seen, then there may be a problem.
312: if (h[k].get(new Integer(rangeLow)) == null
313: || h[k].get(new Integer(rangeHigh)) == null) {
314: fullRangeSeen = false;
315: }
316: buf.append(k == 0 ? " minima seen=("
317: : "; maxima seen=(");
318: for (Enumeration e = h[k].keys(); e.hasMoreElements();) {
319: int v = ((Integer) e.nextElement()).intValue();
320: buf.append(" " + v);
321: }
322: buf.append(") range=" + rangeLow + ".." + rangeHigh);
323: }
324: if (fullRangeSeen) {
325: logln("OK: " + buf.toString());
326: } else {
327: // This may or may not be an error -- if the range of dates
328: // we scan over doesn't happen to contain a minimum or
329: // maximum, it doesn't mean some other range won't.
330: logln("Warning: " + buf.toString());
331: }
332: }
333:
334: logln("End: " + greg.getTime());
335: }
336:
337: /**
338: * Convert year,month,day values to the form "year/month/day".
339: * On input the month value is zero-based, but in the result string it is one-based.
340: */
341: static public String ymdToString(int year, int month, int day) {
342: return "" + year + "/" + (month + 1) + "/" + day;
343: }
344:
345: /**
346: * Convert year,month,day values to the form "year/month/day".
347: */
348: static public String ymdToString(Calendar cal) {
349: double day = getJulianDay(cal);
350: if (cal instanceof ChineseCalendar) {
351: return ""
352: + cal.get(Calendar.EXTENDED_YEAR)
353: + "/"
354: + (cal.get(Calendar.MONTH) + 1)
355: + (cal.get(ChineseCalendar.IS_LEAP_MONTH) == 1 ? "(leap)"
356: : "") + "/" + cal.get(Calendar.DATE) + " ("
357: + day + ")";
358: }
359: return ymdToString(cal.get(Calendar.EXTENDED_YEAR), cal
360: .get(MONTH), cal.get(DATE))
361: + " (" + day + ")";
362: }
363:
364: static double getJulianDay(Calendar cal) {
365: return (cal.getTime().getTime() - JULIAN_EPOCH) / DAY_MS;
366: }
367:
368: static final double DAY_MS = 24 * 60 * 60 * 1000.0;
369: static final long JULIAN_EPOCH = -210866760000000L; // 1/1/4713 BC 12:00
370: }
|