mirror of https://github.com/apache/poi.git
Bugzill 52378: Support for Excel's Analysis Tool Pack functions workday and networkdays
git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@1221682 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
parent
ca28206971
commit
a20915f758
|
@ -34,6 +34,7 @@
|
||||||
|
|
||||||
<changes>
|
<changes>
|
||||||
<release version="3.8-beta6" date="2012-??-??">
|
<release version="3.8-beta6" date="2012-??-??">
|
||||||
|
<action dev="poi-developers" type="add">52378 - Support for WORKDAY and NETWORKDAYS functions</action>
|
||||||
<action dev="poi-developers" type="add">52349 - Merge the logic between the TEXT function and DataFormatter</action>
|
<action dev="poi-developers" type="add">52349 - Merge the logic between the TEXT function and DataFormatter</action>
|
||||||
<action dev="poi-developers" type="fix">52349 - Correctly support excel style date format strings in the TEXT function</action>
|
<action dev="poi-developers" type="fix">52349 - Correctly support excel style date format strings in the TEXT function</action>
|
||||||
<action dev="poi-developers" type="fix">52369 - XSSFExcelExtractor should format numeric cells based on the format strings applied to them</action>
|
<action dev="poi-developers" type="fix">52369 - XSSFExcelExtractor should format numeric cells based on the format strings applied to them</action>
|
||||||
|
|
|
@ -1,19 +1,12 @@
|
||||||
/* ====================================================================
|
/*
|
||||||
Licensed to the Apache Software Foundation (ASF) under one or more
|
* ==================================================================== Licensed to the Apache Software Foundation (ASF) under one or more
|
||||||
contributor license agreements. See the NOTICE file distributed with
|
* contributor license agreements. See the NOTICE file distributed with this work for additional information regarding copyright ownership.
|
||||||
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
|
||||||
The ASF licenses this file to You under the Apache License, Version 2.0
|
* 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
|
||||||
(the "License"); you may not use this file except in compliance with
|
* agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
the License. You may obtain a copy of the License at
|
* KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
|
||||||
|
* ====================================================================
|
||||||
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.atp;
|
package org.apache.poi.ss.formula.atp;
|
||||||
|
|
||||||
|
@ -50,13 +43,12 @@ public final class AnalysisToolPak implements UDFFinder {
|
||||||
|
|
||||||
private final Map<String, FreeRefFunction> _functionsByName = createFunctionsMap();
|
private final Map<String, FreeRefFunction> _functionsByName = createFunctionsMap();
|
||||||
|
|
||||||
|
|
||||||
private AnalysisToolPak() {
|
private AnalysisToolPak() {
|
||||||
// enforce singleton
|
// enforce singleton
|
||||||
}
|
}
|
||||||
|
|
||||||
public FreeRefFunction findFunction(String name) {
|
public FreeRefFunction findFunction(String name) {
|
||||||
return _functionsByName.get(name);
|
return _functionsByName.get(name.toUpperCase());
|
||||||
}
|
}
|
||||||
|
|
||||||
private Map<String, FreeRefFunction> createFunctionsMap() {
|
private Map<String, FreeRefFunction> createFunctionsMap() {
|
||||||
|
@ -140,7 +132,7 @@ public final class AnalysisToolPak implements UDFFinder {
|
||||||
r(m, "MDURATION", null);
|
r(m, "MDURATION", null);
|
||||||
r(m, "MROUND", MRound.instance);
|
r(m, "MROUND", MRound.instance);
|
||||||
r(m, "MULTINOMIAL", null);
|
r(m, "MULTINOMIAL", null);
|
||||||
r(m, "NETWORKDAYS", null);
|
r(m, "NETWORKDAYS", NetworkdaysFunction.instance);
|
||||||
r(m, "NOMINAL", null);
|
r(m, "NOMINAL", null);
|
||||||
r(m, "OCT2BIN", null);
|
r(m, "OCT2BIN", null);
|
||||||
r(m, "OCT2DEC", null);
|
r(m, "OCT2DEC", null);
|
||||||
|
@ -163,7 +155,7 @@ public final class AnalysisToolPak implements UDFFinder {
|
||||||
r(m, "TBILLPRICE", null);
|
r(m, "TBILLPRICE", null);
|
||||||
r(m, "TBILLYIELD", null);
|
r(m, "TBILLYIELD", null);
|
||||||
r(m, "WEEKNUM", null);
|
r(m, "WEEKNUM", null);
|
||||||
r(m, "WORKDAY", null);
|
r(m, "WORKDAY", WorkdayFunction.instance);
|
||||||
r(m, "XIRR", null);
|
r(m, "XIRR", null);
|
||||||
r(m, "XNPV", null);
|
r(m, "XNPV", null);
|
||||||
r(m, "YEARFRAC", YearFrac.instance);
|
r(m, "YEARFRAC", YearFrac.instance);
|
||||||
|
|
|
@ -0,0 +1,117 @@
|
||||||
|
/* ====================================================================
|
||||||
|
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.atp;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Calendar;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.apache.poi.ss.formula.eval.AreaEvalBase;
|
||||||
|
import org.apache.poi.ss.formula.eval.EvaluationException;
|
||||||
|
import org.apache.poi.ss.formula.eval.OperandResolver;
|
||||||
|
import org.apache.poi.ss.formula.eval.StringEval;
|
||||||
|
import org.apache.poi.ss.formula.eval.ValueEval;
|
||||||
|
import org.apache.poi.ss.usermodel.DateUtil;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Evaluator for formula arguments.
|
||||||
|
*
|
||||||
|
* @author jfaenomoto@gmail.com
|
||||||
|
*/
|
||||||
|
final class ArgumentsEvaluator {
|
||||||
|
|
||||||
|
public static final ArgumentsEvaluator instance = new ArgumentsEvaluator();
|
||||||
|
|
||||||
|
private ArgumentsEvaluator() {
|
||||||
|
// enforces singleton
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Evaluate a generic {@link ValueEval} argument to a double value that represents a date in POI.
|
||||||
|
*
|
||||||
|
* @param arg {@link ValueEval} an argument.
|
||||||
|
* @param srcCellRow number cell row.
|
||||||
|
* @param srcCellCol number cell column.
|
||||||
|
* @return a double representing a date in POI.
|
||||||
|
* @throws EvaluationException exception upon argument evaluation.
|
||||||
|
*/
|
||||||
|
public double evaluateDateArg(ValueEval arg, int srcCellRow, int srcCellCol) throws EvaluationException {
|
||||||
|
ValueEval ve = OperandResolver.getSingleValue(arg, srcCellRow, (short) srcCellCol);
|
||||||
|
|
||||||
|
if (ve instanceof StringEval) {
|
||||||
|
String strVal = ((StringEval) ve).getStringValue();
|
||||||
|
Double dVal = OperandResolver.parseDouble(strVal);
|
||||||
|
if (dVal != null) {
|
||||||
|
return dVal.doubleValue();
|
||||||
|
}
|
||||||
|
Calendar date = DateParser.parseDate(strVal);
|
||||||
|
return DateUtil.getExcelDate(date, false);
|
||||||
|
}
|
||||||
|
return OperandResolver.coerceValueToDouble(ve);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Evaluate a generic {@link ValueEval} argument to an array of double values that represents dates in POI.
|
||||||
|
*
|
||||||
|
* @param arg {@link ValueEval} an argument.
|
||||||
|
* @param srcCellRow number cell row.
|
||||||
|
* @param srcCellCol number cell column.
|
||||||
|
* @return an array of doubles representing dates in POI.
|
||||||
|
* @throws EvaluationException exception upon argument evaluation.
|
||||||
|
*/
|
||||||
|
public double[] evaluateDatesArg(ValueEval arg, int srcCellRow, int srcCellCol) throws EvaluationException {
|
||||||
|
if (arg == null) {
|
||||||
|
return new double[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (arg instanceof StringEval) {
|
||||||
|
return new double[]{ evaluateDateArg(arg, srcCellRow, srcCellCol) };
|
||||||
|
} else if (arg instanceof AreaEvalBase) {
|
||||||
|
List<Double> valuesList = new ArrayList<Double>();
|
||||||
|
AreaEvalBase area = (AreaEvalBase) arg;
|
||||||
|
for (int i = area.getFirstRow(); i <= area.getLastRow(); i++) {
|
||||||
|
for (int j = area.getFirstColumn(); j <= area.getLastColumn(); j++) {
|
||||||
|
valuesList.add(evaluateDateArg(area.getValue(i, j), i, j));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
double[] values = new double[valuesList.size()];
|
||||||
|
for (int i = 0; i < valuesList.size(); i++) {
|
||||||
|
values[i] = valuesList.get(i).doubleValue();
|
||||||
|
}
|
||||||
|
return values;
|
||||||
|
}
|
||||||
|
return new double[]{ OperandResolver.coerceValueToDouble(arg) };
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Evaluate a generic {@link ValueEval} argument to a double value.
|
||||||
|
*
|
||||||
|
* @param arg {@link ValueEval} an argument.
|
||||||
|
* @param srcCellRow number cell row.
|
||||||
|
* @param srcCellCol number cell column.
|
||||||
|
* @return a double value.
|
||||||
|
* @throws EvaluationException exception upon argument evaluation.
|
||||||
|
*/
|
||||||
|
public double evaluateNumberArg(ValueEval arg, int srcCellRow, int srcCellCol) throws EvaluationException {
|
||||||
|
if (arg == null) {
|
||||||
|
return 0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
return OperandResolver.coerceValueToDouble(arg);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,102 @@
|
||||||
|
/* ====================================================================
|
||||||
|
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.atp;
|
||||||
|
|
||||||
|
import java.util.Calendar;
|
||||||
|
import java.util.GregorianCalendar;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
import org.apache.poi.ss.formula.eval.ErrorEval;
|
||||||
|
import org.apache.poi.ss.formula.eval.EvaluationException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parser for java dates.
|
||||||
|
*
|
||||||
|
* @author jfaenomoto@gmail.com
|
||||||
|
*/
|
||||||
|
public class DateParser {
|
||||||
|
|
||||||
|
public DateParser instance = new DateParser();
|
||||||
|
|
||||||
|
private DateParser() {
|
||||||
|
// enforcing singleton
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parses a date from a string.
|
||||||
|
*
|
||||||
|
* @param strVal a string with a date pattern.
|
||||||
|
* @return a date parsed from argument.
|
||||||
|
* @throws EvaluationException exception upon parsing.
|
||||||
|
*/
|
||||||
|
public static Calendar parseDate(String strVal) throws EvaluationException {
|
||||||
|
String[] parts = Pattern.compile("/").split(strVal);
|
||||||
|
if (parts.length != 3) {
|
||||||
|
throw new EvaluationException(ErrorEval.VALUE_INVALID);
|
||||||
|
}
|
||||||
|
String part2 = parts[2];
|
||||||
|
int spacePos = part2.indexOf(' ');
|
||||||
|
if (spacePos > 0) {
|
||||||
|
// drop time portion if present
|
||||||
|
part2 = part2.substring(0, spacePos);
|
||||||
|
}
|
||||||
|
int f0;
|
||||||
|
int f1;
|
||||||
|
int f2;
|
||||||
|
try {
|
||||||
|
f0 = Integer.parseInt(parts[0]);
|
||||||
|
f1 = Integer.parseInt(parts[1]);
|
||||||
|
f2 = Integer.parseInt(part2);
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
throw new EvaluationException(ErrorEval.VALUE_INVALID);
|
||||||
|
}
|
||||||
|
if (f0 < 0 || f1 < 0 || f2 < 0 || (f0 > 12 && f1 > 12 && f2 > 12)) {
|
||||||
|
// easy to see this cannot be a valid date
|
||||||
|
throw new EvaluationException(ErrorEval.VALUE_INVALID);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (f0 >= 1900 && f0 < 9999) {
|
||||||
|
// when 4 digit value appears first, the format is YYYY/MM/DD, regardless of OS settings
|
||||||
|
return makeDate(f0, f1, f2);
|
||||||
|
}
|
||||||
|
// otherwise the format seems to depend on OS settings (default date format)
|
||||||
|
if (false) {
|
||||||
|
// MM/DD/YYYY is probably a good guess, if the in the US
|
||||||
|
return makeDate(f2, f0, f1);
|
||||||
|
}
|
||||||
|
// TODO - find a way to choose the correct date format
|
||||||
|
throw new RuntimeException("Unable to determine date format for text '" + strVal + "'");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param month 1-based
|
||||||
|
*/
|
||||||
|
private static Calendar makeDate(int year, int month, int day) throws EvaluationException {
|
||||||
|
if (month < 1 || month > 12) {
|
||||||
|
throw new EvaluationException(ErrorEval.VALUE_INVALID);
|
||||||
|
}
|
||||||
|
Calendar cal = new GregorianCalendar(year, month - 1, 1, 0, 0, 0);
|
||||||
|
cal.set(Calendar.MILLISECOND, 0);
|
||||||
|
if (day < 1 || day > cal.getActualMaximum(Calendar.DAY_OF_MONTH)) {
|
||||||
|
throw new EvaluationException(ErrorEval.VALUE_INVALID);
|
||||||
|
}
|
||||||
|
cal.set(Calendar.DAY_OF_MONTH, day);
|
||||||
|
return cal;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -21,11 +21,6 @@ import org.apache.poi.ss.formula.OperationEvaluationContext;
|
||||||
import org.apache.poi.ss.formula.eval.*;
|
import org.apache.poi.ss.formula.eval.*;
|
||||||
import org.apache.poi.ss.formula.functions.FreeRefFunction;
|
import org.apache.poi.ss.formula.functions.FreeRefFunction;
|
||||||
import org.apache.poi.ss.formula.functions.NumericFunction;
|
import org.apache.poi.ss.formula.functions.NumericFunction;
|
||||||
import org.apache.poi.ss.usermodel.DateUtil;
|
|
||||||
|
|
||||||
import java.util.Calendar;
|
|
||||||
import java.util.GregorianCalendar;
|
|
||||||
import java.util.regex.Pattern;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Implementation of Excel 'Analysis ToolPak' function MROUND()<br/>
|
* Implementation of Excel 'Analysis ToolPak' function MROUND()<br/>
|
||||||
|
|
|
@ -0,0 +1,84 @@
|
||||||
|
/* ====================================================================
|
||||||
|
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.atp;
|
||||||
|
|
||||||
|
import org.apache.poi.ss.formula.OperationEvaluationContext;
|
||||||
|
import org.apache.poi.ss.formula.eval.ErrorEval;
|
||||||
|
import org.apache.poi.ss.formula.eval.EvaluationException;
|
||||||
|
import org.apache.poi.ss.formula.eval.NumberEval;
|
||||||
|
import org.apache.poi.ss.formula.eval.ValueEval;
|
||||||
|
import org.apache.poi.ss.formula.functions.FreeRefFunction;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implementation of Excel 'Analysis ToolPak' function NETWORKDAYS()<br/>
|
||||||
|
* Returns the number of workdays given a starting and an ending date, considering an interval of holidays. A workday is any non
|
||||||
|
* saturday/sunday date.
|
||||||
|
* <p/>
|
||||||
|
* <b>Syntax</b><br/>
|
||||||
|
* <b>NETWORKDAYS</b>(<b>startDate</b>, <b>endDate</b>, holidays)
|
||||||
|
* <p/>
|
||||||
|
*
|
||||||
|
* @author jfaenomoto@gmail.com
|
||||||
|
*/
|
||||||
|
final class NetworkdaysFunction implements FreeRefFunction {
|
||||||
|
|
||||||
|
public static final FreeRefFunction instance = new NetworkdaysFunction(ArgumentsEvaluator.instance);
|
||||||
|
|
||||||
|
private ArgumentsEvaluator evaluator;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor.
|
||||||
|
*
|
||||||
|
* @param anEvaluator an injected {@link ArgumentsEvaluator}.
|
||||||
|
*/
|
||||||
|
private NetworkdaysFunction(ArgumentsEvaluator anEvaluator) {
|
||||||
|
// enforces singleton
|
||||||
|
this.evaluator = anEvaluator;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Evaluate for NETWORKDAYS. Given two dates and a optional date or interval of holidays, determines how many working days are there
|
||||||
|
* between those dates.
|
||||||
|
*
|
||||||
|
* @return {@link ValueEval} for the number of days between two dates.
|
||||||
|
*/
|
||||||
|
public ValueEval evaluate(ValueEval[] args, OperationEvaluationContext ec) {
|
||||||
|
if (args.length < 2 || args.length > 3) {
|
||||||
|
return ErrorEval.VALUE_INVALID;
|
||||||
|
}
|
||||||
|
|
||||||
|
int srcCellRow = ec.getRowIndex();
|
||||||
|
int srcCellCol = ec.getColumnIndex();
|
||||||
|
|
||||||
|
double start, end;
|
||||||
|
double[] holidays;
|
||||||
|
try {
|
||||||
|
start = this.evaluator.evaluateDateArg(args[0], srcCellRow, srcCellCol);
|
||||||
|
end = this.evaluator.evaluateDateArg(args[1], srcCellRow, srcCellCol);
|
||||||
|
if (start > end) {
|
||||||
|
return ErrorEval.NAME_INVALID;
|
||||||
|
}
|
||||||
|
ValueEval holidaysCell = args.length == 3 ? args[2] : null;
|
||||||
|
holidays = this.evaluator.evaluateDatesArg(holidaysCell, srcCellRow, srcCellCol);
|
||||||
|
return new NumberEval(WorkdayCalculator.instance.calculateWorkdays(start, end, holidays));
|
||||||
|
} catch (EvaluationException e) {
|
||||||
|
return ErrorEval.VALUE_INVALID;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,168 @@
|
||||||
|
/* ====================================================================
|
||||||
|
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.atp;
|
||||||
|
|
||||||
|
import java.util.Calendar;
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
import org.apache.poi.ss.usermodel.DateUtil;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A calculator for workdays, considering dates as excel representations.
|
||||||
|
*
|
||||||
|
* @author jfaenomoto@gmail.com
|
||||||
|
*/
|
||||||
|
public class WorkdayCalculator {
|
||||||
|
|
||||||
|
public static final WorkdayCalculator instance = new WorkdayCalculator();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor.
|
||||||
|
*/
|
||||||
|
private WorkdayCalculator() {
|
||||||
|
// enforcing singleton
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculate how many workdays are there between a start and an end date, as excel representations, considering a range of holidays.
|
||||||
|
*
|
||||||
|
* @param start start date.
|
||||||
|
* @param end end date.
|
||||||
|
* @param holidays an array of holidays.
|
||||||
|
* @return number of workdays between start and end dates, including both dates.
|
||||||
|
*/
|
||||||
|
public int calculateWorkdays(double start, double end, double[] holidays) {
|
||||||
|
int saturdaysPast = this.pastDaysOfWeek(start, end, Calendar.SATURDAY);
|
||||||
|
int sundaysPast = this.pastDaysOfWeek(start, end, Calendar.SUNDAY);
|
||||||
|
int nonWeekendHolidays = this.calculateNonWeekendHolidays(start, end, holidays);
|
||||||
|
return (int) (end - start + 1) - saturdaysPast - sundaysPast - nonWeekendHolidays;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculate the workday past x workdays from a starting date, considering a range of holidays.
|
||||||
|
*
|
||||||
|
* @param start start date.
|
||||||
|
* @param workdays number of workdays to be past from starting date.
|
||||||
|
* @param holidays an array of holidays.
|
||||||
|
* @return date past x workdays.
|
||||||
|
*/
|
||||||
|
public Date calculateWorkdays(double start, int workdays, double[] holidays) {
|
||||||
|
Date startDate = DateUtil.getJavaDate(start);
|
||||||
|
Calendar endDate = Calendar.getInstance();
|
||||||
|
endDate.setTime(startDate);
|
||||||
|
endDate.add(Calendar.DAY_OF_YEAR, workdays);
|
||||||
|
int skippedDays = 0;
|
||||||
|
do {
|
||||||
|
double end = DateUtil.getExcelDate(endDate.getTime());
|
||||||
|
int saturdaysPast = this.pastDaysOfWeek(start, end, Calendar.SATURDAY);
|
||||||
|
int sundaysPast = this.pastDaysOfWeek(start, end, Calendar.SUNDAY);
|
||||||
|
int nonWeekendHolidays = this.calculateNonWeekendHolidays(start, end, holidays);
|
||||||
|
skippedDays = saturdaysPast + sundaysPast + nonWeekendHolidays;
|
||||||
|
endDate.add(Calendar.DAY_OF_YEAR, skippedDays);
|
||||||
|
start = end + isNonWorkday(end, holidays);
|
||||||
|
} while (skippedDays != 0);
|
||||||
|
return endDate.getTime();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculates how many days of week past between a start and an end date.
|
||||||
|
*
|
||||||
|
* @param start start date.
|
||||||
|
* @param end end date.
|
||||||
|
* @param dayOfWeek a day of week as represented by {@link Calendar} constants.
|
||||||
|
* @return how many days of week past in this interval.
|
||||||
|
*/
|
||||||
|
protected int pastDaysOfWeek(double start, double end, int dayOfWeek) {
|
||||||
|
int pastDaysOfWeek = 0;
|
||||||
|
int startDay = (int) Math.floor(start < end ? start : end);
|
||||||
|
int endDay = (int) Math.floor(end > start ? end : start);
|
||||||
|
for (; startDay <= endDay; startDay++) {
|
||||||
|
Calendar today = Calendar.getInstance();
|
||||||
|
today.setTime(DateUtil.getJavaDate(startDay));
|
||||||
|
if (today.get(Calendar.DAY_OF_WEEK) == dayOfWeek) {
|
||||||
|
pastDaysOfWeek++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return start < end ? pastDaysOfWeek : -pastDaysOfWeek;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculates how many holidays in a list are workdays, considering an interval of dates.
|
||||||
|
*
|
||||||
|
* @param start start date.
|
||||||
|
* @param end end date.
|
||||||
|
* @param holidays an array of holidays.
|
||||||
|
* @return number of holidays that occur in workdays, between start and end dates.
|
||||||
|
*/
|
||||||
|
protected int calculateNonWeekendHolidays(double start, double end, double[] holidays) {
|
||||||
|
int nonWeekendHolidays = 0;
|
||||||
|
double startDay = start < end ? start : end;
|
||||||
|
double endDay = end > start ? end : start;
|
||||||
|
for (int i = 0; i < holidays.length; i++) {
|
||||||
|
if (isInARange(startDay, endDay, holidays[i])) {
|
||||||
|
if (!isWeekend(holidays[i])) {
|
||||||
|
nonWeekendHolidays++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return start < end ? nonWeekendHolidays : -nonWeekendHolidays;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param aDate a given date.
|
||||||
|
* @return <code>true</code> if date is weekend, <code>false</code> otherwise.
|
||||||
|
*/
|
||||||
|
protected boolean isWeekend(double aDate) {
|
||||||
|
Calendar date = Calendar.getInstance();
|
||||||
|
date.setTime(DateUtil.getJavaDate(aDate));
|
||||||
|
return date.get(Calendar.DAY_OF_WEEK) == Calendar.SATURDAY || date.get(Calendar.DAY_OF_WEEK) == Calendar.SUNDAY;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param aDate a given date.
|
||||||
|
* @param holidays an array of holidays.
|
||||||
|
* @return <code>true</code> if date is a holiday, <code>false</code> otherwise.
|
||||||
|
*/
|
||||||
|
protected boolean isHoliday(double aDate, double[] holidays) {
|
||||||
|
for (int i = 0; i < holidays.length; i++) {
|
||||||
|
if (Math.round(holidays[i]) == Math.round(aDate)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param aDate a given date.
|
||||||
|
* @param holidays an array of holidays.
|
||||||
|
* @return <code>1</code> is not a workday, <code>0</code> otherwise.
|
||||||
|
*/
|
||||||
|
protected int isNonWorkday(double aDate, double[] holidays) {
|
||||||
|
return isWeekend(aDate) || isHoliday(aDate, holidays) ? 1 : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param start start date.
|
||||||
|
* @param end end date.
|
||||||
|
* @param aDate a date to be analyzed.
|
||||||
|
* @return <code>true</code> if aDate is between start and end dates, <code>false</code> otherwise.
|
||||||
|
*/
|
||||||
|
protected boolean isInARange(double start, double end, double aDate) {
|
||||||
|
return aDate >= start && aDate <= end;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,78 @@
|
||||||
|
/* ====================================================================
|
||||||
|
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.atp;
|
||||||
|
|
||||||
|
import org.apache.poi.ss.formula.OperationEvaluationContext;
|
||||||
|
import org.apache.poi.ss.formula.eval.ErrorEval;
|
||||||
|
import org.apache.poi.ss.formula.eval.EvaluationException;
|
||||||
|
import org.apache.poi.ss.formula.eval.NumberEval;
|
||||||
|
import org.apache.poi.ss.formula.eval.ValueEval;
|
||||||
|
import org.apache.poi.ss.formula.functions.FreeRefFunction;
|
||||||
|
import org.apache.poi.ss.usermodel.DateUtil;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implementation of Excel 'Analysis ToolPak' function WORKDAY()<br/>
|
||||||
|
* Returns the date past a number of workdays beginning at a start date, considering an interval of holidays. A workday is any non
|
||||||
|
* saturday/sunday date.
|
||||||
|
* <p/>
|
||||||
|
* <b>Syntax</b><br/>
|
||||||
|
* <b>WORKDAY</b>(<b>startDate</b>, <b>days</b>, holidays)
|
||||||
|
* <p/>
|
||||||
|
*
|
||||||
|
* @author jfaenomoto@gmail.com
|
||||||
|
*/
|
||||||
|
final class WorkdayFunction implements FreeRefFunction {
|
||||||
|
|
||||||
|
public static final FreeRefFunction instance = new WorkdayFunction(ArgumentsEvaluator.instance);
|
||||||
|
|
||||||
|
private ArgumentsEvaluator evaluator;
|
||||||
|
|
||||||
|
private WorkdayFunction(ArgumentsEvaluator anEvaluator) {
|
||||||
|
// enforces singleton
|
||||||
|
this.evaluator = anEvaluator;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Evaluate for WORKDAY. Given a date, a number of days and a optional date or interval of holidays, determines which date it is past
|
||||||
|
* number of parametrized workdays.
|
||||||
|
*
|
||||||
|
* @return {@link ValueEval} with date as its value.
|
||||||
|
*/
|
||||||
|
public ValueEval evaluate(ValueEval[] args, OperationEvaluationContext ec) {
|
||||||
|
if (args.length < 2 || args.length > 3) {
|
||||||
|
return ErrorEval.VALUE_INVALID;
|
||||||
|
}
|
||||||
|
|
||||||
|
int srcCellRow = ec.getRowIndex();
|
||||||
|
int srcCellCol = ec.getColumnIndex();
|
||||||
|
|
||||||
|
double start;
|
||||||
|
int days;
|
||||||
|
double[] holidays;
|
||||||
|
try {
|
||||||
|
start = this.evaluator.evaluateDateArg(args[0], srcCellRow, srcCellCol);
|
||||||
|
days = (int) Math.floor(this.evaluator.evaluateNumberArg(args[1], srcCellRow, srcCellCol));
|
||||||
|
ValueEval holidaysCell = args.length == 3 ? args[2] : null;
|
||||||
|
holidays = this.evaluator.evaluateDatesArg(holidaysCell, srcCellRow, srcCellCol);
|
||||||
|
return new NumberEval(DateUtil.getExcelDate(WorkdayCalculator.instance.calculateWorkdays(start, days, holidays)));
|
||||||
|
} catch (EvaluationException e) {
|
||||||
|
return ErrorEval.VALUE_INVALID;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -91,67 +91,12 @@ final class YearFrac implements FreeRefFunction {
|
||||||
if (dVal != null) {
|
if (dVal != null) {
|
||||||
return dVal.doubleValue();
|
return dVal.doubleValue();
|
||||||
}
|
}
|
||||||
Calendar date = parseDate(strVal);
|
Calendar date = DateParser.parseDate(strVal);
|
||||||
return DateUtil.getExcelDate(date, false);
|
return DateUtil.getExcelDate(date, false);
|
||||||
}
|
}
|
||||||
return OperandResolver.coerceValueToDouble(ve);
|
return OperandResolver.coerceValueToDouble(ve);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Calendar parseDate(String strVal) throws EvaluationException {
|
|
||||||
String[] parts = Pattern.compile("/").split(strVal);
|
|
||||||
if (parts.length != 3) {
|
|
||||||
throw new EvaluationException(ErrorEval.VALUE_INVALID);
|
|
||||||
}
|
|
||||||
String part2 = parts[2];
|
|
||||||
int spacePos = part2.indexOf(' ');
|
|
||||||
if (spacePos > 0) {
|
|
||||||
// drop time portion if present
|
|
||||||
part2 = part2.substring(0, spacePos);
|
|
||||||
}
|
|
||||||
int f0;
|
|
||||||
int f1;
|
|
||||||
int f2;
|
|
||||||
try {
|
|
||||||
f0 = Integer.parseInt(parts[0]);
|
|
||||||
f1 = Integer.parseInt(parts[1]);
|
|
||||||
f2 = Integer.parseInt(part2);
|
|
||||||
} catch (NumberFormatException e) {
|
|
||||||
throw new EvaluationException(ErrorEval.VALUE_INVALID);
|
|
||||||
}
|
|
||||||
if (f0<0 || f1<0 || f2<0 || (f0>12 && f1>12 && f2>12)) {
|
|
||||||
// easy to see this cannot be a valid date
|
|
||||||
throw new EvaluationException(ErrorEval.VALUE_INVALID);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (f0 >= 1900 && f0 < 9999) {
|
|
||||||
// when 4 digit value appears first, the format is YYYY/MM/DD, regardless of OS settings
|
|
||||||
return makeDate(f0, f1, f2);
|
|
||||||
}
|
|
||||||
// otherwise the format seems to depend on OS settings (default date format)
|
|
||||||
if (false) {
|
|
||||||
// MM/DD/YYYY is probably a good guess, if the in the US
|
|
||||||
return makeDate(f2, f0, f1);
|
|
||||||
}
|
|
||||||
// TODO - find a way to choose the correct date format
|
|
||||||
throw new RuntimeException("Unable to determine date format for text '" + strVal + "'");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param month 1-based
|
|
||||||
*/
|
|
||||||
private static Calendar makeDate(int year, int month, int day) throws EvaluationException {
|
|
||||||
if (month < 1 || month > 12) {
|
|
||||||
throw new EvaluationException(ErrorEval.VALUE_INVALID);
|
|
||||||
}
|
|
||||||
Calendar cal = new GregorianCalendar(year, month-1, 1, 0, 0, 0);
|
|
||||||
cal.set(Calendar.MILLISECOND, 0);
|
|
||||||
if (day <1 || day>cal.getActualMaximum(Calendar.DAY_OF_MONTH)) {
|
|
||||||
throw new EvaluationException(ErrorEval.VALUE_INVALID);
|
|
||||||
}
|
|
||||||
cal.set(Calendar.DAY_OF_MONTH, day);
|
|
||||||
return cal;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static int evaluateIntArg(ValueEval arg, int srcCellRow, int srcCellCol) throws EvaluationException {
|
private static int evaluateIntArg(ValueEval arg, int srcCellRow, int srcCellCol) throws EvaluationException {
|
||||||
ValueEval ve = OperandResolver.getSingleValue(arg, srcCellRow, (short) srcCellCol);
|
ValueEval ve = OperandResolver.getSingleValue(arg, srcCellRow, (short) srcCellCol);
|
||||||
return OperandResolver.coerceValueToInt(ve);
|
return OperandResolver.coerceValueToInt(ve);
|
||||||
|
|
|
@ -0,0 +1,74 @@
|
||||||
|
/* ====================================================================
|
||||||
|
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.atp;
|
||||||
|
|
||||||
|
import static java.util.Calendar.OCTOBER;
|
||||||
|
|
||||||
|
import java.util.Calendar;
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
import junit.framework.TestCase;
|
||||||
|
|
||||||
|
import org.apache.poi.ss.formula.eval.ErrorEval;
|
||||||
|
import org.apache.poi.ss.formula.eval.EvaluationException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author jfaenomoto@gmail.com
|
||||||
|
*/
|
||||||
|
public class TestDateParser extends TestCase {
|
||||||
|
|
||||||
|
public void testFailWhenNoDate() {
|
||||||
|
try {
|
||||||
|
DateParser.parseDate("potato");
|
||||||
|
fail("Shouldn't parse potato!");
|
||||||
|
} catch (EvaluationException e) {
|
||||||
|
assertEquals(ErrorEval.VALUE_INVALID, e.getErrorEval());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testFailWhenLooksLikeDateButItIsnt() {
|
||||||
|
try {
|
||||||
|
DateParser.parseDate("potato/cucumber/banana");
|
||||||
|
fail("Shouldn't parse this thing!");
|
||||||
|
} catch (EvaluationException e) {
|
||||||
|
assertEquals(ErrorEval.VALUE_INVALID, e.getErrorEval());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testFailWhenIsInvalidDate() {
|
||||||
|
try {
|
||||||
|
DateParser.parseDate("13/13/13");
|
||||||
|
fail("Shouldn't parse this thing!");
|
||||||
|
} catch (EvaluationException e) {
|
||||||
|
assertEquals(ErrorEval.VALUE_INVALID, e.getErrorEval());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testShouldParseValidDate() throws EvaluationException {
|
||||||
|
Calendar aDate = Calendar.getInstance();
|
||||||
|
aDate.setTime(new Date(84, OCTOBER, 20));
|
||||||
|
assertEquals(aDate, DateParser.parseDate("1984/10/20"));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testShouldIgnoreTimestamp() throws EvaluationException {
|
||||||
|
Calendar aDate = Calendar.getInstance();
|
||||||
|
aDate.setTime(new Date(84, OCTOBER, 20));
|
||||||
|
assertEquals(aDate, DateParser.parseDate("1984/10/20 12:34:56"));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,135 @@
|
||||||
|
/* ====================================================================
|
||||||
|
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.atp;
|
||||||
|
|
||||||
|
import static java.util.Calendar.DECEMBER;
|
||||||
|
import static java.util.Calendar.JANUARY;
|
||||||
|
import static java.util.Calendar.MARCH;
|
||||||
|
import static java.util.Calendar.NOVEMBER;
|
||||||
|
import static java.util.Calendar.OCTOBER;
|
||||||
|
import static org.apache.poi.ss.formula.eval.ErrorEval.NAME_INVALID;
|
||||||
|
import static org.apache.poi.ss.formula.eval.ErrorEval.VALUE_INVALID;
|
||||||
|
|
||||||
|
import java.text.SimpleDateFormat;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import junit.framework.TestCase;
|
||||||
|
|
||||||
|
import org.apache.poi.ss.formula.OperationEvaluationContext;
|
||||||
|
import org.apache.poi.ss.formula.TwoDEval;
|
||||||
|
import org.apache.poi.ss.formula.eval.AreaEval;
|
||||||
|
import org.apache.poi.ss.formula.eval.AreaEvalBase;
|
||||||
|
import org.apache.poi.ss.formula.eval.NumericValueEval;
|
||||||
|
import org.apache.poi.ss.formula.eval.StringEval;
|
||||||
|
import org.apache.poi.ss.formula.eval.ValueEval;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author jfaenomoto@gmail.com
|
||||||
|
*/
|
||||||
|
public class TestNetworkdaysFunction extends TestCase {
|
||||||
|
|
||||||
|
private static final SimpleDateFormat formatter = new SimpleDateFormat("yyyy/MM/dd");
|
||||||
|
|
||||||
|
private static final String STARTING_DATE = formatter.format(new Date(108, OCTOBER, 1));
|
||||||
|
|
||||||
|
private static final String END_DATE = formatter.format(new Date(109, MARCH, 1));
|
||||||
|
|
||||||
|
private static final String FIRST_HOLIDAY = formatter.format(new Date(108, NOVEMBER, 26));
|
||||||
|
|
||||||
|
private static final String SECOND_HOLIDAY = formatter.format(new Date(108, DECEMBER, 4));
|
||||||
|
|
||||||
|
private static final String THIRD_HOLIDAY = formatter.format(new Date(109, JANUARY, 21));
|
||||||
|
|
||||||
|
private static final OperationEvaluationContext EC = new OperationEvaluationContext(null, null, 1, 1, 1, null);
|
||||||
|
|
||||||
|
public void testFailWhenNoArguments() {
|
||||||
|
assertEquals(VALUE_INVALID, NetworkdaysFunction.instance.evaluate(new ValueEval[0], null));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testFailWhenLessThan2Arguments() {
|
||||||
|
assertEquals(VALUE_INVALID, NetworkdaysFunction.instance.evaluate(new ValueEval[1], null));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testFailWhenMoreThan3Arguments() {
|
||||||
|
assertEquals(VALUE_INVALID, NetworkdaysFunction.instance.evaluate(new ValueEval[4], null));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testFailWhenArgumentsAreNotDates() {
|
||||||
|
assertEquals(VALUE_INVALID, NetworkdaysFunction.instance.evaluate(new ValueEval[]{ new StringEval("Potato"),
|
||||||
|
new StringEval("Cucumber") }, EC));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testFailWhenStartDateAfterEndDate() {
|
||||||
|
assertEquals(NAME_INVALID, NetworkdaysFunction.instance.evaluate(new ValueEval[]{ new StringEval(END_DATE.toString()),
|
||||||
|
new StringEval(STARTING_DATE.toString()) }, EC));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testReturnNetworkdays() {
|
||||||
|
assertEquals(108, (int) ((NumericValueEval) NetworkdaysFunction.instance.evaluate(new ValueEval[]{
|
||||||
|
new StringEval(STARTING_DATE.toString()), new StringEval(END_DATE.toString()) }, EC)).getNumberValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testReturnNetworkdaysWithAHoliday() {
|
||||||
|
assertEquals(107, (int) ((NumericValueEval) NetworkdaysFunction.instance.evaluate(new ValueEval[]{
|
||||||
|
new StringEval(STARTING_DATE.toString()), new StringEval(END_DATE.toString()), new StringEval(FIRST_HOLIDAY.toString()) },
|
||||||
|
EC)).getNumberValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testReturnNetworkdaysWithManyHolidays() {
|
||||||
|
assertEquals(105, (int) ((NumericValueEval) NetworkdaysFunction.instance.evaluate(new ValueEval[]{
|
||||||
|
new StringEval(STARTING_DATE.toString()), new StringEval(END_DATE.toString()),
|
||||||
|
new MockAreaEval(FIRST_HOLIDAY, SECOND_HOLIDAY, THIRD_HOLIDAY) }, EC)).getNumberValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
private class MockAreaEval extends AreaEvalBase {
|
||||||
|
|
||||||
|
private List<ValueEval> holidays;
|
||||||
|
|
||||||
|
public MockAreaEval(String... holidays) {
|
||||||
|
this(0, 0, 0, holidays.length - 1);
|
||||||
|
this.holidays = new ArrayList<ValueEval>();
|
||||||
|
for (String holiday : holidays) {
|
||||||
|
this.holidays.add(new StringEval(holiday));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected MockAreaEval(int firstRow, int firstColumn, int lastRow, int lastColumn) {
|
||||||
|
super(firstRow, firstColumn, lastRow, lastColumn);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ValueEval getRelativeValue(int relativeRowIndex, int relativeColumnIndex) {
|
||||||
|
return this.holidays.get(relativeColumnIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
public AreaEval offset(int relFirstRowIx, int relLastRowIx, int relFirstColIx, int relLastColIx) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public TwoDEval getColumn(int columnIndex) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public TwoDEval getRow(int rowIndex) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,96 @@
|
||||||
|
/* ====================================================================
|
||||||
|
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.atp;
|
||||||
|
|
||||||
|
import static java.util.Calendar.DECEMBER;
|
||||||
|
import static java.util.Calendar.SATURDAY;
|
||||||
|
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
import junit.framework.TestCase;
|
||||||
|
|
||||||
|
import org.apache.poi.ss.usermodel.DateUtil;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author jfaenomoto@gmail.com
|
||||||
|
*/
|
||||||
|
public class TestWorkdayCalculator extends TestCase {
|
||||||
|
|
||||||
|
public void testCalculateWorkdaysShouldReturnJustWeekdaysWhenNoWeekend() {
|
||||||
|
final double A_MONDAY = DateUtil.getExcelDate(new Date(111, DECEMBER, 12));
|
||||||
|
final double A_FRIDAY = DateUtil.getExcelDate(new Date(111, DECEMBER, 16));
|
||||||
|
assertEquals(5, WorkdayCalculator.instance.calculateWorkdays(A_MONDAY, A_FRIDAY, new double[0]));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testCalculateWorkdaysShouldReturnAllDaysButNoSaturdays() {
|
||||||
|
final double A_WEDNESDAY = DateUtil.getExcelDate(new Date(111, DECEMBER, 14));
|
||||||
|
final double A_SATURDAY = DateUtil.getExcelDate(new Date(111, DECEMBER, 18));
|
||||||
|
assertEquals(3, WorkdayCalculator.instance.calculateWorkdays(A_WEDNESDAY, A_SATURDAY, new double[0]));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testCalculateWorkdaysShouldReturnAllDaysButNoSundays() {
|
||||||
|
final double A_SUNDAY = DateUtil.getExcelDate(new Date(111, DECEMBER, 11));
|
||||||
|
final double A_THURSDAY = DateUtil.getExcelDate(new Date(111, DECEMBER, 15));
|
||||||
|
assertEquals(4, WorkdayCalculator.instance.calculateWorkdays(A_SUNDAY, A_THURSDAY, new double[0]));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testCalculateWorkdaysShouldReturnAllDaysButNoHolidays() {
|
||||||
|
final double A_MONDAY = DateUtil.getExcelDate(new Date(111, DECEMBER, 12));
|
||||||
|
final double A_FRIDAY = DateUtil.getExcelDate(new Date(111, DECEMBER, 16));
|
||||||
|
final double A_WEDNESDAY = DateUtil.getExcelDate(new Date(111, DECEMBER, 14));
|
||||||
|
assertEquals(4, WorkdayCalculator.instance.calculateWorkdays(A_MONDAY, A_FRIDAY, new double[]{ A_WEDNESDAY }));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testCalculateWorkdaysShouldIgnoreWeekendHolidays() {
|
||||||
|
final double A_FRIDAY = DateUtil.getExcelDate(new Date(111, DECEMBER, 16));
|
||||||
|
final double A_SATURDAY = DateUtil.getExcelDate(new Date(111, DECEMBER, 17));
|
||||||
|
final double A_SUNDAY = DateUtil.getExcelDate(new Date(111, DECEMBER, 18));
|
||||||
|
final double A_WEDNESDAY = DateUtil.getExcelDate(new Date(111, DECEMBER, 21));
|
||||||
|
assertEquals(4, WorkdayCalculator.instance.calculateWorkdays(A_FRIDAY, A_WEDNESDAY, new double[]{ A_SATURDAY, A_SUNDAY }));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testPastDaysOfWeekShouldReturn0Past0Saturdays() {
|
||||||
|
final double A_WEDNESDAY = DateUtil.getExcelDate(new Date(111, DECEMBER, 7));
|
||||||
|
final double A_FRIDAY = DateUtil.getExcelDate(new Date(111, DECEMBER, 9));
|
||||||
|
assertEquals(0, WorkdayCalculator.instance.pastDaysOfWeek(A_WEDNESDAY, A_FRIDAY, SATURDAY));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testPastDaysOfWeekShouldReturn1Past1Saturdays() {
|
||||||
|
final double A_WEDNESDAY = DateUtil.getExcelDate(new Date(111, DECEMBER, 7));
|
||||||
|
final double A_SUNDAY = DateUtil.getExcelDate(new Date(111, DECEMBER, 11));
|
||||||
|
assertEquals(1, WorkdayCalculator.instance.pastDaysOfWeek(A_WEDNESDAY, A_SUNDAY, SATURDAY));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testPastDaysOfWeekShouldReturn2Past2Saturdays() {
|
||||||
|
final double A_THURSDAY = DateUtil.getExcelDate(new Date(111, DECEMBER, 8));
|
||||||
|
final double A_MONDAY = DateUtil.getExcelDate(new Date(111, DECEMBER, 19));
|
||||||
|
assertEquals(2, WorkdayCalculator.instance.pastDaysOfWeek(A_THURSDAY, A_MONDAY, SATURDAY));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testPastDaysOfWeekShouldReturn1BeginningFromASaturday() {
|
||||||
|
final double A_SATURDAY = DateUtil.getExcelDate(new Date(111, DECEMBER, 10));
|
||||||
|
final double A_SUNDAY = DateUtil.getExcelDate(new Date(111, DECEMBER, 11));
|
||||||
|
assertEquals(1, WorkdayCalculator.instance.pastDaysOfWeek(A_SATURDAY, A_SUNDAY, SATURDAY));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testPastDaysOfWeekShouldReturn1EndingAtASaturday() {
|
||||||
|
final double A_THURSDAY = DateUtil.getExcelDate(new Date(111, DECEMBER, 8));
|
||||||
|
final double A_SATURDAY = DateUtil.getExcelDate(new Date(111, DECEMBER, 10));
|
||||||
|
assertEquals(1, WorkdayCalculator.instance.pastDaysOfWeek(A_THURSDAY, A_SATURDAY, SATURDAY));
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,140 @@
|
||||||
|
/* ====================================================================
|
||||||
|
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.atp;
|
||||||
|
|
||||||
|
import static java.util.Calendar.APRIL;
|
||||||
|
import static java.util.Calendar.DECEMBER;
|
||||||
|
import static java.util.Calendar.JANUARY;
|
||||||
|
import static java.util.Calendar.MAY;
|
||||||
|
import static java.util.Calendar.NOVEMBER;
|
||||||
|
import static java.util.Calendar.OCTOBER;
|
||||||
|
import static java.util.Calendar.SEPTEMBER;
|
||||||
|
import static org.apache.poi.ss.formula.eval.ErrorEval.NUM_ERROR;
|
||||||
|
import static org.apache.poi.ss.formula.eval.ErrorEval.VALUE_INVALID;
|
||||||
|
|
||||||
|
import java.text.SimpleDateFormat;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Calendar;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import junit.framework.TestCase;
|
||||||
|
|
||||||
|
import org.apache.poi.ss.formula.OperationEvaluationContext;
|
||||||
|
import org.apache.poi.ss.formula.TwoDEval;
|
||||||
|
import org.apache.poi.ss.formula.eval.AreaEval;
|
||||||
|
import org.apache.poi.ss.formula.eval.AreaEvalBase;
|
||||||
|
import org.apache.poi.ss.formula.eval.NumberEval;
|
||||||
|
import org.apache.poi.ss.formula.eval.StringEval;
|
||||||
|
import org.apache.poi.ss.formula.eval.ValueEval;
|
||||||
|
import org.apache.poi.ss.usermodel.DateUtil;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author jfaenomoto@gmail.com
|
||||||
|
*/
|
||||||
|
public class TestWorkdayFunction extends TestCase {
|
||||||
|
|
||||||
|
private static final SimpleDateFormat formatter = new SimpleDateFormat("yyyy/MM/dd");
|
||||||
|
|
||||||
|
private static final String STARTING_DATE = formatter.format(new Date(108, OCTOBER, 1));
|
||||||
|
|
||||||
|
private static final String FIRST_HOLIDAY = formatter.format(new Date(108, NOVEMBER, 26));
|
||||||
|
|
||||||
|
private static final String SECOND_HOLIDAY = formatter.format(new Date(108, DECEMBER, 4));
|
||||||
|
|
||||||
|
private static final String THIRD_HOLIDAY = formatter.format(new Date(109, JANUARY, 21));
|
||||||
|
|
||||||
|
private static final String RETROATIVE_HOLIDAY = formatter.format(new Date(108, SEPTEMBER, 29));
|
||||||
|
|
||||||
|
private static final OperationEvaluationContext EC = new OperationEvaluationContext(null, null, 1, 1, 1, null);
|
||||||
|
|
||||||
|
public void testFailWhenNoArguments() {
|
||||||
|
assertEquals(VALUE_INVALID, WorkdayFunction.instance.evaluate(new ValueEval[0], null));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testFailWhenLessThan2Arguments() {
|
||||||
|
assertEquals(VALUE_INVALID, WorkdayFunction.instance.evaluate(new ValueEval[1], null));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testFailWhenMoreThan3Arguments() {
|
||||||
|
assertEquals(VALUE_INVALID, WorkdayFunction.instance.evaluate(new ValueEval[4], null));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testFailWhenArgumentsAreNotDatesNorNumbers() {
|
||||||
|
assertEquals(VALUE_INVALID, WorkdayFunction.instance.evaluate(
|
||||||
|
new ValueEval[]{ new StringEval("Potato"), new StringEval("Cucumber") }, EC));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testReturnWorkdays() {
|
||||||
|
assertEquals(new Date(109, APRIL, 30), DateUtil.getJavaDate(((NumberEval) WorkdayFunction.instance.evaluate(new ValueEval[]{
|
||||||
|
new StringEval(STARTING_DATE.toString()), new NumberEval(151) }, EC)).getNumberValue()));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testReturnWorkdaysWithDaysTruncated() {
|
||||||
|
assertEquals(new Date(109, APRIL, 30), DateUtil.getJavaDate(((NumberEval) WorkdayFunction.instance.evaluate(new ValueEval[]{
|
||||||
|
new StringEval(STARTING_DATE.toString()), new NumberEval(151.99999) }, EC)).getNumberValue()));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testReturnRetroativeWorkday() {
|
||||||
|
assertEquals(new Date(108, SEPTEMBER, 23), DateUtil.getJavaDate(((NumberEval) WorkdayFunction.instance.evaluate(new ValueEval[]{
|
||||||
|
new StringEval(STARTING_DATE.toString()), new NumberEval(-5), new StringEval(RETROATIVE_HOLIDAY.toString()) }, EC))
|
||||||
|
.getNumberValue()));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testReturnNetworkdaysWithManyHolidays() {
|
||||||
|
assertEquals(new Date(109, MAY, 5), DateUtil.getJavaDate(((NumberEval) WorkdayFunction.instance.evaluate(new ValueEval[]{
|
||||||
|
new StringEval(STARTING_DATE.toString()), new NumberEval(151),
|
||||||
|
new MockAreaEval(FIRST_HOLIDAY, SECOND_HOLIDAY, THIRD_HOLIDAY) }, EC)).getNumberValue()));
|
||||||
|
}
|
||||||
|
|
||||||
|
private class MockAreaEval extends AreaEvalBase {
|
||||||
|
|
||||||
|
private List<ValueEval> holidays;
|
||||||
|
|
||||||
|
public MockAreaEval(String... holidays) {
|
||||||
|
this(0, 0, 0, holidays.length - 1);
|
||||||
|
this.holidays = new ArrayList<ValueEval>();
|
||||||
|
for (String holiday : holidays) {
|
||||||
|
this.holidays.add(new StringEval(holiday));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected MockAreaEval(int firstRow, int firstColumn, int lastRow, int lastColumn) {
|
||||||
|
super(firstRow, firstColumn, lastRow, lastColumn);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ValueEval getRelativeValue(int relativeRowIndex, int relativeColumnIndex) {
|
||||||
|
return this.holidays.get(relativeColumnIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
public AreaEval offset(int relFirstRowIx, int relLastRowIx, int relFirstColIx, int relLastColIx) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public TwoDEval getColumn(int columnIndex) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public TwoDEval getRow(int rowIndex) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue