| /* |
| ******************************************************************************* |
| * Copyright (C) 1996-2010, International Business Machines Corporation and * |
| * others. All Rights Reserved. * |
| ******************************************************************************* |
| */ |
| |
| package com.ibm.icu.dev.test.calendar; |
| |
| import java.util.Date; |
| import java.util.HashMap; |
| import java.util.Locale; |
| import java.util.Map; |
| |
| import com.ibm.icu.dev.test.TestFmwk; |
| import com.ibm.icu.text.DateFormat; |
| import com.ibm.icu.text.SimpleDateFormat; |
| import com.ibm.icu.util.Calendar; |
| import com.ibm.icu.util.ChineseCalendar; |
| import com.ibm.icu.util.GregorianCalendar; |
| import com.ibm.icu.util.SimpleTimeZone; |
| |
| /** |
| * 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. |
| */ |
| public 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. |
| Map[][] limits = new Map[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. |
| Map[] h = limits[j]; |
| if (h[0] == null) { |
| h[0] = new HashMap(); |
| h[1] = new HashMap(); |
| } |
| 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] + "(" + f + |
| ")=" + minLow + ".." + minHigh + |
| ", actual_min=" + minActual); |
| } |
| if (maxActual < maxLow || maxActual > maxHigh) { |
| errln("Fail: " + ymdToString(cal) + |
| " Range for max of " + FIELD_NAME[f] + "(" + f + |
| ")=" + maxLow + ".." + maxHigh + |
| ", actual_max=" + maxActual); |
| } |
| if (v < minActual || v > maxActual) { |
| errln("Fail: " + ymdToString(cal) + |
| " " + FIELD_NAME[f] + "(" + 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]); |
| Map[] 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 (Object v : h[k].keySet()) { |
| 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()); |
| } |
| |
| /** |
| * doLimitsTest with default test duration |
| */ |
| public void doLimitsTest(Calendar cal, int[] fieldsToTest, Date startDate) { |
| int testTime = getInclusion() <= 5 ? -3 : -120; // in seconds |
| doLimitsTest(cal, fieldsToTest, startDate, testTime); |
| } |
| |
| /** |
| * Test the functions getMaximum/getGeratestMinimum logically correct. |
| * This method assumes day of week cycle is consistent. |
| * @param cal The calendar instance to be tested. |
| * @param leapMonth true if the calendar system has leap months |
| */ |
| public void doTheoreticalLimitsTest(Calendar cal, boolean leapMonth) { |
| int nDOW = cal.getMaximum(Calendar.DAY_OF_WEEK); |
| int maxDOY = cal.getMaximum(Calendar.DAY_OF_YEAR); |
| int lmaxDOW = cal.getLeastMaximum(Calendar.DAY_OF_YEAR); |
| int maxWOY = cal.getMaximum(Calendar.WEEK_OF_YEAR); |
| int lmaxWOY = cal.getLeastMaximum(Calendar.WEEK_OF_YEAR); |
| int maxM = cal.getMaximum(Calendar.MONTH) + 1; |
| int lmaxM = cal.getLeastMaximum(Calendar.MONTH) + 1; |
| int maxDOM = cal.getMaximum(Calendar.DAY_OF_MONTH); |
| int lmaxDOM = cal.getLeastMaximum(Calendar.DAY_OF_MONTH); |
| int maxDOWIM = cal.getMaximum(Calendar.DAY_OF_WEEK_IN_MONTH); |
| int lmaxDOWIM = cal.getLeastMaximum(Calendar.DAY_OF_WEEK_IN_MONTH); |
| int maxWOM = cal.getMaximum(Calendar.WEEK_OF_MONTH); |
| int lmaxWOM = cal.getLeastMaximum(Calendar.WEEK_OF_MONTH); |
| int minDaysInFirstWeek = cal.getMinimalDaysInFirstWeek(); |
| |
| // Day of year |
| int expected; |
| if (!leapMonth) { |
| expected = maxM*maxDOM; |
| if (maxDOY > expected) { |
| errln("FAIL: Maximum value of DAY_OF_YEAR is too big: " + maxDOY + "/expected: <=" + expected); |
| } |
| expected = lmaxM*lmaxDOM; |
| if (lmaxDOW < expected) { |
| errln("FAIL: Least maximum value of DAY_OF_YEAR is too small: " + lmaxDOW + "/expected: >=" + expected); |
| } |
| } |
| |
| // Week of year |
| expected = maxDOY/nDOW + 1; |
| if (maxWOY > expected) { |
| errln("FAIL: Maximum value of WEEK_OF_YEAR is too big: " + maxWOY + "/expected: <=" + expected); |
| } |
| expected = lmaxDOW/nDOW; |
| if (lmaxWOY < expected) { |
| errln("FAIL: Least maximum value of WEEK_OF_YEAR is too small: " + lmaxWOY + "/expected >=" + expected); |
| } |
| |
| // Day of week in month |
| expected = (maxDOM + nDOW - 1)/nDOW; |
| if (maxDOWIM != expected) { |
| errln("FAIL: Maximum value of DAY_OF_WEEK_IN_MONTH is incorrect: " + maxDOWIM + "/expected: " + expected); |
| } |
| expected = (lmaxDOM + nDOW - 1)/nDOW; |
| if (lmaxDOWIM != expected) { |
| errln("FAIL: Least maximum value of DAY_OF_WEEK_IN_MONTH is incorrect: " + lmaxDOWIM + "/expected: " + expected); |
| } |
| |
| // Week of month |
| expected = (maxDOM + (nDOW - 1) + (nDOW - minDaysInFirstWeek)) / nDOW; |
| if (maxWOM != expected) { |
| errln("FAIL: Maximum value of WEEK_OF_MONTH is incorrect: " + maxWOM + "/expected: " + expected); |
| } |
| expected = (lmaxDOM + (nDOW - minDaysInFirstWeek)) / nDOW; |
| if (lmaxWOM != expected) { |
| errln("FAIL: Least maximum value of WEEK_OF_MONTH is incorrect: " + lmaxWOM + "/expected: " + expected); |
| } |
| } |
| |
| /** |
| * 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(Calendar.IS_LEAP_MONTH)==1?"(leap)":"") + "/" + |
| cal.get(Calendar.DATE) + " (" + day + ", time=" + cal.getTimeInMillis() + ")"; |
| } |
| return ymdToString(cal.get(Calendar.EXTENDED_YEAR), |
| cal.get(MONTH), cal.get(DATE)) + |
| " (" + day + ", time=" + cal.getTimeInMillis() + ")"; |
| } |
| |
| 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 |
| } |