blob: 703859ecfc0a233af82c356cdfa18ceeadf38fa3 [file] [log] [blame]
/*
*******************************************************************************
* Copyright (C) 1996-2005, International Business Machines Corporation and *
* others. All Rights Reserved. *
*******************************************************************************
*/
package com.ibm.icu.dev.test.calendar;
import com.ibm.icu.dev.test.*;
import com.ibm.icu.text.DateFormat;
import com.ibm.icu.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
import java.util.Hashtable;
import java.util.Enumeration;
import com.ibm.icu.util.*;
/**
* A base class for classes that test individual Calendar subclasses.
* Defines various useful utility methods and constants
*/
public class CalendarTest extends TestFmwk {
// Constants for use by subclasses, solely to save typing
public final static int SUN = Calendar.SUNDAY;
public final static int MON = Calendar.MONDAY;
public final static int TUE = Calendar.TUESDAY;
public final static int WED = Calendar.WEDNESDAY;
public final static int THU = Calendar.THURSDAY;
public final static int FRI = Calendar.FRIDAY;
public final static int SAT = Calendar.SATURDAY;
public final static int ERA = Calendar.ERA;
public final static int YEAR = Calendar.YEAR;
public final static int MONTH = Calendar.MONTH;
public final static int DATE = Calendar.DATE;
public final static int HOUR = Calendar.HOUR;
public final static int MINUTE = Calendar.MINUTE;
public final static int SECOND = Calendar.SECOND;
public final static int DOY = Calendar.DAY_OF_YEAR;
public final static int WOY = Calendar.WEEK_OF_YEAR;
public final static int WOM = Calendar.WEEK_OF_MONTH;
public final static int DOW = Calendar.DAY_OF_WEEK;
public final static int DOWM = Calendar.DAY_OF_WEEK_IN_MONTH;
public final static SimpleTimeZone UTC = new SimpleTimeZone(0, "GMT");
private static final String[] FIELD_NAME = {
"ERA", "YEAR", "MONTH", "WEEK_OF_YEAR", "WEEK_OF_MONTH",
"DAY_OF_MONTH", "DAY_OF_YEAR", "DAY_OF_WEEK",
"DAY_OF_WEEK_IN_MONTH", "AM_PM", "HOUR", "HOUR_OF_DAY",
"MINUTE", "SECOND", "MILLISECOND", "ZONE_OFFSET",
"DST_OFFSET", "YEAR_WOY", "DOW_LOCAL", "EXTENDED_YEAR",
"JULIAN_DAY", "MILLISECONDS_IN_DAY",
"IS_LEAP_MONTH" // (ChineseCalendar only)
};
public static final String fieldName(int f) {
return (f>=0 && f<FIELD_NAME.length) ?
FIELD_NAME[f] : ("<Field " + f + ">");
}
/**
* Iterates through a list of calendar <code>TestCase</code> objects and
* makes sure that the time-to-fields and fields-to-time calculations work
* correnctly for the values in each test case.
*/
public void doTestCases(TestCase[] cases, Calendar cal)
{
cal.setTimeZone(UTC);
// Get a format to use for printing dates in the calendar system we're testing
DateFormat format = DateFormat.getDateTimeInstance(cal, DateFormat.SHORT, -1, Locale.getDefault());
final String pattern = (cal instanceof ChineseCalendar) ?
"E MMl/dd/y G HH:mm:ss.S z" :
"E, MM/dd/yyyy G HH:mm:ss.S z";
((SimpleDateFormat)format).applyPattern(pattern);
// This format is used for printing Gregorian dates.
DateFormat gregFormat = new SimpleDateFormat(pattern);
gregFormat.setTimeZone(UTC);
GregorianCalendar pureGreg = new GregorianCalendar(UTC);
pureGreg.setGregorianChange(new Date(Long.MIN_VALUE));
DateFormat pureGregFmt = new SimpleDateFormat("E M/d/yyyy G");
pureGregFmt.setCalendar(pureGreg);
// Now iterate through the test cases and see what happens
for (int i = 0; i < cases.length; i++)
{
logln("\ntest case: " + i);
TestCase test = cases[i];
//
// First we want to make sure that the millis -> fields calculation works
// test.applyTime will call setTime() on the calendar object, and
// test.fieldsEqual will retrieve all of the field values and make sure
// that they're the same as the ones in the testcase
//
test.applyTime(cal);
if (!test.fieldsEqual(cal, this)) {
errln("Fail: (millis=>fields) " +
gregFormat.format(test.getTime()) + " => " +
format.format(cal.getTime()) +
", expected " + test);
}
//
// If that was OK, check the fields -> millis calculation
// test.applyFields will set all of the calendar's fields to
// match those in the test case.
//
cal.clear();
test.applyFields(cal);
if (!test.equals(cal)) {
errln("Fail: (fields=>millis) " + test + " => " +
pureGregFmt.format(cal.getTime()) +
", expected " + pureGregFmt.format(test.getTime()));
}
}
}
static public final boolean ROLL = true;
static public final boolean ADD = false;
/**
* Process test cases for <code>add</code> and <code>roll</code> methods.
* Each test case is an array of integers, as follows:
* <ul>
* <li>0: input year
* <li>1: month (zero-based)
* <li>2: day
* <li>3: field to roll or add to
* <li>4: amount to roll or add
* <li>5: result year
* <li>6: month (zero-based)
* <li>7: day
* </ul>
* For example:
* <pre>
* // input add by output
* // year month day field amount year month day
* { 5759, HESHVAN, 2, MONTH, 1, 5759, KISLEV, 2 },
* </pre>
*
* @param roll <code>true</code> or <code>ROLL</code> to test the <code>roll</code> method;
* <code>false</code> or <code>ADD</code> to test the <code>add</code method
*/
public void doRollAdd(boolean roll, Calendar cal, int[][] tests)
{
String name = roll ? "rolling" : "adding";
for (int i = 0; i < tests.length; i++) {
int[] test = tests[i];
cal.clear();
if (cal instanceof ChineseCalendar) {
cal.set(Calendar.EXTENDED_YEAR, test[0]);
cal.set(Calendar.MONTH, test[1]);
cal.set(Calendar.DAY_OF_MONTH, test[2]);
} else {
cal.set(test[0], test[1], test[2]);
}
double day0 = getJulianDay(cal);
if (roll) {
cal.roll(test[3], test[4]);
} else {
cal.add(test[3], test[4]);
}
int y = cal.get(cal instanceof ChineseCalendar ?
Calendar.EXTENDED_YEAR : YEAR);
if (y != test[5] || cal.get(MONTH) != test[6]
|| cal.get(DATE) != test[7])
{
errln("Fail: " + name + " "+ ymdToString(test[0], test[1], test[2])
+ " (" + day0 + ")"
+ " " + FIELD_NAME[test[3]] + " by " + test[4]
+ ": expected " + ymdToString(test[5], test[6], test[7])
+ ", got " + ymdToString(cal));
} else if (isVerbose()) {
logln("OK: " + name + " "+ ymdToString(test[0], test[1], test[2])
+ " (" + day0 + ")"
+ " " + FIELD_NAME[test[3]] + " by " + test[4]
+ ": got " + ymdToString(cal));
}
}
}
/**
* Test the functions getXxxMinimum() and getXxxMaximum() by marching a
* test calendar 'cal' through 'numberOfDays' sequential days starting
* with 'startDate'. For each date, read a field value along with its
* reported actual minimum and actual maximum. These values are
* checked against one another as well as against getMinimum(),
* getGreatestMinimum(), getLeastMaximum(), and getMaximum(). We
* expect to see:
*
* 1. minimum <= actualMinimum <= greatestMinimum <=
* leastMaximum <= actualMaximum <= maximum
*
* 2. actualMinimum <= value <= actualMaximum
*
* Note: In addition to outright failures, this test reports some
* results as warnings. These are not generally of concern, but they
* should be evaluated by a human. To see these, run this test in
* verbose mode.
* @param cal the calendar to be tested
* @param fieldsToTest an array of field values to be tested, e.g., new
* int[] { Calendar.MONTH, Calendar.DAY_OF_MONTH }. It only makes
* sense to test the day fields; the time fields are not tested by this
* method. If null, then test all standard fields.
* @param startDate the first date to test
* @param testDuration if positive, the number of days to be tested.
* If negative, the number of seconds to run the test.
*/
protected void doLimitsTest(Calendar cal, int[] fieldsToTest,
Date startDate, int testDuration) {
GregorianCalendar greg = new GregorianCalendar();
greg.setTime(startDate);
logln("Start: " + startDate);
if (fieldsToTest == null) {
fieldsToTest = new int[] {
Calendar.ERA, Calendar.YEAR, Calendar.MONTH,
Calendar.WEEK_OF_YEAR, Calendar.WEEK_OF_MONTH,
Calendar.DAY_OF_MONTH, Calendar.DAY_OF_YEAR,
Calendar.DAY_OF_WEEK_IN_MONTH, Calendar.YEAR_WOY,
Calendar.EXTENDED_YEAR
};
}
// Keep a record of minima and maxima that we actually see.
// These are kept in an array of arrays of hashes.
Hashtable[][] limits = new Hashtable[fieldsToTest.length][2];
Object nub = new Object(); // Meaningless placeholder
// This test can run for a long time; show progress.
long millis = System.currentTimeMillis();
long mark = millis + 5000; // 5 sec
millis -= testDuration * 1000; // stop time if testDuration<0
for (int i=0;
testDuration>0 ? i<testDuration
: System.currentTimeMillis()<millis;
++i) {
if (System.currentTimeMillis() >= mark) {
logln("(" + i + " days)");
mark += 5000; // 5 sec
}
cal.setTimeInMillis(greg.getTimeInMillis());
for (int j=0; j<fieldsToTest.length; ++j) {
int f = fieldsToTest[j];
int v = cal.get(f);
int minActual = cal.getActualMinimum(f);
int maxActual = cal.getActualMaximum(f);
int minLow = cal.getMinimum(f);
int minHigh = cal.getGreatestMinimum(f);
int maxLow = cal.getLeastMaximum(f);
int maxHigh = cal.getMaximum(f);
// Fetch the hash for this field and keep track of the
// minima and maxima.
Hashtable[] h = limits[j];
if (h[0] == null) {
h[0] = new Hashtable();
h[1] = new Hashtable();
}
h[0].put(new Integer(minActual), nub);
h[1].put(new Integer(maxActual), nub);
if (minActual < minLow || minActual > minHigh) {
errln("Fail: " + ymdToString(cal) +
" Range for min of " + FIELD_NAME[f] +
"=" + minLow + ".." + minHigh +
", actual_min=" + minActual);
}
if (maxActual < maxLow || maxActual > maxHigh) {
errln("Fail: " + ymdToString(cal) +
" Range for max of " + FIELD_NAME[f] +
"=" + maxLow + ".." + maxHigh +
", actual_max=" + maxActual);
}
if (v < minActual || v > maxActual) {
errln("Fail: " + ymdToString(cal) +
" " + FIELD_NAME[f] + "=" + v +
", actual range=" + minActual + ".." + maxActual +
", allowed=(" + minLow + ".." + minHigh + ")..(" +
maxLow + ".." + maxHigh + ")");
}
}
greg.add(Calendar.DAY_OF_YEAR, 1);
}
// Check actual maxima and minima seen against ranges returned
// by API.
StringBuffer buf = new StringBuffer();
for (int j=0; j<fieldsToTest.length; ++j) {
int f = fieldsToTest[j];
buf.setLength(0);
buf.append(FIELD_NAME[f]);
Hashtable[] h = limits[j];
boolean fullRangeSeen = true;
for (int k=0; k<2; ++k) {
int rangeLow = (k==0) ?
cal.getMinimum(f) : cal.getLeastMaximum(f);
int rangeHigh = (k==0) ?
cal.getGreatestMinimum(f) : cal.getMaximum(f);
// If either the top of the range or the bottom was never
// seen, then there may be a problem.
if (h[k].get(new Integer(rangeLow)) == null ||
h[k].get(new Integer(rangeHigh)) == null) {
fullRangeSeen = false;
}
buf.append(k==0 ? " minima seen=(" : "; maxima seen=(");
for (Enumeration e=h[k].keys(); e.hasMoreElements(); ) {
int v = ((Integer) e.nextElement()).intValue();
buf.append(" " + v);
}
buf.append(") range=" + rangeLow + ".." + rangeHigh);
}
if (fullRangeSeen) {
logln("OK: " + buf.toString());
} else {
// This may or may not be an error -- if the range of dates
// we scan over doesn't happen to contain a minimum or
// maximum, it doesn't mean some other range won't.
logln("Warning: " + buf.toString());
}
}
logln("End: " + greg.getTime());
}
/**
* Convert year,month,day values to the form "year/month/day".
* On input the month value is zero-based, but in the result string it is one-based.
*/
static public String ymdToString(int year, int month, int day) {
return "" + year + "/" + (month+1) + "/" + day;
}
/**
* Convert year,month,day values to the form "year/month/day".
*/
static public String ymdToString(Calendar cal) {
double day = getJulianDay(cal);
if (cal instanceof ChineseCalendar) {
return "" + cal.get(Calendar.EXTENDED_YEAR) + "/" +
(cal.get(Calendar.MONTH)+1) +
(cal.get(ChineseCalendar.IS_LEAP_MONTH)==1?"(leap)":"") + "/" +
cal.get(Calendar.DATE) + " (" + day + ")";
}
return ymdToString(cal.get(Calendar.EXTENDED_YEAR),
cal.get(MONTH), cal.get(DATE)) +
" (" + day + ")";
}
static double getJulianDay(Calendar cal) {
return (cal.getTime().getTime() - JULIAN_EPOCH) / DAY_MS;
}
static final double DAY_MS = 24*60*60*1000.0;
static final long JULIAN_EPOCH = -210866760000000L; // 1/1/4713 BC 12:00
}