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:
parent
2a295bf8ea
commit
fa006be753
@ -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>
|
||||||
|
@ -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();
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
BIN
test-data/spreadsheet/48968.xls
Normal file
BIN
test-data/spreadsheet/48968.xls
Normal file
Binary file not shown.
Loading…
Reference in New Issue
Block a user