diff --git a/src/documentation/content/xdocs/status.xml b/src/documentation/content/xdocs/status.xml index 23f60da20..3277a33a9 100644 --- a/src/documentation/content/xdocs/status.xml +++ b/src/documentation/content/xdocs/status.xml @@ -34,6 +34,7 @@ + 48968 - Support for HOUR, MINUTE and SECOND date formulas 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 Fixed NPOIFS handling of new and empty Document Nodes Fixed NPOIFS access to Document Nodes not in the top level directory 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 7c84a5860..428b6d797 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 2d95c96d0..73a4b2341 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 590d096e1..d82674d73 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 000000000..ede20de39 --- /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 new file mode 100644 index 000000000..bbafffe9c Binary files /dev/null and b/test-data/spreadsheet/48968.xls differ