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-ffa450edef68tags/REL_3_8_BETA2
@@ -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> |
@@ -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(); |
@@ -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; | |||
} | |||
} |
@@ -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); | |||
} | |||
} |
@@ -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); | |||
} | |||
} |