Fix bug #48968 - Implement support for HOUR, MINUTE and SECOND formulas

Includes some re-working of the existing Calendar functions, unit tests for the old and new Calendar functions, and a wider date+formula+formatting test for this area


git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@1085591 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Nick Burch 2011-03-25 22:52:12 +00:00
parent 2a295bf8ea
commit fa006be753
6 changed files with 171 additions and 22 deletions

View File

@ -34,6 +34,7 @@
<changes> <changes>
<release version="3.8-beta2" date="2011-??-??"> <release version="3.8-beta2" date="2011-??-??">
<action dev="poi-developers" type="add">48968 - Support for HOUR, MINUTE and SECOND date formulas</action>
<action dev="poi-developers" type="add">Added NPOIFS constructors to most POIDocument classes and their extractors, and more widely deprecated the Document(DirectoryNode, POIFSFileSystem) constructor in favour of the more general Document(DirectoryNode) one</action> <action dev="poi-developers" type="add">Added NPOIFS constructors to most POIDocument classes and their extractors, and more widely deprecated the Document(DirectoryNode, POIFSFileSystem) constructor in favour of the more general Document(DirectoryNode) one</action>
<action dev="poi-developers" type="fix">Fixed NPOIFS handling of new and empty Document Nodes</action> <action dev="poi-developers" type="fix">Fixed NPOIFS handling of new and empty Document Nodes</action>
<action dev="poi-developers" type="fix">Fixed NPOIFS access to Document Nodes not in the top level directory</action> <action dev="poi-developers" type="fix">Fixed NPOIFS access to Document Nodes not in the top level directory</action>

View File

@ -94,7 +94,7 @@ public final class FunctionEval {
retval[38] = BooleanFunction.NOT; retval[38] = BooleanFunction.NOT;
retval[39] = NumericFunction.MOD; retval[39] = NumericFunction.MOD;
retval[46] = AggregateFunction.VAR; retval[46] = AggregateFunction.VAR;
retval[48] = TextFunction.TEXT; retval[48] = TextFunction.TEXT;
retval[56] = FinanceFunction.PV; retval[56] = FinanceFunction.PV;
@ -111,6 +111,9 @@ public final class FunctionEval {
retval[68] = CalendarFieldFunction.MONTH; retval[68] = CalendarFieldFunction.MONTH;
retval[69] = CalendarFieldFunction.YEAR; retval[69] = CalendarFieldFunction.YEAR;
retval[71] = CalendarFieldFunction.HOUR;
retval[72] = CalendarFieldFunction.MINUTE;
retval[73] = CalendarFieldFunction.SECOND;
retval[74] = new Now(); retval[74] = new Now();
retval[76] = new Rows(); retval[76] = new Rows();

View File

@ -29,30 +29,29 @@ import org.apache.poi.ss.formula.eval.ValueEval;
import org.apache.poi.ss.usermodel.DateUtil; import org.apache.poi.ss.usermodel.DateUtil;
/** /**
* Implementation of Excel functions DAY, MONTH and YEAR * Implementation of Excel functions Date parsing functions:
* * Date - DAY, MONTH and YEAR
* * Time - HOUR, MINUTE and SECOND
* @author Guenter Kickinger g.kickinger@gmx.net
*/ */
public final class CalendarFieldFunction extends Fixed1ArgFunction { public final class CalendarFieldFunction extends Fixed1ArgFunction {
public static final Function YEAR = new CalendarFieldFunction(Calendar.YEAR);
public static final Function YEAR = new CalendarFieldFunction(Calendar.YEAR, false); public static final Function MONTH = new CalendarFieldFunction(Calendar.MONTH);
public static final Function MONTH = new CalendarFieldFunction(Calendar.MONTH, true); public static final Function DAY = new CalendarFieldFunction(Calendar.DAY_OF_MONTH);
public static final Function DAY = new CalendarFieldFunction(Calendar.DAY_OF_MONTH, false); public static final Function HOUR = new CalendarFieldFunction(Calendar.HOUR_OF_DAY);
public static final Function MINUTE = new CalendarFieldFunction(Calendar.MINUTE);
public static final Function SECOND = new CalendarFieldFunction(Calendar.SECOND);
private final int _dateFieldId; private final int _dateFieldId;
private final boolean _needsOneBaseAdjustment;
private CalendarFieldFunction(int dateFieldId, boolean needsOneBaseAdjustment) { private CalendarFieldFunction(int dateFieldId) {
_dateFieldId = dateFieldId; _dateFieldId = dateFieldId;
_needsOneBaseAdjustment = needsOneBaseAdjustment;
} }
public final ValueEval evaluate(int srcRowIndex, int srcColumnIndex, ValueEval arg0) { public final ValueEval evaluate(int srcRowIndex, int srcColumnIndex, ValueEval arg0) {
int val; double val;
try { try {
ValueEval ve = OperandResolver.getSingleValue(arg0, srcRowIndex, srcColumnIndex); ValueEval ve = OperandResolver.getSingleValue(arg0, srcRowIndex, srcColumnIndex);
val = OperandResolver.coerceValueToInt(ve); val = OperandResolver.coerceValueToDouble(ve);
} catch (EvaluationException e) { } catch (EvaluationException e) {
return e.getErrorEval(); return e.getErrorEval();
} }
@ -62,26 +61,30 @@ public final class CalendarFieldFunction extends Fixed1ArgFunction {
return new NumberEval(getCalField(val)); return new NumberEval(getCalField(val));
} }
private int getCalField(int serialDay) { private int getCalField(double serialDate) {
if (serialDay == 0) { // For some reason, a date of 0 in Excel gets shown
// Special weird case // as the non existant 1900-01-00
// day zero should be 31-Dec-1899, but Excel seems to think it is 0-Jan-1900 if(((int)serialDate) == 0) {
switch (_dateFieldId) { switch (_dateFieldId) {
case Calendar.YEAR: return 1900; case Calendar.YEAR: return 1900;
case Calendar.MONTH: return 1; case Calendar.MONTH: return 1;
case Calendar.DAY_OF_MONTH: return 0; case Calendar.DAY_OF_MONTH: return 0;
} }
throw new IllegalStateException("bad date field " + _dateFieldId); // They want time, that's normal
} }
Date d = DateUtil.getJavaDate(serialDay, false); // TODO fix 1900/1904 problem
// TODO Figure out if we're in 1900 or 1904
Date d = DateUtil.getJavaDate(serialDate, false);
Calendar c = new GregorianCalendar(); Calendar c = new GregorianCalendar();
c.setTime(d); c.setTime(d);
int result = c.get(_dateFieldId); int result = c.get(_dateFieldId);
if (_needsOneBaseAdjustment) {
// Month is a special case due to C semantics
if (_dateFieldId == Calendar.MONTH) {
result++; result++;
} }
return result; return result;
} }
} }

View File

@ -22,10 +22,16 @@ import java.io.File;
import java.io.FileNotFoundException; import java.io.FileNotFoundException;
import java.io.FileOutputStream; import java.io.FileOutputStream;
import java.io.IOException; import java.io.IOException;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Calendar;
import java.util.Date;
import java.util.Iterator; import java.util.Iterator;
import java.util.List; import java.util.List;
import java.util.Locale;
import java.util.TimeZone;
import junit.framework.AssertionFailedError; import junit.framework.AssertionFailedError;
@ -46,6 +52,7 @@ import org.apache.poi.ss.formula.ptg.Ptg;
import org.apache.poi.ss.usermodel.BaseTestBugzillaIssues; import org.apache.poi.ss.usermodel.BaseTestBugzillaIssues;
import org.apache.poi.ss.usermodel.Cell; import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.CellStyle; import org.apache.poi.ss.usermodel.CellStyle;
import org.apache.poi.ss.usermodel.DateUtil;
import org.apache.poi.ss.usermodel.Name; import org.apache.poi.ss.usermodel.Name;
import org.apache.poi.ss.usermodel.Row; import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet; import org.apache.poi.ss.usermodel.Sheet;
@ -2042,4 +2049,54 @@ if(1==2) {
assertEquals(1, wb.getNumberOfSheets()); assertEquals(1, wb.getNumberOfSheets());
assertEquals("DGATE", wb.getSheetAt(0).getRow(1).getCell(0).getStringCellValue()); assertEquals("DGATE", wb.getSheetAt(0).getRow(1).getCell(0).getStringCellValue());
} }
public void test48968() throws Exception {
HSSFWorkbook wb = openSample("48968.xls");
assertEquals(1, wb.getNumberOfSheets());
// Check the dates
HSSFSheet s = wb.getSheetAt(0);
Date d20110325 = s.getRow(0).getCell(0).getDateCellValue();
Date d19000102 = s.getRow(11).getCell(0).getDateCellValue();
Date d19000100 = s.getRow(21).getCell(0).getDateCellValue();
assertEquals(s.getRow(0).getCell(3).getStringCellValue(), timeToUTC(d20110325));
assertEquals(s.getRow(11).getCell(3).getStringCellValue(), timeToUTC(d19000102));
// There is no such thing as 00/01/1900...
assertEquals("00/01/1900 06:14:24", s.getRow(21).getCell(3).getStringCellValue());
assertEquals("31/12/1899 06:14:24", timeToUTC(d19000100));
// Check the cached values
assertEquals("HOUR(A1)", s.getRow(5).getCell(0).getCellFormula());
assertEquals(11.0, s.getRow(5).getCell(0).getNumericCellValue());
assertEquals("MINUTE(A1)", s.getRow(6).getCell(0).getCellFormula());
assertEquals(39.0, s.getRow(6).getCell(0).getNumericCellValue());
assertEquals("SECOND(A1)", s.getRow(7).getCell(0).getCellFormula());
assertEquals(54.0, s.getRow(7).getCell(0).getNumericCellValue());
// Re-evaulate and check
HSSFFormulaEvaluator.evaluateAllFormulaCells(wb);
assertEquals("HOUR(A1)", s.getRow(5).getCell(0).getCellFormula());
assertEquals(11.0, s.getRow(5).getCell(0).getNumericCellValue());
assertEquals("MINUTE(A1)", s.getRow(6).getCell(0).getCellFormula());
assertEquals(39.0, s.getRow(6).getCell(0).getNumericCellValue());
assertEquals("SECOND(A1)", s.getRow(7).getCell(0).getCellFormula());
assertEquals(54.0, s.getRow(7).getCell(0).getNumericCellValue());
// Push the time forward a bit and check
double date = s.getRow(0).getCell(0).getNumericCellValue();
s.getRow(0).getCell(0).setCellValue(date + 1.26);
HSSFFormulaEvaluator.evaluateAllFormulaCells(wb);
assertEquals("HOUR(A1)", s.getRow(5).getCell(0).getCellFormula());
assertEquals(11.0+6.0, s.getRow(5).getCell(0).getNumericCellValue());
assertEquals("MINUTE(A1)", s.getRow(6).getCell(0).getCellFormula());
assertEquals(39.0+14.0+1, s.getRow(6).getCell(0).getNumericCellValue());
assertEquals("SECOND(A1)", s.getRow(7).getCell(0).getCellFormula());
assertEquals(54.0+24.0-60, s.getRow(7).getCell(0).getNumericCellValue());
}
private String timeToUTC(Date d) {
SimpleDateFormat fmt = new SimpleDateFormat("dd/MM/yyyy HH:mm:ss", Locale.UK);
fmt.setTimeZone(TimeZone.getTimeZone("UTC"));
return fmt.format(d);
}
} }

View File

@ -0,0 +1,85 @@
/* ====================================================================
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.ss.formula.functions;
import junit.framework.AssertionFailedError;
import junit.framework.TestCase;
import org.apache.poi.hssf.usermodel.HSSFCell;
import org.apache.poi.hssf.usermodel.HSSFFormulaEvaluator;
import org.apache.poi.hssf.usermodel.HSSFSheet;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.CellValue;
/**
* Test for YEAR / MONTH / DAY / HOUR / MINUTE / SECOND
*/
public final class TestCalendarFieldFunction extends TestCase {
private HSSFCell cell11;
private HSSFFormulaEvaluator evaluator;
public void setUp() {
HSSFWorkbook wb = new HSSFWorkbook();
HSSFSheet sheet = wb.createSheet("new sheet");
cell11 = sheet.createRow(0).createCell(0);
cell11.setCellType(HSSFCell.CELL_TYPE_FORMULA);
evaluator = new HSSFFormulaEvaluator(wb);
}
public void testValid() {
confirm("YEAR(2.26)", 1900);
confirm("MONTH(2.26)", 1);
confirm("DAY(2.26)", 2);
confirm("HOUR(2.26)", 6);
confirm("MINUTE(2.26)", 14);
confirm("SECOND(2.26)", 24);
confirm("YEAR(40627.4860417)", 2011);
confirm("MONTH(40627.4860417)", 3);
confirm("DAY(40627.4860417)", 25);
confirm("HOUR(40627.4860417)", 11);
confirm("MINUTE(40627.4860417)", 39);
confirm("SECOND(40627.4860417)", 54);
}
public void testBugDate() {
confirm("YEAR(0.0)", 1900);
confirm("MONTH(0.0)", 1);
confirm("DAY(0.0)", 0);
confirm("YEAR(0.26)", 1900);
confirm("MONTH(0.26)", 1);
confirm("DAY(0.26)", 0);
confirm("HOUR(0.26)", 6);
confirm("MINUTE(0.26)", 14);
confirm("SECOND(0.26)", 24);
}
private void confirm(String formulaText, double expectedResult) {
cell11.setCellFormula(formulaText);
evaluator.clearAllCachedResultValues();
CellValue cv = evaluator.evaluate(cell11);
if (cv.getCellType() != Cell.CELL_TYPE_NUMERIC) {
throw new AssertionFailedError("Wrong result type: " + cv.formatAsString());
}
double actualValue = cv.getNumberValue();
assertEquals(expectedResult, actualValue, 0);
}
}

Binary file not shown.