From fa006be75386acd5cb70351bc26edf333305dae0 Mon Sep 17 00:00:00 2001 From: Nick Burch Date: Fri, 25 Mar 2011 22:52:12 +0000 Subject: [PATCH] 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 --- src/documentation/content/xdocs/status.xml | 1 + .../poi/ss/formula/eval/FunctionEval.java | 5 +- .../functions/CalendarFieldFunction.java | 45 +++++----- .../apache/poi/hssf/usermodel/TestBugs.java | 57 ++++++++++++ .../functions/TestCalendarFieldFunction.java | 85 ++++++++++++++++++ test-data/spreadsheet/48968.xls | Bin 0 -> 22016 bytes 6 files changed, 171 insertions(+), 22 deletions(-) create mode 100644 src/testcases/org/apache/poi/ss/formula/functions/TestCalendarFieldFunction.java create mode 100644 test-data/spreadsheet/48968.xls 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 0000000000000000000000000000000000000000..bbafffe9c13cc1e1a28bfedbe4cd19dc5f80e3ca GIT binary patch literal 22016 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 literal 0 HcmV?d00001