Initial support for evaluating external add-in functions like YEARFRAC

git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@688650 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Josh Micich 2008-08-25 08:09:02 +00:00
parent 313d71cc51
commit 02608ba18d
16 changed files with 1124 additions and 126 deletions

View File

@ -37,6 +37,7 @@
<!-- Don't forget to update status.xml too! -->
<release version="3.1.1-alpha1" date="2008-??-??">
<action dev="POI-DEVELOPERS" type="add">Initial support for evaluating external add-in functions like YEARFRAC</action>
<action dev="POI-DEVELOPERS" type="fix">45672 - Fix for MissingRecordAwareHSSFListener to prevent multiple LastCellOfRowDummyRecords when shared formulas are present</action>
<action dev="POI-DEVELOPERS" type="fix">45645 - Fix for HSSFSheet.autoSizeColumn() for widths exceeding Short.MAX_VALUE</action>
<action dev="POI-DEVELOPERS" type="add">45623 - Support for additional HSSF header and footer fields, including bold and full file path</action>

View File

@ -34,6 +34,7 @@
<!-- Don't forget to update changes.xml too! -->
<changes>
<release version="3.1.1-alpha1" date="2008-??-??">
<action dev="POI-DEVELOPERS" type="add">Initial support for evaluating external add-in functions like YEARFRAC</action>
<action dev="POI-DEVELOPERS" type="fix">45672 - Fix for MissingRecordAwareHSSFListener to prevent multiple LastCellOfRowDummyRecords when shared formulas are present</action>
<action dev="POI-DEVELOPERS" type="fix">45645 - Fix for HSSFSheet.autoSizeColumn() for widths exceeding Short.MAX_VALUE</action>
<action dev="POI-DEVELOPERS" type="add">45623 - Support for additional HSSF header and footer fields, including bold and full file path</action>

View File

@ -150,6 +150,7 @@ public final class SupBookRecord extends Record {
sb.append("Internal References ");
sb.append(" nSheets= ").append(field_1_number_of_sheets);
}
sb.append("]");
return sb.toString();
}
private int getDataSize() {

View File

@ -30,11 +30,11 @@ public final class NameXPtg extends OperandPtg {
private final static int SIZE = 7;
/** index to REF entry in externsheet record */
private int _sheetRefIndex;
private final int _sheetRefIndex;
/** index to defined name or externname table(1 based) */
private int _nameNumber;
private final int _nameNumber;
/** reserved must be 0 */
private int _reserved;
private final int _reserved;
private NameXPtg(int sheetRefIndex, int nameNumber, int reserved) {
_sheetRefIndex = sheetRefIndex;
@ -73,4 +73,11 @@ public final class NameXPtg extends OperandPtg {
public byte getDefaultOperandClass() {
return Ptg.CLASS_VALUE;
}
public int getSheetRefIndex() {
return _sheetRefIndex;
}
public int getNameIndex() {
return _nameNumber - 1;
}
}

View File

@ -0,0 +1,154 @@
/* ====================================================================
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
==================================================================== */
package org.apache.poi.hssf.record.formula.atp;
import java.util.HashMap;
import java.util.Map;
import org.apache.poi.hssf.record.formula.eval.ErrorEval;
import org.apache.poi.hssf.record.formula.eval.Eval;
import org.apache.poi.hssf.record.formula.eval.ValueEval;
import org.apache.poi.hssf.record.formula.functions.FreeRefFunction;
import org.apache.poi.hssf.usermodel.HSSFSheet;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
public final class AnalysisToolPak {
private static final FreeRefFunction NotImplemented = new FreeRefFunction() {
public ValueEval evaluate(Eval[] args, int srcCellRow, short srcCellCol,
HSSFWorkbook workbook, HSSFSheet sheet) {
return ErrorEval.FUNCTION_NOT_IMPLEMENTED;
}
};
private static Map _functionsByName = createFunctionsMap();
private AnalysisToolPak() {
// no instances of this class
}
public static FreeRefFunction findFunction(String name) {
return (FreeRefFunction)_functionsByName.get(name);
}
private static Map createFunctionsMap() {
Map m = new HashMap(100);
r(m, "ACCRINT", null);
r(m, "ACCRINTM", null);
r(m, "AMORDEGRC", null);
r(m, "AMORLINC", null);
r(m, "BESSELI", null);
r(m, "BESSELJ", null);
r(m, "BESSELK", null);
r(m, "BESSELY", null);
r(m, "BIN2DEC", null);
r(m, "BIN2HEX", null);
r(m, "BIN2OCT", null);
r(m, "CO MPLEX", null);
r(m, "CONVERT", null);
r(m, "COUPDAYBS", null);
r(m, "COUPDAYS", null);
r(m, "COUPDAYSNC", null);
r(m, "COUPNCD", null);
r(m, "COUPNUM", null);
r(m, "COUPPCD", null);
r(m, "CUMIPMT", null);
r(m, "CUMPRINC", null);
r(m, "DEC2BIN", null);
r(m, "DEC2HEX", null);
r(m, "DEC2OCT", null);
r(m, "DELTA", null);
r(m, "DISC", null);
r(m, "DOLLARDE", null);
r(m, "DOLLARFR", null);
r(m, "DURATION", null);
r(m, "EDATE", null);
r(m, "EFFECT", null);
r(m, "EOMONTH", null);
r(m, "ERF", null);
r(m, "ERFC", null);
r(m, "FACTDOUBLE", null);
r(m, "FVSCHEDULE", null);
r(m, "GCD", null);
r(m, "GESTEP", null);
r(m, "HEX2BIN", null);
r(m, "HEX2DEC", null);
r(m, "HEX2OCT", null);
r(m, "IMABS", null);
r(m, "IMAGINARY", null);
r(m, "IMARGUMENT", null);
r(m, "IMCONJUGATE", null);
r(m, "IMCOS", null);
r(m, "IMDIV", null);
r(m, "IMEXP", null);
r(m, "IMLN", null);
r(m, "IMLOG10", null);
r(m, "IMLOG2", null);
r(m, "IMPOWER", null);
r(m, "IMPRODUCT", null);
r(m, "IMREAL", null);
r(m, "IMSIN", null);
r(m, "IMSQRT", null);
r(m, "IMSUB", null);
r(m, "IMSUM", null);
r(m, "INTRATE", null);
r(m, "ISEVEN", null);
r(m, "ISODD", null);
r(m, "LCM", null);
r(m, "MDURATION", null);
r(m, "MROUND", null);
r(m, "MULTINOMIAL", null);
r(m, "NETWORKDAYS", null);
r(m, "NOMINAL", null);
r(m, "OCT2BIN", null);
r(m, "OCT2DEC", null);
r(m, "OCT2HEX", null);
r(m, "ODDFPRICE", null);
r(m, "ODDFYIELD", null);
r(m, "ODDLPRICE", null);
r(m, "ODDLYIELD", null);
r(m, "PRICE", null);
r(m, "PRICEDISC", null);
r(m, "PRICEMAT", null);
r(m, "QUOTIENT", null);
r(m, "RAND BETWEEN", null);
r(m, "RECEIVED", null);
r(m, "SERIESSUM", null);
r(m, "SQRTPI", null);
r(m, "TBILLEQ", null);
r(m, "TBILLPRICE", null);
r(m, "TBILLYIELD", null);
r(m, "WEEKNUM", null);
r(m, "WORKDAY", null);
r(m, "XIRR", null);
r(m, "XNPV", null);
r(m, "YEARFRAC", YearFrac.instance);
r(m, "YIELD", null);
r(m, "YIELDDISC", null);
r(m, "YIELDMAT", null);
return m;
}
private static void r(Map m, String functionName, FreeRefFunction pFunc) {
FreeRefFunction func = pFunc == null ? NotImplemented : pFunc;
m.put(functionName, func);
}
}

View File

@ -0,0 +1,160 @@
/* ====================================================================
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
==================================================================== */
package org.apache.poi.hssf.record.formula.atp;
import java.util.Calendar;
import java.util.GregorianCalendar;
import java.util.regex.Pattern;
import org.apache.poi.hssf.record.formula.eval.ErrorEval;
import org.apache.poi.hssf.record.formula.eval.Eval;
import org.apache.poi.hssf.record.formula.eval.EvaluationException;
import org.apache.poi.hssf.record.formula.eval.NumberEval;
import org.apache.poi.hssf.record.formula.eval.OperandResolver;
import org.apache.poi.hssf.record.formula.eval.StringEval;
import org.apache.poi.hssf.record.formula.eval.ValueEval;
import org.apache.poi.hssf.record.formula.functions.FreeRefFunction;
import org.apache.poi.hssf.usermodel.HSSFDateUtil;
import org.apache.poi.hssf.usermodel.HSSFSheet;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
/**
* Implementation of Excel 'Analysis ToolPak' function YEARFRAC()<br/>
*
* Returns the fraction of the year spanned by two dates.<p/>
*
* <b>Syntax</b><br/>
* <b>YEARFRAC</b>(<b>startDate</b>, <b>endDate</b>, basis)<p/>
*
* The <b>basis</b> optionally specifies the behaviour of YEARFRAC as follows:
*
* <table border="0" cellpadding="1" cellspacing="0" summary="basis parameter description">
* <tr><th>Value</th><th>Days per Month</th><th>Days per Year</th></tr>
* <tr align='center'><td>0 (default)</td><td>30</td><td>360</td></tr>
* <tr align='center'><td>1</td><td>actual</td><td>actual</td></tr>
* <tr align='center'><td>2</td><td>actual</td><td>360</td></tr>
* <tr align='center'><td>3</td><td>actual</td><td>365</td></tr>
* <tr align='center'><td>4</td><td>30</td><td>360</td></tr>
* </table>
*
*/
final class YearFrac implements FreeRefFunction {
public static final FreeRefFunction instance = new YearFrac();
private YearFrac() {
// enforce singleton
}
public ValueEval evaluate(Eval[] args, int srcCellRow, short srcCellCol, HSSFWorkbook workbook,
HSSFSheet sheet) {
double result;
try {
int basis = 0; // default
switch(args.length) {
case 3:
basis = evaluateIntArg(args[2], srcCellRow, srcCellCol);
case 2:
break;
default:
return ErrorEval.VALUE_INVALID;
}
double startDateVal = evaluateDateArg(args[0], srcCellRow, srcCellCol);
double endDateVal = evaluateDateArg(args[1], srcCellRow, srcCellCol);
result = YearFracCalculator.calculate(startDateVal, endDateVal, basis);
} catch (EvaluationException e) {
return e.getErrorEval();
}
return new NumberEval(result);
}
private static double evaluateDateArg(Eval arg, int srcCellRow, short srcCellCol) throws EvaluationException {
ValueEval ve = OperandResolver.getSingleValue(arg, srcCellRow, srcCellCol);
if (ve instanceof StringEval) {
String strVal = ((StringEval) ve).getStringValue();
Double dVal = OperandResolver.parseDouble(strVal);
if (dVal != null) {
return dVal.doubleValue();
}
Calendar date = parseDate(strVal);
return HSSFDateUtil.getExcelDate(date, false);
}
return OperandResolver.coerceValueToDouble(ve);
}
private static Calendar parseDate(String strVal) throws EvaluationException {
String[] parts = Pattern.compile("/").split(strVal);
if (parts.length != 3) {
throw new EvaluationException(ErrorEval.VALUE_INVALID);
}
String part2 = parts[2];
int spacePos = part2.indexOf(' ');
if (spacePos > 0) {
// drop time portion if present
part2 = part2.substring(0, spacePos);
}
int f0;
int f1;
int f2;
try {
f0 = Integer.parseInt(parts[0]);
f1 = Integer.parseInt(parts[1]);
f2 = Integer.parseInt(part2);
} catch (NumberFormatException e) {
throw new EvaluationException(ErrorEval.VALUE_INVALID);
}
if (f0<0 || f1<0 || f2<0 || f0>12 || f1>12 || f2>12) {
// easy to see this cannot be a valid date
throw new EvaluationException(ErrorEval.VALUE_INVALID);
}
if (f0 >= 1900 && f0 < 9999) {
// when 4 digit value appears first, the format is YYYY/MM/DD, regardless of OS settings
return makeDate(f0, f1, f2);
}
// otherwise the format seems to depend on OS settings (default date format)
if (false) {
// MM/DD/YYYY is probably a good guess, if the in the US
return makeDate(f2, f0, f1);
}
// TODO - find a way to choose the correct date format
throw new RuntimeException("Unable to determine date format for text '" + strVal + "'");
}
/**
* @param month 1-based
*/
private static Calendar makeDate(int year, int month, int day) throws EvaluationException {
if (month < 1 || month > 12) {
throw new EvaluationException(ErrorEval.VALUE_INVALID);
}
Calendar cal = new GregorianCalendar(year, month-1, 1, 0, 0, 0);
cal.set(Calendar.MILLISECOND, 0);
if (day <1 || day>cal.getActualMaximum(Calendar.DAY_OF_MONTH)) {
throw new EvaluationException(ErrorEval.VALUE_INVALID);
}
return cal;
}
private static int evaluateIntArg(Eval arg, int srcCellRow, short srcCellCol) throws EvaluationException {
ValueEval ve = OperandResolver.getSingleValue(arg, srcCellRow, srcCellCol);
return OperandResolver.coerceValueToInt(ve);
}
}

View File

@ -0,0 +1,344 @@
/* ====================================================================
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
==================================================================== */
package org.apache.poi.hssf.record.formula.atp;
import java.util.Calendar;
import java.util.GregorianCalendar;
import java.util.TimeZone;
import org.apache.poi.hssf.record.formula.eval.ErrorEval;
import org.apache.poi.hssf.record.formula.eval.EvaluationException;
import org.apache.poi.hssf.usermodel.HSSFDateUtil;
/**
* Internal calculation methods for Excel 'Analysis ToolPak' function YEARFRAC()<br/>
*
* Algorithm inspired by www.dwheeler.com/yearfrac
*
* @author Josh Micich
*/
final class YearFracCalculator {
/** use UTC time-zone to avoid daylight savings issues */
private static final TimeZone UTC_TIME_ZONE = TimeZone.getTimeZone("UTC");
private static final int MS_PER_HOUR = 60 * 60 * 1000;
private static final int MS_PER_DAY = 24 * MS_PER_HOUR;
private static final int DAYS_PER_NORMAL_YEAR = 365;
private static final int DAYS_PER_LEAP_YEAR = DAYS_PER_NORMAL_YEAR + 1;
/** the length of normal long months i.e. 31 */
private static final int LONG_MONTH_LEN = 31;
/** the length of normal short months i.e. 30 */
private static final int SHORT_MONTH_LEN = 30;
private static final int SHORT_FEB_LEN = 28;
private static final int LONG_FEB_LEN = SHORT_FEB_LEN + 1;
private YearFracCalculator() {
// no instances of this class
}
public static double calculate(double pStartDateVal, double pEndDateVal, int basis) throws EvaluationException {
if (basis < 0 || basis >= 5) {
// if basis is invalid the result is #NUM!
throw new EvaluationException(ErrorEval.NUM_ERROR);
}
// common logic for all bases
// truncate day values
int startDateVal = (int) Math.floor(pStartDateVal);
int endDateVal = (int) Math.floor(pEndDateVal);
if (startDateVal == endDateVal) {
// when dates are equal, result is zero
return 0;
}
// swap start and end if out of order
if (startDateVal > endDateVal) {
int temp = startDateVal;
startDateVal = endDateVal;
endDateVal = temp;
}
switch (basis) {
case 0: return basis0(startDateVal, endDateVal);
case 1: return basis1(startDateVal, endDateVal);
case 2: return basis2(startDateVal, endDateVal);
case 3: return basis3(startDateVal, endDateVal);
case 4: return basis4(startDateVal, endDateVal);
}
throw new IllegalStateException("cannot happen");
}
/**
* @param startDateVal assumed to be less than or equal to endDateVal
* @param endDateVal assumed to be greater than or equal to startDateVal
*/
public static double basis0(int startDateVal, int endDateVal) {
SimpleDate startDate = createDate(startDateVal);
SimpleDate endDate = createDate(endDateVal);
int date1day = startDate.day;
int date2day = endDate.day;
// basis zero has funny adjustments to the day-of-month fields when at end-of-month
if (date1day == LONG_MONTH_LEN && date2day == LONG_MONTH_LEN) {
date1day = SHORT_MONTH_LEN;
date2day = SHORT_MONTH_LEN;
} else if (date1day == LONG_MONTH_LEN) {
date1day = SHORT_MONTH_LEN;
} else if (date1day == SHORT_MONTH_LEN && date2day == LONG_MONTH_LEN) {
date2day = SHORT_MONTH_LEN;
// Note: If date2day==31, it STAYS 31 if date1day < 30.
// Special fixes for February:
} else if (startDate.month == 2 && isLastDayOfMonth(startDate)) {
// Note - these assignments deliberately set Feb 30 date.
date1day = SHORT_MONTH_LEN;
if (endDate.month == 2 && isLastDayOfMonth(endDate)) {
// only adjusted when first date is last day in Feb
date2day = SHORT_MONTH_LEN;
}
}
return calculateAdjusted(startDate, endDate, date1day, date2day);
}
/**
* @param startDateVal assumed to be less than or equal to endDateVal
* @param endDateVal assumed to be greater than or equal to startDateVal
*/
public static double basis1(int startDateVal, int endDateVal) {
SimpleDate startDate = createDate(startDateVal);
SimpleDate endDate = createDate(endDateVal);
double yearLength;
if (isGreaterThanOneYear(startDate, endDate)) {
yearLength = averageYearLength(startDate.year, endDate.year);
} else if (shouldCountFeb29(startDate, endDate)) {
yearLength = DAYS_PER_LEAP_YEAR;
} else {
yearLength = DAYS_PER_NORMAL_YEAR;
}
return dateDiff(startDate.tsMilliseconds, endDate.tsMilliseconds) / yearLength;
}
/**
* @param startDateVal assumed to be less than or equal to endDateVal
* @param endDateVal assumed to be greater than or equal to startDateVal
*/
public static double basis2(int startDateVal, int endDateVal) {
return (endDateVal - startDateVal) / 360.0;
}
/**
* @param startDateVal assumed to be less than or equal to endDateVal
* @param endDateVal assumed to be greater than or equal to startDateVal
*/
public static double basis3(double startDateVal, double endDateVal) {
return (endDateVal - startDateVal) / 365.0;
}
/**
* @param startDateVal assumed to be less than or equal to endDateVal
* @param endDateVal assumed to be greater than or equal to startDateVal
*/
public static double basis4(int startDateVal, int endDateVal) {
SimpleDate startDate = createDate(startDateVal);
SimpleDate endDate = createDate(endDateVal);
int date1day = startDate.day;
int date2day = endDate.day;
// basis four has funny adjustments to the day-of-month fields when at end-of-month
if (date1day == LONG_MONTH_LEN) {
date1day = SHORT_MONTH_LEN;
}
if (date2day == LONG_MONTH_LEN) {
date2day = SHORT_MONTH_LEN;
}
// Note - no adjustments for end of Feb
return calculateAdjusted(startDate, endDate, date1day, date2day);
}
private static double calculateAdjusted(SimpleDate startDate, SimpleDate endDate, int date1day,
int date2day) {
double dayCount
= (endDate.year - startDate.year) * 360
+ (endDate.month - startDate.month) * SHORT_MONTH_LEN
+ (date2day - date1day) * 1;
return dayCount / 360;
}
private static boolean isLastDayOfMonth(SimpleDate date) {
if (date.day < SHORT_FEB_LEN) {
return false;
}
return date.day == getLastDayOfMonth(date);
}
private static int getLastDayOfMonth(SimpleDate date) {
switch (date.month) {
case 1:
case 3:
case 5:
case 7:
case 8:
case 10:
case 12:
return LONG_MONTH_LEN;
case 4:
case 6:
case 9:
case 11:
return SHORT_MONTH_LEN;
}
if (isLeapYear(date.year)) {
return LONG_FEB_LEN;
}
return SHORT_FEB_LEN;
}
/**
* Assumes dates are no more than 1 year apart.
* @return <code>true</code> if dates both within a leap year, or span a period including Feb 29
*/
private static boolean shouldCountFeb29(SimpleDate start, SimpleDate end) {
boolean startIsLeapYear = isLeapYear(start.year);
if (startIsLeapYear && start.year == end.year) {
// note - dates may not actually span Feb-29, but it gets counted anyway in this case
return true;
}
boolean endIsLeapYear = isLeapYear(end.year);
if (!startIsLeapYear && !endIsLeapYear) {
return false;
}
if (startIsLeapYear) {
switch (start.month) {
case SimpleDate.JANUARY:
case SimpleDate.FEBRUARY:
return true;
}
return false;
}
if (endIsLeapYear) {
switch (end.month) {
case SimpleDate.JANUARY:
return false;
case SimpleDate.FEBRUARY:
break;
default:
return true;
}
return end.day == LONG_FEB_LEN;
}
return false;
}
/**
* @return the whole number of days between the two time-stamps. Both time-stamps are
* assumed to represent 12:00 midnight on the respective day.
*/
private static int dateDiff(long startDateMS, long endDateMS) {
long msDiff = endDateMS - startDateMS;
// some extra checks to make sure we don't hide some other bug with the rounding
int remainderHours = (int) ((msDiff % MS_PER_DAY) / MS_PER_HOUR);
switch (remainderHours) {
case 0: // normal case
break;
case 1: // transition from normal time to daylight savings adjusted
case 23: // transition from daylight savings adjusted to normal time
// Unexpected since we are using UTC_TIME_ZONE
default:
throw new RuntimeException("Unexpected date diff between " + startDateMS + " and " + endDateMS);
}
return (int) (0.5 + ((double)msDiff / MS_PER_DAY));
}
private static double averageYearLength(int startYear, int endYear) {
int dayCount = 0;
for (int i=startYear; i<=endYear; i++) {
dayCount += DAYS_PER_NORMAL_YEAR;
if (isLeapYear(i)) {
dayCount++;
}
}
double numberOfYears = endYear-startYear+1;
return dayCount / numberOfYears;
}
private static boolean isLeapYear(int i) {
// leap years are always divisible by 4
if (i % 4 != 0) {
return false;
}
// each 4th century is a leap year
if (i % 400 == 0) {
return true;
}
// all other centuries are *not* leap years
if (i % 100 == 0) {
return false;
}
return true;
}
private static boolean isGreaterThanOneYear(SimpleDate start, SimpleDate end) {
if (start.year == end.year) {
return false;
}
if (start.year + 1 != end.year) {
return true;
}
if (start.month > end.month) {
return false;
}
if (start.month < end.month) {
return true;
}
return start.day < end.day;
}
private static SimpleDate createDate(int dayCount) {
GregorianCalendar calendar = new GregorianCalendar(UTC_TIME_ZONE);
HSSFDateUtil.setCalendar(calendar, dayCount, 0, false);
return new SimpleDate(calendar);
}
private static final class SimpleDate {
public static final int JANUARY = 1;
public static final int FEBRUARY = 2;
public final int year;
/** 1-based month */
public final int month;
/** day of month */
public final int day;
/** milliseconds since 1970 */
public long tsMilliseconds;
public SimpleDate(Calendar cal) {
year = cal.get(Calendar.YEAR);
month = cal.get(Calendar.MONTH) + 1;
day = cal.get(Calendar.DAY_OF_MONTH);
tsMilliseconds = cal.getTimeInMillis();
}
}
}

View File

@ -17,14 +17,16 @@
package org.apache.poi.hssf.record.formula.eval;
import org.apache.poi.hssf.record.formula.atp.AnalysisToolPak;
import org.apache.poi.hssf.record.formula.functions.FreeRefFunction;
import org.apache.poi.hssf.usermodel.HSSFSheet;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
/**
*
* Common entry point for all external functions (where
* Common entry point for all user-defined (non-built-in) functions (where
* <tt>AbstractFunctionPtg.field_2_fnc_index</tt> == 255)
*
* TODO rename to UserDefinedFunction
* @author Josh Micich
*/
final class ExternalFunction implements FreeRefFunction {
@ -36,27 +38,43 @@ final class ExternalFunction implements FreeRefFunction {
throw new RuntimeException("function name argument missing");
}
if (!(args[0] instanceof NameEval)) {
throw new RuntimeException("First argument should be a NameEval, but got ("
+ args[0].getClass().getName() + ")");
}
NameEval functionNameEval = (NameEval) args[0];
int nOutGoingArgs = nIncomingArgs -1;
Eval[] outGoingArgs = new Eval[nOutGoingArgs];
System.arraycopy(args, 1, outGoingArgs, 0, nOutGoingArgs);
Eval nameArg = args[0];
FreeRefFunction targetFunc;
try {
targetFunc = findTargetFunction(workbook, functionNameEval);
if (nameArg instanceof NameEval) {
targetFunc = findInternalUserDefinedFunction(workbook, (NameEval) nameArg);
} else if (nameArg instanceof NameXEval) {
targetFunc = findExternalUserDefinedFunction(workbook, (NameXEval) nameArg);
} else {
throw new RuntimeException("First argument should be a NameEval, but got ("
+ nameArg.getClass().getName() + ")");
}
} catch (EvaluationException e) {
return e.getErrorEval();
}
int nOutGoingArgs = nIncomingArgs -1;
Eval[] outGoingArgs = new Eval[nOutGoingArgs];
System.arraycopy(args, 1, outGoingArgs, 0, nOutGoingArgs);
return targetFunc.evaluate(outGoingArgs, srcCellRow, srcCellCol, workbook, sheet);
}
private FreeRefFunction findTargetFunction(HSSFWorkbook workbook, NameEval functionNameEval) throws EvaluationException {
private FreeRefFunction findExternalUserDefinedFunction(HSSFWorkbook workbook,
NameXEval n) throws EvaluationException {
String functionName = workbook.resolveNameXText(n.getSheetRefIndex(), n.getNameNumber());
if(false) {
System.out.println("received call to external user defined function (" + functionName + ")");
}
// currently only looking for functions from the 'Analysis TookPak'
// not sure how much this logic would need to change to support other or multiple add-ins.
FreeRefFunction result = AnalysisToolPak.findFunction(functionName);
if (result != null) {
return result;
}
throw new EvaluationException(ErrorEval.FUNCTION_NOT_IMPLEMENTED);
}
private FreeRefFunction findInternalUserDefinedFunction(HSSFWorkbook workbook, NameEval functionNameEval) throws EvaluationException {
int numberOfNames = workbook.getNumberOfNames();
@ -68,7 +86,7 @@ final class ExternalFunction implements FreeRefFunction {
String functionName = workbook.getNameName(nameIndex);
if(false) {
System.out.println("received call to external function index (" + functionName + ")");
System.out.println("received call to internal user defined function (" + functionName + ")");
}
// TODO - detect if the NameRecord corresponds to a named range, function, or something undefined
// throw the right errors in these cases
@ -77,5 +95,5 @@ final class ExternalFunction implements FreeRefFunction {
throw new EvaluationException(ErrorEval.FUNCTION_NOT_IMPLEMENTED);
}
}

View File

@ -0,0 +1,49 @@
/* ====================================================================
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
==================================================================== */
package org.apache.poi.hssf.record.formula.eval;
/**
* @author Josh Micich
*/
public final class NameXEval implements Eval {
/** index to REF entry in externsheet record */
private final int _sheetRefIndex;
/** index to defined name or externname table(1 based) */
private final int _nameNumber;
public NameXEval(int sheetRefIndex, int nameNumber) {
_sheetRefIndex = sheetRefIndex;
_nameNumber = nameNumber;
}
public int getSheetRefIndex() {
return _sheetRefIndex;
}
public int getNameNumber() {
return _nameNumber;
}
public String toString() {
StringBuffer sb = new StringBuffer(64);
sb.append(getClass().getName()).append(" [");
sb.append(_sheetRefIndex).append(", ").append(_nameNumber);
sb.append("]");
return sb.toString();
}
}

View File

@ -158,9 +158,16 @@ public final class HSSFDateUtil {
if (!isValidExcelDate(date)) {
return null;
}
int wholeDays = (int)Math.floor(date);
int millisecondsInDay = (int)((date - wholeDays) * DAY_MILLISECONDS + 0.5);
Calendar calendar = new GregorianCalendar(); // using default time-zone
setCalendar(calendar, wholeDays, millisecondsInDay, use1904windowing);
return calendar.getTime();
}
public static void setCalendar(Calendar calendar, int wholeDays, int millisecondsInDay,
boolean use1904windowing) {
int startYear = 1900;
int dayAdjust = -1; // Excel thinks 2/29/1900 is a valid date, which it isn't
int wholeDays = (int)Math.floor(date);
if (use1904windowing) {
startYear = 1904;
dayAdjust = 1; // 1904 date windowing uses 1/2/1904 as the first day
@ -170,12 +177,8 @@ public final class HSSFDateUtil {
// If Excel date == 2/29/1900, will become 3/1/1900 in Java representation
dayAdjust = 0;
}
GregorianCalendar calendar = new GregorianCalendar(startYear,0,
wholeDays + dayAdjust);
int millisecondsInDay = (int)((date - Math.floor(date)) *
DAY_MILLISECONDS + 0.5);
calendar.set(startYear,0, wholeDays + dayAdjust, 0, 0, 0);
calendar.set(GregorianCalendar.MILLISECOND, millisecondsInDay);
return calendar.getTime();
}
/**

View File

@ -51,6 +51,7 @@ import org.apache.poi.hssf.record.formula.eval.ErrorEval;
import org.apache.poi.hssf.record.formula.eval.Eval;
import org.apache.poi.hssf.record.formula.eval.FunctionEval;
import org.apache.poi.hssf.record.formula.eval.NameEval;
import org.apache.poi.hssf.record.formula.eval.NameXEval;
import org.apache.poi.hssf.record.formula.eval.NumberEval;
import org.apache.poi.hssf.record.formula.eval.OperationEval;
import org.apache.poi.hssf.record.formula.eval.Ref2DEval;
@ -61,10 +62,10 @@ import org.apache.poi.hssf.record.formula.eval.ValueEval;
/**
* @author Amol S. Deshmukh &lt; amolweb at ya hoo dot com &gt;
*
*
*/
public class HSSFFormulaEvaluator {
// params to lookup the right constructor using reflection
private static final Class[] VALUE_CONTRUCTOR_CLASS_ARRAY = new Class[] { Ptg.class };
@ -78,8 +79,8 @@ public class HSSFFormulaEvaluator {
private static final Map VALUE_EVALS_MAP = new HashMap();
/*
* Following is the mapping between the Ptg tokens returned
* by the FormulaParser and the *Eval classes that are used
* Following is the mapping between the Ptg tokens returned
* by the FormulaParser and the *Eval classes that are used
* by the FormulaEvaluator
*/
static {
@ -90,15 +91,15 @@ public class HSSFFormulaEvaluator {
}
protected HSSFSheet _sheet;
protected HSSFWorkbook _workbook;
public HSSFFormulaEvaluator(HSSFSheet sheet, HSSFWorkbook workbook) {
_sheet = sheet;
_workbook = workbook;
}
/**
* Does nothing
* @deprecated - not needed, since the current row can be derived from the cell
@ -107,24 +108,24 @@ public class HSSFFormulaEvaluator {
// do nothing
}
/**
* Returns an underlying FormulaParser, for the specified
* Formula String and HSSFWorkbook.
* This will allow you to generate the Ptgs yourself, if
* your needs are more complex than just having the
* formula evaluated.
* formula evaluated.
*/
public static FormulaParser getUnderlyingParser(HSSFWorkbook workbook, String formula) {
return new FormulaParser(formula, workbook);
}
/**
* If cell contains a formula, the formula is evaluated and returned,
* else the CellValue simply copies the appropriate cell value from
* the cell and also its cell type. This method should be preferred over
* evaluateInCell() when the call should not modify the contents of the
* original cell.
* original cell.
* @param cell
*/
public CellValue evaluate(HSSFCell cell) {
@ -157,17 +158,17 @@ public class HSSFFormulaEvaluator {
}
return retval;
}
/**
* If cell contains formula, it evaluates the formula,
* and saves the result of the formula. The cell
* remains as a formula cell.
* Else if cell does not contain formula, this method leaves
* the cell unchanged.
* the cell unchanged.
* Note that the type of the formula result is returned,
* so you know what kind of value is also stored with
* the formula.
* the formula.
* <pre>
* int evaluatedCellType = evaluator.evaluateFormulaCell(cell);
* </pre>
@ -205,14 +206,14 @@ public class HSSFFormulaEvaluator {
}
return -1;
}
/**
* If cell contains formula, it evaluates the formula, and
* puts the formula result back into the cell, in place
* of the old formula.
* Else if cell does not contain formula, this method leaves
* the cell unchanged.
* Note that the same instance of HSSFCell is returned to
* the cell unchanged.
* Note that the same instance of HSSFCell is returned to
* allow chained calls like:
* <pre>
* int evaluatedCellType = evaluator.evaluateInCell(cell).getCellType();
@ -252,7 +253,7 @@ public class HSSFFormulaEvaluator {
}
return cell;
}
/**
* Loops over all cells in all sheets of the supplied
* workbook.
@ -261,7 +262,7 @@ public class HSSFFormulaEvaluator {
* remain as formula cells.
* For cells that do not contain formulas, no changes
* are made.
* This is a helpful wrapper around looping over all
* This is a helpful wrapper around looping over all
* cells, and calling evaluateFormulaCell on each one.
*/
public static void evaluateAllFormulaCells(HSSFWorkbook wb) {
@ -280,8 +281,8 @@ public class HSSFFormulaEvaluator {
}
}
}
/**
* Returns a CellValue wrapper around the supplied ValueEval instance.
* @param eval
@ -318,19 +319,19 @@ public class HSSFFormulaEvaluator {
}
return retval;
}
/**
* Dev. Note: Internal evaluate must be passed only a formula cell
* Dev. Note: Internal evaluate must be passed only a formula cell
* else a runtime exception will be thrown somewhere inside the method.
* (Hence this is a private method.)
*/
private static ValueEval internalEvaluate(HSSFCell srcCell, HSSFSheet sheet, HSSFWorkbook workbook) {
int srcRowNum = srcCell.getRowIndex();
short srcColNum = srcCell.getCellNum();
EvaluationCycleDetector tracker = EvaluationCycleDetectorManager.getTracker();
if(!tracker.startEvaluate(workbook, sheet, srcRowNum, srcColNum)) {
return ErrorEval.CIRCULAR_REF_ERROR;
}
@ -340,7 +341,7 @@ public class HSSFFormulaEvaluator {
tracker.endEvaluate(workbook, sheet, srcRowNum, srcColNum);
}
}
private static ValueEval evaluateCell(HSSFWorkbook workbook, HSSFSheet sheet,
private static ValueEval evaluateCell(HSSFWorkbook workbook, HSSFSheet sheet,
int srcRowNum, short srcColNum, String cellFormulaText) {
Ptg[] ptgs = FormulaParser.parse(cellFormulaText, workbook);
@ -350,20 +351,21 @@ public class HSSFFormulaEvaluator {
// since we don't know how to handle these yet :(
Ptg ptg = ptgs[i];
if (ptg instanceof ControlPtg) {
if (ptg instanceof ControlPtg) {
// skip Parentheses, Attr, etc
continue;
continue;
}
if (ptg instanceof MemErrPtg) { continue; }
if (ptg instanceof MissingArgPtg) { continue; }
if (ptg instanceof NamePtg) {
if (ptg instanceof NamePtg) {
// named ranges, macro functions
NamePtg namePtg = (NamePtg) ptg;
stack.push(new NameEval(namePtg.getIndex()));
continue;
continue;
}
if (ptg instanceof NameXPtg) {
// TODO - external functions
NameXPtg nameXPtg = (NameXPtg) ptg;
stack.push(new NameXEval(nameXPtg.getSheetRefIndex(), nameXPtg.getNameIndex()));
continue;
}
if (ptg instanceof UnknownPtg) { continue; }
@ -426,9 +428,9 @@ public class HSSFFormulaEvaluator {
}
value = dereferenceValue(value, srcRowNum, srcColNum);
if (value instanceof BlankEval) {
// Note Excel behaviour here. A blank final final value is converted to zero.
// Note Excel behaviour here. A blank final final value is converted to zero.
return NumberEval.ZERO;
// Formulas _never_ evaluate to blank. If a formula appears to have evaluated to
// Formulas _never_ evaluate to blank. If a formula appears to have evaluated to
// blank, the actual value is empty string. This can be verified with ISBLANK().
}
return value;
@ -472,13 +474,13 @@ public class HSSFFormulaEvaluator {
}
return operation.evaluate(ops, srcRowNum, srcColNum);
}
public static AreaEval evaluateAreaPtg(HSSFSheet sheet, HSSFWorkbook workbook, AreaPtg ap) {
int row0 = ap.getFirstRow();
int col0 = ap.getFirstColumn();
int row1 = ap.getLastRow();
int col1 = ap.getLastColumn();
// If the last row is -1, then the
// reference is for the rest of the column
// (eg C:C)
@ -497,7 +499,7 @@ public class HSSFFormulaEvaluator {
int col1 = a3dp.getLastColumn();
Workbook wb = workbook.getWorkbook();
HSSFSheet xsheet = workbook.getSheetAt(wb.getSheetIndexFromExternSheetIndex(a3dp.getExternSheetIndex()));
// If the last row is -1, then the
// reference is for the rest of the column
// (eg C:C)
@ -505,12 +507,12 @@ public class HSSFFormulaEvaluator {
if(row1 == -1 && row0 >= 0) {
row1 = (short)xsheet.getLastRowNum();
}
ValueEval[] values = evalArea(workbook, xsheet, row0, col0, row1, col1);
return new Area3DEval(a3dp, values);
}
private static ValueEval[] evalArea(HSSFWorkbook workbook, HSSFSheet sheet,
private static ValueEval[] evalArea(HSSFWorkbook workbook, HSSFSheet sheet,
int row0, int col0, int row1, int col1) {
ValueEval[] values = new ValueEval[(row1 - row0 + 1) * (col1 - col0 + 1)];
for (int x = row0; sheet != null && x < row1 + 1; x++) {
@ -533,7 +535,7 @@ public class HSSFFormulaEvaluator {
* one of: Area3DPtg, AreaPtg, ReferencePtg, Ref3DPtg, IntPtg, NumberPtg,
* StringPtg, BoolPtg <br/>special Note: OperationPtg subtypes cannot be
* passed here!
*
*
* @param ptg
*/
protected static Eval getEvalForPtg(Ptg ptg) {
@ -607,12 +609,12 @@ public class HSSFFormulaEvaluator {
* Creates a Ref2DEval for ReferencePtg.
* Non existent cells are treated as RefEvals containing BlankEval.
*/
private static Ref2DEval createRef2DEval(RefPtg ptg, HSSFCell cell,
private static Ref2DEval createRef2DEval(RefPtg ptg, HSSFCell cell,
HSSFSheet sheet, HSSFWorkbook workbook) {
if (cell == null) {
return new Ref2DEval(ptg, BlankEval.INSTANCE);
}
switch (cell.getCellType()) {
case HSSFCell.CELL_TYPE_NUMERIC:
return new Ref2DEval(ptg, new NumberEval(cell.getNumericCellValue()));
@ -633,7 +635,7 @@ public class HSSFFormulaEvaluator {
/**
* create a Ref3DEval for Ref3DPtg.
*/
private static Ref3DEval createRef3DEval(Ref3DPtg ptg, HSSFCell cell,
private static Ref3DEval createRef3DEval(Ref3DPtg ptg, HSSFCell cell,
HSSFSheet sheet, HSSFWorkbook workbook) {
if (cell == null) {
return new Ref3DEval(ptg, BlankEval.INSTANCE);
@ -654,9 +656,9 @@ public class HSSFFormulaEvaluator {
}
throw new RuntimeException("Unexpected cell type (" + cell.getCellType() + ")");
}
/**
* Mimics the 'data view' of a cell. This allows formula evaluator
* Mimics the 'data view' of a cell. This allows formula evaluator
* to return a CellValue instead of precasting the value to String
* or Number or boolean type.
* @author Amol S. Deshmukh &lt; amolweb at ya hoo dot com &gt;
@ -667,7 +669,7 @@ public class HSSFFormulaEvaluator {
private double numberValue;
private boolean booleanValue;
private byte errorValue;
/**
* CellType should be one of the types defined in HSSFCell
* @param cellType
@ -750,7 +752,7 @@ public class HSSFFormulaEvaluator {
/**
* debug method
*
*
* @param formula
* @param sheet
* @param workbook
@ -770,5 +772,4 @@ public class HSSFFormulaEvaluator {
}
System.out.println("</ptg-group>");
}
}

View File

@ -71,7 +71,7 @@ public final class TestExternalFunctionFormulas extends TestCase {
}
}
public void DISABLEDtestEvaluate() {
public void testEvaluate() {
HSSFWorkbook wb = HSSFTestDataSamples.openSampleWorkbook("externalFunctionExample.xls");
HSSFSheet sheet = wb.getSheetAt(0);
HSSFCell cell = sheet.getRow(0).getCell(0);

View File

@ -0,0 +1,66 @@
/* ====================================================================
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
==================================================================== */
package org.apache.poi.hssf.record.formula.atp;
import java.util.Calendar;
import java.util.GregorianCalendar;
import junit.framework.TestCase;
import org.apache.poi.hssf.record.formula.eval.EvaluationException;
import org.apache.poi.hssf.usermodel.HSSFDateUtil;
/**
* Specific test cases for YearFracCalculator
*/
public final class TestYearFracCalculator extends TestCase {
public void testBasis1() {
confirm(md(1999, 1, 1), md(1999, 4, 5), 1, 0.257534247);
confirm(md(1999, 4, 1), md(1999, 4, 5), 1, 0.010958904);
confirm(md(1999, 4, 1), md(1999, 4, 4), 1, 0.008219178);
confirm(md(1999, 4, 2), md(1999, 4, 5), 1, 0.008219178);
confirm(md(1999, 3, 31), md(1999, 4, 3), 1, 0.008219178);
confirm(md(1999, 4, 5), md(1999, 4, 8), 1, 0.008219178);
confirm(md(1999, 4, 4), md(1999, 4, 7), 1, 0.008219178);
}
private void confirm(double startDate, double endDate, int basis, double expectedValue) {
double actualValue;
try {
actualValue = YearFracCalculator.calculate(startDate, endDate, basis);
} catch (EvaluationException e) {
throw new RuntimeException(e);
}
double diff = actualValue - expectedValue;
if (Math.abs(diff) > 0.000000001) {
double hours = diff * 365 * 24;
System.out.println(startDate + " " + endDate + " off by " + hours + " hours");
assertEquals(expectedValue, actualValue, 0.000000001);
}
}
private static double md(int year, int month, int day) {
Calendar c = new GregorianCalendar();
c.set(year, month-1, day, 0, 0, 0);
c.set(Calendar.MILLISECOND, 0);
return HSSFDateUtil.getExcelDate(c.getTime());
}
}

View File

@ -0,0 +1,178 @@
/* ====================================================================
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
==================================================================== */
package org.apache.poi.hssf.record.formula.atp;
import java.io.PrintStream;
import java.util.Calendar;
import java.util.GregorianCalendar;
import java.util.Iterator;
import junit.framework.Assert;
import junit.framework.AssertionFailedError;
import junit.framework.ComparisonFailure;
import junit.framework.TestCase;
import org.apache.poi.hssf.HSSFTestDataSamples;
import org.apache.poi.hssf.record.formula.eval.EvaluationException;
import org.apache.poi.hssf.usermodel.HSSFCell;
import org.apache.poi.hssf.usermodel.HSSFDateUtil;
import org.apache.poi.hssf.usermodel.HSSFFormulaEvaluator;
import org.apache.poi.hssf.usermodel.HSSFRow;
import org.apache.poi.hssf.usermodel.HSSFSheet;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
/**
* Tests YearFracCalculator using test-cases listed in a sample spreadsheet
*
* @author Josh Micich
*/
public final class TestYearFracCalculatorFromSpreadsheet extends TestCase {
private static final class SS {
public static final int BASIS_COLUMN = 1; // "B"
public static final int START_YEAR_COLUMN = 2; // "C"
public static final int END_YEAR_COLUMN = 5; // "F"
public static final int YEARFRAC_FORMULA_COLUMN = 11; // "L"
public static final int EXPECTED_RESULT_COLUMN = 13; // "N"
}
public void testAll() {
HSSFWorkbook wb = HSSFTestDataSamples.openSampleWorkbook("yearfracExamples.xls");
HSSFSheet sheet = wb.getSheetAt(0);
HSSFFormulaEvaluator formulaEvaluator = new HSSFFormulaEvaluator(sheet, wb);
int nSuccess = 0;
int nFailures = 0;
int nUnexpectedErrors = 0;
Iterator rowIterator = sheet.rowIterator();
while(rowIterator.hasNext()) {
HSSFRow row = (HSSFRow) rowIterator.next();
HSSFCell cell = row.getCell(SS.YEARFRAC_FORMULA_COLUMN);
if (cell == null || cell.getCellType() != HSSFCell.CELL_TYPE_FORMULA) {
continue;
}
try {
processRow(row, cell, formulaEvaluator);
nSuccess++;
} catch (RuntimeException e) {
nUnexpectedErrors ++;
printShortStackTrace(System.err, e);
} catch (AssertionFailedError e) {
nFailures ++;
printShortStackTrace(System.err, e);
}
}
if (nUnexpectedErrors + nFailures > 0) {
String msg = nFailures + " failures(s) and " + nUnexpectedErrors
+ " unexpected errors(s) occurred. See stderr for details";
throw new AssertionFailedError(msg);
}
if (nSuccess < 1) {
throw new RuntimeException("No test sample cases found");
}
}
private static void processRow(HSSFRow row, HSSFCell cell, HSSFFormulaEvaluator formulaEvaluator) {
double startDate = makeDate(row, SS.START_YEAR_COLUMN);
double endDate = makeDate(row, SS.END_YEAR_COLUMN);
int basis = getIntCell(row, SS.BASIS_COLUMN);
double expectedValue = getDoubleCell(row, SS.EXPECTED_RESULT_COLUMN);
double actualValue;
try {
actualValue = YearFracCalculator.calculate(startDate, endDate, basis);
} catch (EvaluationException e) {
throw new RuntimeException(e);
}
if (expectedValue != actualValue) {
throw new ComparisonFailure("Direct calculate failed - row " + (row.getRowNum()+1),
String.valueOf(expectedValue), String.valueOf(actualValue));
}
actualValue = formulaEvaluator.evaluate(cell).getNumberValue();
if (expectedValue != actualValue) {
throw new ComparisonFailure("Formula evaluate failed - row " + (row.getRowNum()+1),
String.valueOf(expectedValue), String.valueOf(actualValue));
}
}
private static double makeDate(HSSFRow row, int yearColumn) {
int year = getIntCell(row, yearColumn + 0);
int month = getIntCell(row, yearColumn + 1);
int day = getIntCell(row, yearColumn + 2);
Calendar c = new GregorianCalendar(year, month-1, day, 0, 0, 0);
c.set(Calendar.MILLISECOND, 0);
return HSSFDateUtil.getExcelDate(c.getTime());
}
private static int getIntCell(HSSFRow row, int colIx) {
double dVal = getDoubleCell(row, colIx);
if (Math.floor(dVal) != dVal) {
throw new RuntimeException("Non integer value (" + dVal
+ ") cell found at column " + (char)('A' + colIx));
}
return (int)dVal;
}
private static double getDoubleCell(HSSFRow row, int colIx) {
HSSFCell cell = row.getCell(colIx);
if (cell == null) {
throw new RuntimeException("No cell found at column " + colIx);
}
double dVal = cell.getNumericCellValue();
return dVal;
}
/**
* Useful to keep output concise when expecting many failures to be reported by this test case
* TODO - refactor duplicates in other Test~FromSpreadsheet classes
*/
private static void printShortStackTrace(PrintStream ps, Throwable e) {
StackTraceElement[] stes = e.getStackTrace();
int startIx = 0;
// skip any top frames inside junit.framework.Assert
while(startIx<stes.length) {
if(!stes[startIx].getClassName().equals(Assert.class.getName())) {
break;
}
startIx++;
}
// skip bottom frames (part of junit framework)
int endIx = startIx+1;
while(endIx < stes.length) {
if(stes[endIx].getClassName().equals(TestCase.class.getName())) {
break;
}
endIx++;
}
if(startIx >= endIx) {
// something went wrong. just print the whole stack trace
e.printStackTrace(ps);
}
endIx -= 4; // skip 4 frames of reflection invocation
ps.println(e.toString());
for(int i=startIx; i<endIx; i++) {
ps.println("\tat " + stes[i].toString());
}
}
}

View File

@ -16,7 +16,6 @@
limitations under the License.
==================================================================== */
package org.apache.poi.hssf.usermodel;
import java.util.Calendar;
@ -52,9 +51,7 @@ public final class TestHSSFDateUtil extends TestCase {
* Checks the date conversion functions in the HSSFDateUtil class.
*/
public void testDateConversion()
throws Exception
{
public void testDateConversion() {
// Iteratating over the hours exposes any rounding issues.
for (int hour = 0; hour < 23; hour++)
@ -131,7 +128,7 @@ public final class TestHSSFDateUtil extends TestCase {
for (int hour = 0; hour < 24; hour++, excelDate += oneHour) {
// Skip 02:00 CET as that is the Daylight change time
// and Java converts it automatically to 03:00 CEST
// and Java converts it automatically to 03:00 CEST
if (hour == 2) {
continue;
}
@ -186,7 +183,7 @@ public final class TestHSSFDateUtil extends TestCase {
HSSFDateUtil.getExcelDate(javaDate, false), oneMinute);
}
}
/**
* Tests that we deal with time-zones properly
*/
@ -207,8 +204,7 @@ public final class TestHSSFDateUtil extends TestCase {
assertEquals("Checking timezone " + id, expected.getTime(), javaDate.getTime());
}
}
/**
* Tests that we correctly detect date formats as such
*/
@ -220,7 +216,7 @@ public final class TestHSSFDateUtil extends TestCase {
assertTrue( HSSFDateUtil.isInternalDateFormat(builtins[i]) );
assertTrue( HSSFDateUtil.isADateFormat(builtins[i],formatStr) );
}
// Now try a few built-in non date formats
builtins = new short[] { 0x01, 0x02, 0x17, 0x1f, 0x30 };
for(int i=0; i<builtins.length; i++) {
@ -228,14 +224,14 @@ public final class TestHSSFDateUtil extends TestCase {
assertFalse( HSSFDateUtil.isInternalDateFormat(builtins[i]) );
assertFalse( HSSFDateUtil.isADateFormat(builtins[i],formatStr) );
}
// Now for some non-internal ones
// These come after the real ones
int numBuiltins = HSSFDataFormat.getNumberOfBuiltinBuiltinFormats();
assertTrue(numBuiltins < 60);
short formatId = 60;
assertFalse( HSSFDateUtil.isInternalDateFormat(formatId) );
// Valid ones first
String[] formats = new String[] {
"yyyy-mm-dd", "yyyy/mm/dd", "yy/mm/dd", "yy/mmm/dd",
@ -243,7 +239,7 @@ public final class TestHSSFDateUtil extends TestCase {
"dd-mm-yy", "dd-mm-yyyy",
"DD-MM-YY", "DD-mm-YYYY",
"dd\\-mm\\-yy", // Sometimes escaped
// These crazy ones are valid
"yyyy-mm-dd;@", "yyyy/mm/dd;@",
"dd-mm-yy;@", "dd-mm-yyyy;@",
@ -257,14 +253,14 @@ public final class TestHSSFDateUtil extends TestCase {
};
for(int i=0; i<formats.length; i++) {
assertTrue(
formats[i] + " is a date format",
HSSFDateUtil.isADateFormat(formatId, formats[i])
formats[i] + " is a date format",
HSSFDateUtil.isADateFormat(formatId, formats[i])
);
}
// Then time based ones too
formats = new String[] {
"yyyy-mm-dd hh:mm:ss", "yyyy/mm/dd HH:MM:SS",
"yyyy-mm-dd hh:mm:ss", "yyyy/mm/dd HH:MM:SS",
"mm/dd HH:MM", "yy/mmm/dd SS",
"mm/dd HH:MM AM", "mm/dd HH:MM am",
"mm/dd HH:MM PM", "mm/dd HH:MM pm",
@ -272,30 +268,30 @@ public final class TestHSSFDateUtil extends TestCase {
};
for(int i=0; i<formats.length; i++) {
assertTrue(
formats[i] + " is a datetime format",
HSSFDateUtil.isADateFormat(formatId, formats[i])
formats[i] + " is a datetime format",
HSSFDateUtil.isADateFormat(formatId, formats[i])
);
}
// Then invalid ones
formats = new String[] {
"yyyy*mm*dd",
"yyyy*mm*dd",
"0.0", "0.000",
"0%", "0.0%",
"[]Foo", "[BLACK]0.00%",
"", null
};
for(int i=0; i<formats.length; i++) {
assertFalse(
formats[i] + " is not a date or datetime format",
HSSFDateUtil.isADateFormat(formatId, formats[i])
assertFalse(
formats[i] + " is not a date or datetime format",
HSSFDateUtil.isADateFormat(formatId, formats[i])
);
}
// And these are ones we probably shouldn't allow,
// but would need a better regexp
formats = new String[] {
"yyyy:mm:dd",
"yyyy:mm:dd",
};
for(int i=0; i<formats.length; i++) {
// assertFalse( HSSFDateUtil.isADateFormat(formatId, formats[i]) );
@ -306,63 +302,63 @@ public final class TestHSSFDateUtil extends TestCase {
* Test that against a real, test file, we still do everything
* correctly
*/
public void testOnARealFile() throws Exception {
public void testOnARealFile() {
HSSFWorkbook workbook = HSSFTestDataSamples.openSampleWorkbook("DateFormats.xls");
HSSFSheet sheet = workbook.getSheetAt(0);
Workbook wb = workbook.getWorkbook();
HSSFRow row;
HSSFCell cell;
HSSFCellStyle style;
double aug_10_2007 = 39304.0;
// Should have dates in 2nd column
// All of them are the 10th of August
// 2 US dates, 3 UK dates
row = sheet.getRow(0);
cell = row.getCell((short)1);
cell = row.getCell(1);
style = cell.getCellStyle();
assertEquals(aug_10_2007, cell.getNumericCellValue(), 0.0001);
assertEquals("d-mmm-yy", style.getDataFormatString(wb));
assertTrue(HSSFDateUtil.isInternalDateFormat(style.getDataFormat()));
assertTrue(HSSFDateUtil.isADateFormat(style.getDataFormat(), style.getDataFormatString(wb)));
assertTrue(HSSFDateUtil.isCellDateFormatted(cell));
row = sheet.getRow(1);
cell = row.getCell((short)1);
cell = row.getCell(1);
style = cell.getCellStyle();
assertEquals(aug_10_2007, cell.getNumericCellValue(), 0.0001);
assertFalse(HSSFDateUtil.isInternalDateFormat(cell.getCellStyle().getDataFormat()));
assertTrue(HSSFDateUtil.isADateFormat(style.getDataFormat(), style.getDataFormatString(wb)));
assertTrue(HSSFDateUtil.isCellDateFormatted(cell));
row = sheet.getRow(2);
cell = row.getCell((short)1);
cell = row.getCell(1);
style = cell.getCellStyle();
assertEquals(aug_10_2007, cell.getNumericCellValue(), 0.0001);
assertTrue(HSSFDateUtil.isInternalDateFormat(cell.getCellStyle().getDataFormat()));
assertTrue(HSSFDateUtil.isADateFormat(style.getDataFormat(), style.getDataFormatString(wb)));
assertTrue(HSSFDateUtil.isCellDateFormatted(cell));
row = sheet.getRow(3);
cell = row.getCell((short)1);
cell = row.getCell(1);
style = cell.getCellStyle();
assertEquals(aug_10_2007, cell.getNumericCellValue(), 0.0001);
assertFalse(HSSFDateUtil.isInternalDateFormat(cell.getCellStyle().getDataFormat()));
assertTrue(HSSFDateUtil.isADateFormat(style.getDataFormat(), style.getDataFormatString(wb)));
assertTrue(HSSFDateUtil.isCellDateFormatted(cell));
row = sheet.getRow(4);
cell = row.getCell((short)1);
cell = row.getCell(1);
style = cell.getCellStyle();
assertEquals(aug_10_2007, cell.getNumericCellValue(), 0.0001);
assertFalse(HSSFDateUtil.isInternalDateFormat(cell.getCellStyle().getDataFormat()));
assertTrue(HSSFDateUtil.isADateFormat(style.getDataFormat(), style.getDataFormatString(wb)));
assertTrue(HSSFDateUtil.isCellDateFormatted(cell));
}
public void testDateBug_2Excel() {
assertEquals(59.0, HSSFDateUtil.getExcelDate(createDate(1900, CALENDAR_FEBRUARY, 28), false), 0.00001);
assertEquals(61.0, HSSFDateUtil.getExcelDate(createDate(1900, CALENDAR_MARCH, 1), false), 0.00001);
@ -372,41 +368,49 @@ public final class TestHSSFDateUtil extends TestCase {
assertEquals(37257.00, HSSFDateUtil.getExcelDate(createDate(2002, CALENDAR_JANUARY, 1), false), 0.00001);
assertEquals(38074.00, HSSFDateUtil.getExcelDate(createDate(2004, CALENDAR_MARCH, 28), false), 0.00001);
}
public void testDateBug_2Java() {
assertEquals(createDate(1900, CALENDAR_FEBRUARY, 28), HSSFDateUtil.getJavaDate(59.0, false));
assertEquals(createDate(1900, CALENDAR_MARCH, 1), HSSFDateUtil.getJavaDate(61.0, false));
assertEquals(createDate(2002, CALENDAR_FEBRUARY, 28), HSSFDateUtil.getJavaDate(37315.00, false));
assertEquals(createDate(2002, CALENDAR_MARCH, 1), HSSFDateUtil.getJavaDate(37316.00, false));
assertEquals(createDate(2002, CALENDAR_JANUARY, 1), HSSFDateUtil.getJavaDate(37257.00, false));
assertEquals(createDate(2004, CALENDAR_MARCH, 28), HSSFDateUtil.getJavaDate(38074.00, false));
}
public void testDate1904() {
assertEquals(createDate(1904, CALENDAR_JANUARY, 2), HSSFDateUtil.getJavaDate(1.0, true));
assertEquals(createDate(1904, CALENDAR_JANUARY, 1), HSSFDateUtil.getJavaDate(0.0, true));
assertEquals(0.0, HSSFDateUtil.getExcelDate(createDate(1904, CALENDAR_JANUARY, 1), true), 0.00001);
assertEquals(1.0, HSSFDateUtil.getExcelDate(createDate(1904, CALENDAR_JANUARY, 2), true), 0.00001);
assertEquals(createDate(1998, CALENDAR_JULY, 5), HSSFDateUtil.getJavaDate(35981, false));
assertEquals(createDate(1998, CALENDAR_JULY, 5), HSSFDateUtil.getJavaDate(34519, true));
assertEquals(35981.0, HSSFDateUtil.getExcelDate(createDate(1998, CALENDAR_JULY, 5), false), 0.00001);
assertEquals(34519.0, HSSFDateUtil.getExcelDate(createDate(1998, CALENDAR_JULY, 5), true), 0.00001);
}
/**
* @param month zero based
* @param month zero based
* @param day one based
*/
private static Date createDate(int year, int month, int day) {
return createDate(year, month, day, 0, 0, 0);
}
/**
* @param month zero based
* @param day one based
*/
private static Date createDate(int year, int month, int day, int hour, int minute, int second) {
Calendar c = new GregorianCalendar();
c.set(year, month, day, 0, 0, 0);
c.set(year, month, day, hour, minute, second);
c.set(Calendar.MILLISECOND, 0);
return c.getTime();
}
/**
* Check if HSSFDateUtil.getAbsoluteDay works as advertised.
*/
@ -420,16 +424,27 @@ public final class TestHSSFDateUtil extends TestCase {
}
public void testConvertTime() {
final double delta = 1E-7; // a couple of digits more accuracy than strictly required
assertEquals(0.5, HSSFDateUtil.convertTime("12:00"), delta);
assertEquals(2.0/3, HSSFDateUtil.convertTime("16:00"), delta);
assertEquals(0.0000116, HSSFDateUtil.convertTime("0:00:01"), delta);
assertEquals(0.7330440, HSSFDateUtil.convertTime("17:35:35"), delta);
}
public void testParseDate() {
assertEquals(createDate(2008, Calendar.AUGUST, 3), HSSFDateUtil.parseYYYYMMDDDate("2008/08/03"));
assertEquals(createDate(1994, Calendar.MAY, 1), HSSFDateUtil.parseYYYYMMDDDate("1994/05/01"));
}
/**
* Ensure that date values *with* a fractional portion get the right time of day
*/
public void testConvertDateTime() {
// Excel day 30000 is date 18-Feb-1982
// 0.7 corresponds to time 16:48:00
Date actual = HSSFDateUtil.getJavaDate(30000.7);
Date expected = createDate(1982, 1, 18, 16, 48, 0);
assertEquals(expected, actual);
}
}