]> source.dussan.org Git - poi.git/commitdiff
Fix bug #48968 - Implement support for HOUR, MINUTE and SECOND formulas
authorNick Burch <nick@apache.org>
Fri, 25 Mar 2011 22:52:12 +0000 (22:52 +0000)
committerNick Burch <nick@apache.org>
Fri, 25 Mar 2011 22:52:12 +0000 (22:52 +0000)
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
src/java/org/apache/poi/ss/formula/eval/FunctionEval.java
src/java/org/apache/poi/ss/formula/functions/CalendarFieldFunction.java
src/testcases/org/apache/poi/hssf/usermodel/TestBugs.java
src/testcases/org/apache/poi/ss/formula/functions/TestCalendarFieldFunction.java [new file with mode: 0644]
test-data/spreadsheet/48968.xls [new file with mode: 0644]

index 23f60da20b2dac32277173865d00ca0076de6e9d..3277a33a9f7a02406f48f09959efd52129481b57 100644 (file)
@@ -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>
index 7c84a5860bdedb373d253309d2c8ff8ca96d0ce0..428b6d797dbf15f0618bea28164e56cbd74f93db 100644 (file)
@@ -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();
index 2d95c96d0076c0d994c9ce476f86993e3ef5c224..73a4b2341ef835ad42c77261c86c9c3a73f91bd2 100644 (file)
@@ -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;
        }
 }
index 590d096e113640ed608eaa30e41834632866b36e..d82674d7332a014e5d4e5852f26ffc6af88f4b62 100644 (file)
@@ -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 (file)
index 0000000..ede20de
--- /dev/null
@@ -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 (file)
index 0000000..bbafffe
Binary files /dev/null and b/test-data/spreadsheet/48968.xls differ