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:
parent
313d71cc51
commit
02608ba18d
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -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() {
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
160
src/java/org/apache/poi/hssf/record/formula/atp/YearFrac.java
Normal file
160
src/java/org/apache/poi/hssf/record/formula/atp/YearFrac.java
Normal 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);
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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 < amolweb at ya hoo dot com >
|
||||
*
|
||||
*
|
||||
*/
|
||||
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 < amolweb at ya hoo dot com >
|
||||
@ -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>");
|
||||
}
|
||||
|
||||
}
|
||||
|
BIN
src/testcases/org/apache/poi/hssf/data/yearfracExamples.xls
Normal file
BIN
src/testcases/org/apache/poi/hssf/data/yearfracExamples.xls
Normal file
Binary file not shown.
@ -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);
|
||||
|
@ -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());
|
||||
}
|
||||
}
|
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user