diff options
-rw-r--r-- | src/documentation/content/xdocs/status.xml | 1 | ||||
-rw-r--r-- | src/java/org/apache/poi/ss/formula/eval/FunctionEval.java | 5 | ||||
-rw-r--r-- | src/java/org/apache/poi/ss/formula/functions/CalendarFieldFunction.java | 45 | ||||
-rw-r--r-- | src/testcases/org/apache/poi/hssf/usermodel/TestBugs.java | 57 | ||||
-rw-r--r-- | src/testcases/org/apache/poi/ss/formula/functions/TestCalendarFieldFunction.java | 85 | ||||
-rw-r--r-- | test-data/spreadsheet/48968.xls | bin | 0 -> 22016 bytes |
6 files changed, 171 insertions, 22 deletions
diff --git a/src/documentation/content/xdocs/status.xml b/src/documentation/content/xdocs/status.xml index 23f60da20b..3277a33a9f 100644 --- a/src/documentation/content/xdocs/status.xml +++ b/src/documentation/content/xdocs/status.xml @@ -34,6 +34,7 @@ <changes> <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="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> diff --git a/src/java/org/apache/poi/ss/formula/eval/FunctionEval.java b/src/java/org/apache/poi/ss/formula/eval/FunctionEval.java index 7c84a5860b..428b6d797d 100644 --- a/src/java/org/apache/poi/ss/formula/eval/FunctionEval.java +++ b/src/java/org/apache/poi/ss/formula/eval/FunctionEval.java @@ -94,7 +94,7 @@ public final class FunctionEval { retval[38] = BooleanFunction.NOT; retval[39] = NumericFunction.MOD; - retval[46] = AggregateFunction.VAR; + retval[46] = AggregateFunction.VAR; retval[48] = TextFunction.TEXT; retval[56] = FinanceFunction.PV; @@ -111,6 +111,9 @@ public final class FunctionEval { retval[68] = CalendarFieldFunction.MONTH; retval[69] = CalendarFieldFunction.YEAR; + retval[71] = CalendarFieldFunction.HOUR; + retval[72] = CalendarFieldFunction.MINUTE; + retval[73] = CalendarFieldFunction.SECOND; retval[74] = new Now(); retval[76] = new Rows(); diff --git a/src/java/org/apache/poi/ss/formula/functions/CalendarFieldFunction.java b/src/java/org/apache/poi/ss/formula/functions/CalendarFieldFunction.java index 2d95c96d00..73a4b2341e 100644 --- a/src/java/org/apache/poi/ss/formula/functions/CalendarFieldFunction.java +++ b/src/java/org/apache/poi/ss/formula/functions/CalendarFieldFunction.java @@ -29,30 +29,29 @@ import org.apache.poi.ss.formula.eval.ValueEval; import org.apache.poi.ss.usermodel.DateUtil; /** - * Implementation of Excel functions DAY, MONTH and YEAR - * - * - * @author Guenter Kickinger g.kickinger@gmx.net + * Implementation of Excel functions Date parsing functions: + * Date - DAY, MONTH and YEAR + * Time - HOUR, MINUTE and SECOND */ public final class CalendarFieldFunction extends Fixed1ArgFunction { - - public static final Function YEAR = new CalendarFieldFunction(Calendar.YEAR, false); - public static final Function MONTH = new CalendarFieldFunction(Calendar.MONTH, true); - public static final Function DAY = new CalendarFieldFunction(Calendar.DAY_OF_MONTH, false); + public static final Function YEAR = new CalendarFieldFunction(Calendar.YEAR); + public static final Function MONTH = new CalendarFieldFunction(Calendar.MONTH); + public static final Function DAY = new CalendarFieldFunction(Calendar.DAY_OF_MONTH); + 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 boolean _needsOneBaseAdjustment; - private CalendarFieldFunction(int dateFieldId, boolean needsOneBaseAdjustment) { + private CalendarFieldFunction(int dateFieldId) { _dateFieldId = dateFieldId; - _needsOneBaseAdjustment = needsOneBaseAdjustment; } public final ValueEval evaluate(int srcRowIndex, int srcColumnIndex, ValueEval arg0) { - int val; + double val; try { ValueEval ve = OperandResolver.getSingleValue(arg0, srcRowIndex, srcColumnIndex); - val = OperandResolver.coerceValueToInt(ve); + val = OperandResolver.coerceValueToDouble(ve); } catch (EvaluationException e) { return e.getErrorEval(); } @@ -62,26 +61,30 @@ public final class CalendarFieldFunction extends Fixed1ArgFunction { return new NumberEval(getCalField(val)); } - private int getCalField(int serialDay) { - if (serialDay == 0) { - // Special weird case - // day zero should be 31-Dec-1899, but Excel seems to think it is 0-Jan-1900 + private int getCalField(double serialDate) { + // For some reason, a date of 0 in Excel gets shown + // as the non existant 1900-01-00 + if(((int)serialDate) == 0) { switch (_dateFieldId) { case Calendar.YEAR: return 1900; case Calendar.MONTH: return 1; 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(); c.setTime(d); - int result = c.get(_dateFieldId); - if (_needsOneBaseAdjustment) { + + // Month is a special case due to C semantics + if (_dateFieldId == Calendar.MONTH) { result++; } + return result; } } diff --git a/src/testcases/org/apache/poi/hssf/usermodel/TestBugs.java b/src/testcases/org/apache/poi/hssf/usermodel/TestBugs.java index 590d096e11..d82674d733 100644 --- a/src/testcases/org/apache/poi/hssf/usermodel/TestBugs.java +++ b/src/testcases/org/apache/poi/hssf/usermodel/TestBugs.java @@ -22,10 +22,16 @@ import java.io.File; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; +import java.text.DateFormat; +import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Arrays; +import java.util.Calendar; +import java.util.Date; import java.util.Iterator; import java.util.List; +import java.util.Locale; +import java.util.TimeZone; 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.Cell; 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.Row; import org.apache.poi.ss.usermodel.Sheet; @@ -2042,4 +2049,54 @@ if(1==2) { assertEquals(1, wb.getNumberOfSheets()); 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); + } } diff --git a/src/testcases/org/apache/poi/ss/formula/functions/TestCalendarFieldFunction.java b/src/testcases/org/apache/poi/ss/formula/functions/TestCalendarFieldFunction.java new file mode 100644 index 0000000000..ede20de393 --- /dev/null +++ b/src/testcases/org/apache/poi/ss/formula/functions/TestCalendarFieldFunction.java @@ -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); + } +} diff --git a/test-data/spreadsheet/48968.xls b/test-data/spreadsheet/48968.xls Binary files differnew file mode 100644 index 0000000000..bbafffe9c1 --- /dev/null +++ b/test-data/spreadsheet/48968.xls |