git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@688650 13f79535-47bb-0310-9956-ffa450edef68tags/REL_3_2_FINAL
@@ -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); | |||
} | |||
} |
@@ -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>"); | |||
} | |||
} |
@@ -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); | |||
} | |||
} |