git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@1855662 13f79535-47bb-0310-9956-ffa450edef68tags/REL_4_1_0
package org.apache.poi.ss.formula.eval; | package org.apache.poi.ss.formula.eval; | ||||
import org.apache.poi.ss.formula.EvaluationCell; | import org.apache.poi.ss.formula.EvaluationCell; | ||||
import org.apache.poi.ss.usermodel.DateUtil; | |||||
import org.apache.poi.ss.util.CellRangeAddress; | import org.apache.poi.ss.util.CellRangeAddress; | ||||
import java.time.DateTimeException; | |||||
import java.util.regex.Pattern; | import java.util.regex.Pattern; | ||||
/** | /** | ||||
return ((NumericValueEval)ev).getNumberValue(); | return ((NumericValueEval)ev).getNumberValue(); | ||||
} | } | ||||
if (ev instanceof StringEval) { | if (ev instanceof StringEval) { | ||||
Double dd = parseDouble(((StringEval) ev).getStringValue()); | |||||
String sval = ((StringEval) ev).getStringValue(); | |||||
Double dd = parseDouble(sval); | |||||
if(dd == null) dd = parseDateTime(sval); | |||||
if (dd == null) { | if (dd == null) { | ||||
throw EvaluationException.invalidValue(); | throw EvaluationException.invalidValue(); | ||||
} | } | ||||
} | } | ||||
public static Double parseDateTime(String pText) { | |||||
try { | |||||
return DateUtil.parseDateTime(pText); | |||||
} catch (DateTimeException e) { | |||||
return null; | |||||
} | |||||
} | |||||
/** | /** | ||||
* @param ve must be a <tt>NumberEval</tt>, <tt>StringEval</tt>, <tt>BoolEval</tt>, or <tt>BlankEval</tt> | * @param ve must be a <tt>NumberEval</tt>, <tt>StringEval</tt>, <tt>BoolEval</tt>, or <tt>BlankEval</tt> | ||||
* @return the converted string value. never <code>null</code> | * @return the converted string value. never <code>null</code> |
import org.apache.poi.ss.formula.eval.NumberEval; | import org.apache.poi.ss.formula.eval.NumberEval; | ||||
import org.apache.poi.ss.formula.eval.OperandResolver; | import org.apache.poi.ss.formula.eval.OperandResolver; | ||||
import org.apache.poi.ss.formula.eval.ValueEval; | import org.apache.poi.ss.formula.eval.ValueEval; | ||||
import org.apache.poi.ss.usermodel.DateUtil; | |||||
import java.time.DateTimeException; | |||||
/** | /** | ||||
* Implementation for Excel VALUE() function.<p> | * Implementation for Excel VALUE() function.<p> | ||||
} | } | ||||
String strText = OperandResolver.coerceValueToString(veText); | String strText = OperandResolver.coerceValueToString(veText); | ||||
Double result = convertTextToNumber(strText); | Double result = convertTextToNumber(strText); | ||||
if(result == null) result = parseDateTime(strText); | |||||
if (result == null) { | if (result == null) { | ||||
return ErrorEval.VALUE_INVALID; | return ErrorEval.VALUE_INVALID; | ||||
} | } | ||||
* | * | ||||
* @return <code>null</code> if there is any problem converting the text | * @return <code>null</code> if there is any problem converting the text | ||||
*/ | */ | ||||
private static Double convertTextToNumber(String strText) { | |||||
public static Double convertTextToNumber(String strText) { | |||||
boolean foundCurrency = false; | boolean foundCurrency = false; | ||||
boolean foundUnaryPlus = false; | boolean foundUnaryPlus = false; | ||||
boolean foundUnaryMinus = false; | boolean foundUnaryMinus = false; | ||||
double result = foundUnaryMinus ? -d : d; | double result = foundUnaryMinus ? -d : d; | ||||
return foundPercentage ? result/100. : result; | return foundPercentage ? result/100. : result; | ||||
} | } | ||||
public static Double parseDateTime(String pText) { | |||||
try { | |||||
return DateUtil.parseDateTime(pText); | |||||
} catch (DateTimeException e) { | |||||
return null; | |||||
} | |||||
} | |||||
} | } |
package org.apache.poi.ss.usermodel; | package org.apache.poi.ss.usermodel; | ||||
import java.time.LocalDate; | |||||
import java.time.LocalTime; | |||||
import java.time.ZoneId; | |||||
import java.time.format.DateTimeFormatter; | |||||
import java.time.format.DateTimeFormatterBuilder; | |||||
import java.time.temporal.ChronoField; | |||||
import java.time.temporal.TemporalAccessor; | |||||
import java.time.temporal.TemporalQueries; | |||||
import java.util.Calendar; | import java.util.Calendar; | ||||
import java.util.Date; | import java.util.Date; | ||||
import java.util.TimeZone; | import java.util.TimeZone; | ||||
// for format which start with "[DBNum1]" or "[DBNum2]" or "[DBNum3]" could be a Chinese date | // for format which start with "[DBNum1]" or "[DBNum2]" or "[DBNum3]" could be a Chinese date | ||||
private static final Pattern date_ptrn5 = Pattern.compile("^\\[DBNum(1|2|3)\\]"); | private static final Pattern date_ptrn5 = Pattern.compile("^\\[DBNum(1|2|3)\\]"); | ||||
private static final DateTimeFormatter dateTimeFormats = new DateTimeFormatterBuilder() | |||||
.appendPattern("[dd MMM[ yyyy]][[ ]h:m[:s] a][[ ]H:m[:s]]") | |||||
.appendPattern("[[yyyy ]dd-MMM[-yyyy]][[ ]h:m[:s] a][[ ]H:m[:s]]") | |||||
.appendPattern("[M/dd[/yyyy]][[ ]h:m[:s] a][[ ]H:m[:s]]") | |||||
.appendPattern("[[yyyy/]M/dd][[ ]h:m[:s] a][[ ]H:m[:s]]") | |||||
.parseDefaulting(ChronoField.YEAR_OF_ERA, Calendar.getInstance().get(Calendar.YEAR)) | |||||
.toFormatter(); | |||||
/** | /** | ||||
* Given a Date, converts it into a double representing its internal Excel representation, | * Given a Date, converts it into a double representing its internal Excel representation, | ||||
* which is the number of days since 1/1/1900. Fractional days represent hours, minutes, and seconds. | * which is the number of days since 1/1/1900. Fractional days represent hours, minutes, and seconds. | ||||
} | } | ||||
return result; | return result; | ||||
} | } | ||||
public static Double parseDateTime(String str){ | |||||
TemporalAccessor tmp = dateTimeFormats.parse(str.replaceAll("\\s+", " ")); | |||||
LocalTime time = tmp.query(TemporalQueries.localTime()); | |||||
LocalDate date = tmp.query(TemporalQueries.localDate()); | |||||
if(time == null && date == null) return null; | |||||
double tm = 0; | |||||
if(date != null) { | |||||
Date d = Date.from(date.atStartOfDay().atZone(ZoneId.systemDefault()).toInstant()); | |||||
tm = DateUtil.getExcelDate(d); | |||||
} | |||||
if(time != null) tm += 1.0*time.toSecondOfDay()/SECONDS_PER_DAY; | |||||
return tm; | |||||
} | |||||
} | } |
import junit.framework.AssertionFailedError; | import junit.framework.AssertionFailedError; | ||||
import junit.framework.TestCase; | import junit.framework.TestCase; | ||||
import java.util.LinkedHashMap; | |||||
import java.util.Map; | |||||
/** | /** | ||||
* Tests for <tt>OperandResolver</tt> | * Tests for <tt>OperandResolver</tt> | ||||
* | * | ||||
assertNull(OperandResolver.parseDouble(value)); | assertNull(OperandResolver.parseDouble(value)); | ||||
} | } | ||||
} | } | ||||
public void testCoerceDateStringToNumber() throws EvaluationException { | |||||
Map<String, Double> values = new LinkedHashMap<>(); | |||||
values.put("2019/1/18", 43483.); | |||||
values.put("01/18/2019", 43483.); | |||||
values.put("18 Jan 2019", 43483.); | |||||
values.put("18-Jan-2019", 43483.); | |||||
for (String str : values.keySet()) { | |||||
assertEquals(OperandResolver.coerceValueToDouble(new StringEval(str)), values.get(str), 0.00001); | |||||
} | |||||
} | |||||
public void testCoerceTimeStringToNumber() throws EvaluationException { | |||||
Map<String, Double> values = new LinkedHashMap<>(); | |||||
values.put("00:00", 0.0); | |||||
values.put("12:00", 0.5); | |||||
values.put("15:43:09", 0.654965278); | |||||
values.put("15:43", 0.654861111); | |||||
values.put("3:43 PM", 0.654861111); | |||||
for (String str : values.keySet()) { | |||||
assertEquals(OperandResolver.coerceValueToDouble(new StringEval(str)), values.get(str), 0.00001); | |||||
} | |||||
} | |||||
} | } |
public int formulasRowIdx; | public int formulasRowIdx; | ||||
@Parameter(value = 4) | @Parameter(value = 4) | ||||
public HSSFFormulaEvaluator evaluator; | public HSSFFormulaEvaluator evaluator; | ||||
@Parameter(value = 5) | |||||
public int precisionColumnIndex; | |||||
HSSFSheet sheet = workbook.getSheetAt(sheetIdx); | HSSFSheet sheet = workbook.getSheetAt(sheetIdx); | ||||
processFunctionGroup(data, sheet, SS.START_TEST_CASES_ROW_INDEX, filename); | processFunctionGroup(data, sheet, SS.START_TEST_CASES_ROW_INDEX, filename); | ||||
} | } | ||||
workbook.close(); | workbook.close(); | ||||
return data; | return data; | ||||
private static void processFunctionGroup(List<Object[]> data, HSSFSheet sheet, final int startRowIndex, String filename) { | private static void processFunctionGroup(List<Object[]> data, HSSFSheet sheet, final int startRowIndex, String filename) { | ||||
HSSFFormulaEvaluator evaluator = new HSSFFormulaEvaluator(sheet.getWorkbook()); | HSSFFormulaEvaluator evaluator = new HSSFFormulaEvaluator(sheet.getWorkbook()); | ||||
int precisionColumnIndex = -1; | |||||
HSSFRow precisionRow = sheet.getWorkbook().getSheetAt(0).getRow(11); | |||||
HSSFCell precisionCell = precisionRow == null ? null : precisionRow.getCell(0); | |||||
if(precisionCell != null && precisionCell.getCellType() == CellType.NUMERIC){ | |||||
precisionColumnIndex = (int)precisionCell.getNumericCellValue(); | |||||
} | |||||
String currentGroupComment = ""; | String currentGroupComment = ""; | ||||
final int maxRows = sheet.getLastRowNum()+1; | final int maxRows = sheet.getLastRowNum()+1; | ||||
for(int rowIndex=startRowIndex; rowIndex<maxRows; rowIndex++) { | for(int rowIndex=startRowIndex; rowIndex<maxRows; rowIndex++) { | ||||
testName = evalCell.getCellFormula(); | testName = evalCell.getCellFormula(); | ||||
} | } | ||||
data.add(new Object[]{testName, filename, sheet, rowIndex, evaluator}); | |||||
data.add(new Object[]{testName, filename, sheet, rowIndex, evaluator, precisionColumnIndex}); | |||||
} | } | ||||
fail("Missing end marker '" + SS.TEST_CASES_END_MARKER + "' on sheet '" + sheet.getSheetName() + "'"); | fail("Missing end marker '" + SS.TEST_CASES_END_MARKER + "' on sheet '" + sheet.getSheetName() + "'"); | ||||
} | } | ||||
HSSFRow r = sheet.getRow(formulasRowIdx); | HSSFRow r = sheet.getRow(formulasRowIdx); | ||||
HSSFCell evalCell = r.getCell(SS.COLUMN_INDEX_EVALUATION); | HSSFCell evalCell = r.getCell(SS.COLUMN_INDEX_EVALUATION); | ||||
HSSFCell expectedCell = r.getCell(SS.COLUMN_INDEX_EXPECTED_RESULT); | HSSFCell expectedCell = r.getCell(SS.COLUMN_INDEX_EXPECTED_RESULT); | ||||
HSSFCell precisionCell = r.getCell(precisionColumnIndex); | |||||
CellReference cr = new CellReference(sheet.getSheetName(), formulasRowIdx, evalCell.getColumnIndex(), false, false); | CellReference cr = new CellReference(sheet.getSheetName(), formulasRowIdx, evalCell.getColumnIndex(), false, false); | ||||
String msg = String.format(Locale.ROOT, "In %s %s {=%s} '%s'" | String msg = String.format(Locale.ROOT, "In %s %s {=%s} '%s'" | ||||
, filename, cr.formatAsString(), evalCell.getCellFormula(), testName); | , filename, cr.formatAsString(), evalCell.getCellFormula(), testName); | ||||
case FORMULA: // will never be used, since we will call method after formula evaluation | case FORMULA: // will never be used, since we will call method after formula evaluation | ||||
fail("Cannot expect formula as result of formula evaluation: " + msg); | fail("Cannot expect formula as result of formula evaluation: " + msg); | ||||
case NUMERIC: | case NUMERIC: | ||||
assertEquals(expectedCell.getNumericCellValue(), actualValue.getNumberValue(), 0.0); | |||||
double precision = precisionCell != null && precisionCell.getCellType() == CellType.NUMERIC | |||||
? precisionCell.getNumericCellValue() : 0.0; | |||||
assertEquals(expectedCell.getNumericCellValue(), actualValue.getNumberValue(), precision); | |||||
break; | break; | ||||
case STRING: | case STRING: | ||||
assertEquals(msg, expectedCell.getRichStringCellValue().getString(), actualValue.getStringValue()); | assertEquals(msg, expectedCell.getRichStringCellValue().getString(), actualValue.getStringValue()); | ||||
HSSFSheet sheet = workbook.getSheetAt(0); | HSSFSheet sheet = workbook.getSheetAt(0); | ||||
String specifiedClassName = sheet.getRow(2).getCell(0).getRichStringCellValue().getString(); | String specifiedClassName = sheet.getRow(2).getCell(0).getRichStringCellValue().getString(); | ||||
assertEquals("Test class name in spreadsheet comment", clazz.getName(), specifiedClassName); | assertEquals("Test class name in spreadsheet comment", clazz.getName(), specifiedClassName); | ||||
HSSFRow precisionRow = sheet.getRow(11); | |||||
HSSFCell precisionCell = precisionRow == null ? null : precisionRow.getCell(0); | |||||
if(precisionCell != null && precisionCell.getCellType() == CellType.NUMERIC){ | |||||
} | |||||
} | } | ||||
/** | /** |
/* ==================================================================== | |||||
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 org.junit.runners.Parameterized.Parameters; | |||||
import java.util.Collection; | |||||
public class TestDateTimeToNumberFromSpreadsheet extends BaseTestFunctionsFromSpreadsheet { | |||||
@Parameters(name="{0}") | |||||
public static Collection<Object[]> data() throws Exception { | |||||
return data(TestDateTimeToNumberFromSpreadsheet.class, "DateTimeToNumberTestCases.xls"); | |||||
} | |||||
} |