View | Details | Raw Unified | Return to bug 52378
Collapse All | Expand All

(-)src/java/org/apache/poi/ss/formula/atp/NetworkdaysFunction.java (+66 lines)
Line 0 Link Here
1
package org.apache.poi.ss.formula.atp;
2
3
import org.apache.poi.ss.formula.OperationEvaluationContext;
4
import org.apache.poi.ss.formula.eval.ErrorEval;
5
import org.apache.poi.ss.formula.eval.EvaluationException;
6
import org.apache.poi.ss.formula.eval.NumberEval;
7
import org.apache.poi.ss.formula.eval.ValueEval;
8
import org.apache.poi.ss.formula.functions.FreeRefFunction;
9
10
/**
11
 * Implementation of Excel 'Analysis ToolPak' function NETWORKDAYS()<br/>
12
 * Returns the number of workdays given a starting and an ending date, considering an interval of holidays. A workday is any non
13
 * saturday/sunday date.
14
 * <p/>
15
 * <b>Syntax</b><br/>
16
 * <b>NETWORKDAYS</b>(<b>startDate</b>, <b>endDate</b>, holidays)
17
 * <p/>
18
 * 
19
 * @author jfaenomoto@gmail.com
20
 */
21
final class NetworkdaysFunction implements FreeRefFunction {
22
23
    public static final FreeRefFunction instance = new NetworkdaysFunction(ArgumentsEvaluator.instance);
24
25
    private ArgumentsEvaluator evaluator;
26
27
    /**
28
     * Constructor.
29
     * 
30
     * @param anEvaluator an injected {@link ArgumentsEvaluator}.
31
     */
32
    private NetworkdaysFunction(ArgumentsEvaluator anEvaluator) {
33
        // enforces singleton
34
        this.evaluator = anEvaluator;
35
    }
36
37
    /**
38
     * Evaluate for NETWORKDAYS. Given two dates and a optional date or interval of holidays, determines how many working days are there
39
     * between those dates.
40
     * 
41
     * @return {@link ValueEval} for the number of days between two dates.
42
     */
43
    public ValueEval evaluate(ValueEval[] args, OperationEvaluationContext ec) {
44
        if (args.length < 2 || args.length > 3) {
45
            return ErrorEval.VALUE_INVALID;
46
        }
47
48
        int srcCellRow = ec.getRowIndex();
49
        int srcCellCol = ec.getColumnIndex();
50
51
        double start, end;
52
        double[] holidays;
53
        try {
54
            start = this.evaluator.evaluateDateArg(args[0], srcCellRow, srcCellCol);
55
            end = this.evaluator.evaluateDateArg(args[1], srcCellRow, srcCellCol);
56
            if (start > end) {
57
                return ErrorEval.NAME_INVALID;
58
            }
59
            ValueEval holidaysCell = args.length == 3 ? args[2] : null;
60
            holidays = this.evaluator.evaluateDatesArg(holidaysCell, srcCellRow, srcCellCol);
61
            return new NumberEval(WorkdayCalculator.instance.calculateWorkdays(start, end, holidays));
62
        } catch (EvaluationException e) {
63
            return ErrorEval.VALUE_INVALID;
64
        }
65
    }
66
}
(-)src/java/org/apache/poi/ss/formula/atp/MRound.java (-5 lines)
Lines 21-32 Link Here
21
import org.apache.poi.ss.formula.eval.*;
21
import org.apache.poi.ss.formula.eval.*;
22
import org.apache.poi.ss.formula.functions.FreeRefFunction;
22
import org.apache.poi.ss.formula.functions.FreeRefFunction;
23
import org.apache.poi.ss.formula.functions.NumericFunction;
23
import org.apache.poi.ss.formula.functions.NumericFunction;
24
import org.apache.poi.ss.usermodel.DateUtil;
25
24
26
import java.util.Calendar;
27
import java.util.GregorianCalendar;
28
import java.util.regex.Pattern;
29
30
/**
25
/**
31
 * Implementation of Excel 'Analysis ToolPak' function MROUND()<br/>
26
 * Implementation of Excel 'Analysis ToolPak' function MROUND()<br/>
32
 *
27
 *
(-)src/java/org/apache/poi/ss/formula/atp/WorkdayFunction.java (+61 lines)
Line 0 Link Here
1
package org.apache.poi.ss.formula.atp;
2
3
import org.apache.poi.ss.formula.OperationEvaluationContext;
4
import org.apache.poi.ss.formula.eval.ErrorEval;
5
import org.apache.poi.ss.formula.eval.EvaluationException;
6
import org.apache.poi.ss.formula.eval.NumberEval;
7
import org.apache.poi.ss.formula.eval.ValueEval;
8
import org.apache.poi.ss.formula.functions.FreeRefFunction;
9
import org.apache.poi.ss.usermodel.DateUtil;
10
11
/**
12
 * Implementation of Excel 'Analysis ToolPak' function WORKDAY()<br/>
13
 * Returns the date past a number of workdays beginning at a start date, considering an interval of holidays. A workday is any non
14
 * saturday/sunday date.
15
 * <p/>
16
 * <b>Syntax</b><br/>
17
 * <b>WORKDAY</b>(<b>startDate</b>, <b>days</b>, holidays)
18
 * <p/>
19
 * 
20
 * @author jfaenomoto@gmail.com
21
 */
22
final class WorkdayFunction implements FreeRefFunction {
23
24
    public static final FreeRefFunction instance = new WorkdayFunction(ArgumentsEvaluator.instance);
25
26
    private ArgumentsEvaluator evaluator;
27
28
    private WorkdayFunction(ArgumentsEvaluator anEvaluator) {
29
        // enforces singleton
30
        this.evaluator = anEvaluator;
31
    }
32
33
    /**
34
     * Evaluate for WORKDAY. Given a date, a number of days and a optional date or interval of holidays, determines which date it is past
35
     * number of parametrized workdays.
36
     * 
37
     * @return {@link ValueEval} with date as its value.
38
     */
39
    public ValueEval evaluate(ValueEval[] args, OperationEvaluationContext ec) {
40
        if (args.length < 2 || args.length > 3) {
41
            return ErrorEval.VALUE_INVALID;
42
        }
43
44
        int srcCellRow = ec.getRowIndex();
45
        int srcCellCol = ec.getColumnIndex();
46
47
        double start;
48
        int days;
49
        double[] holidays;
50
        try {
51
            start = this.evaluator.evaluateDateArg(args[0], srcCellRow, srcCellCol);
52
            days = (int) Math.floor(this.evaluator.evaluateNumberArg(args[1], srcCellRow, srcCellCol));
53
            ValueEval holidaysCell = args.length == 3 ? args[2] : null;
54
            holidays = this.evaluator.evaluateDatesArg(holidaysCell, srcCellRow, srcCellCol);
55
            return new NumberEval(DateUtil.getExcelDate(WorkdayCalculator.instance.calculateWorkdays(start, days, holidays)));
56
        } catch (EvaluationException e) {
57
            return ErrorEval.VALUE_INVALID;
58
        }
59
    }
60
61
}
(-)src/java/org/apache/poi/ss/formula/atp/ArgumentsEvaluator.java (+100 lines)
Line 0 Link Here
1
package org.apache.poi.ss.formula.atp;
2
3
import java.util.ArrayList;
4
import java.util.Calendar;
5
import java.util.List;
6
7
import org.apache.poi.ss.formula.eval.AreaEvalBase;
8
import org.apache.poi.ss.formula.eval.EvaluationException;
9
import org.apache.poi.ss.formula.eval.OperandResolver;
10
import org.apache.poi.ss.formula.eval.StringEval;
11
import org.apache.poi.ss.formula.eval.ValueEval;
12
import org.apache.poi.ss.usermodel.DateUtil;
13
14
/**
15
 * Evaluator for formula arguments.
16
 * 
17
 * @author jfaenomoto@gmail.com
18
 */
19
final class ArgumentsEvaluator {
20
21
    public static final ArgumentsEvaluator instance = new ArgumentsEvaluator();
22
23
    private ArgumentsEvaluator() {
24
        // enforces singleton
25
    }
26
27
    /**
28
     * Evaluate a generic {@link ValueEval} argument to a double value that represents a date in POI.
29
     * 
30
     * @param arg {@link ValueEval} an argument.
31
     * @param srcCellRow number cell row.
32
     * @param srcCellCol number cell column.
33
     * @return a double representing a date in POI.
34
     * @throws EvaluationException exception upon argument evaluation.
35
     */
36
    public double evaluateDateArg(ValueEval arg, int srcCellRow, int srcCellCol) throws EvaluationException {
37
        ValueEval ve = OperandResolver.getSingleValue(arg, srcCellRow, (short) srcCellCol);
38
39
        if (ve instanceof StringEval) {
40
            String strVal = ((StringEval) ve).getStringValue();
41
            Double dVal = OperandResolver.parseDouble(strVal);
42
            if (dVal != null) {
43
                return dVal.doubleValue();
44
            }
45
            Calendar date = DateParser.parseDate(strVal);
46
            return DateUtil.getExcelDate(date, false);
47
        }
48
        return OperandResolver.coerceValueToDouble(ve);
49
    }
50
51
    /**
52
     * Evaluate a generic {@link ValueEval} argument to an array of double values that represents dates in POI.
53
     * 
54
     * @param arg {@link ValueEval} an argument.
55
     * @param srcCellRow number cell row.
56
     * @param srcCellCol number cell column.
57
     * @return an array of doubles representing dates in POI.
58
     * @throws EvaluationException exception upon argument evaluation.
59
     */
60
    public double[] evaluateDatesArg(ValueEval arg, int srcCellRow, int srcCellCol) throws EvaluationException {
61
        if (arg == null) {
62
            return new double[0];
63
        }
64
65
        if (arg instanceof StringEval) {
66
            return new double[]{ evaluateDateArg(arg, srcCellRow, srcCellCol) };
67
        } else if (arg instanceof AreaEvalBase) {
68
            List<Double> valuesList = new ArrayList<Double>();
69
            AreaEvalBase area = (AreaEvalBase) arg;
70
            for (int i = area.getFirstRow(); i <= area.getLastRow(); i++) {
71
                for (int j = area.getFirstColumn(); j <= area.getLastColumn(); j++) {
72
                    valuesList.add(evaluateDateArg(area.getValue(i, j), i, j));
73
                }
74
            }
75
            double[] values = new double[valuesList.size()];
76
            for (int i = 0; i < valuesList.size(); i++) {
77
                values[i] = valuesList.get(i).doubleValue();
78
            }
79
            return values;
80
        }
81
        return new double[]{ OperandResolver.coerceValueToDouble(arg) };
82
    }
83
84
    /**
85
     * Evaluate a generic {@link ValueEval} argument to a double value.
86
     * 
87
     * @param arg {@link ValueEval} an argument.
88
     * @param srcCellRow number cell row.
89
     * @param srcCellCol number cell column.
90
     * @return a double value.
91
     * @throws EvaluationException exception upon argument evaluation.
92
     */
93
    public double evaluateNumberArg(ValueEval arg, int srcCellRow, int srcCellCol) throws EvaluationException {
94
        if (arg == null) {
95
            return 0f;
96
        }
97
98
        return OperandResolver.coerceValueToDouble(arg);
99
    }
100
}
(-)src/java/org/apache/poi/ss/formula/atp/WorkdayCalculator.java (+151 lines)
Line 0 Link Here
1
package org.apache.poi.ss.formula.atp;
2
3
import java.util.Calendar;
4
import java.util.Date;
5
6
import org.apache.poi.ss.usermodel.DateUtil;
7
8
/**
9
 * A calculator for workdays, considering dates as excel representations.
10
 * 
11
 * @author jfaenomoto@gmail.com
12
 */
13
public class WorkdayCalculator {
14
15
    public static final WorkdayCalculator instance = new WorkdayCalculator();
16
17
    /**
18
     * Constructor.
19
     */
20
    private WorkdayCalculator() {
21
        // enforcing singleton
22
    }
23
24
    /**
25
     * Calculate how many workdays are there between a start and an end date, as excel representations, considering a range of holidays.
26
     * 
27
     * @param start start date.
28
     * @param end end date.
29
     * @param holidays an array of holidays.
30
     * @return number of workdays between start and end dates, including both dates.
31
     */
32
    public int calculateWorkdays(double start, double end, double[] holidays) {
33
        int saturdaysPast = this.pastDaysOfWeek(start, end, Calendar.SATURDAY);
34
        int sundaysPast = this.pastDaysOfWeek(start, end, Calendar.SUNDAY);
35
        int nonWeekendHolidays = this.calculateNonWeekendHolidays(start, end, holidays);
36
        return (int) (end - start + 1) - saturdaysPast - sundaysPast - nonWeekendHolidays;
37
    }
38
39
    /**
40
     * Calculate the workday past x workdays from a starting date, considering a range of holidays.
41
     * 
42
     * @param start start date.
43
     * @param workdays number of workdays to be past from starting date.
44
     * @param holidays an array of holidays.
45
     * @return date past x workdays.
46
     */
47
    public Date calculateWorkdays(double start, int workdays, double[] holidays) {
48
        Date startDate = DateUtil.getJavaDate(start);
49
        Calendar endDate = Calendar.getInstance();
50
        endDate.setTime(startDate);
51
        endDate.add(Calendar.DAY_OF_YEAR, workdays);
52
        int skippedDays = 0;
53
        do {
54
            double end = DateUtil.getExcelDate(endDate.getTime());
55
            int saturdaysPast = this.pastDaysOfWeek(start, end, Calendar.SATURDAY);
56
            int sundaysPast = this.pastDaysOfWeek(start, end, Calendar.SUNDAY);
57
            int nonWeekendHolidays = this.calculateNonWeekendHolidays(start, end, holidays);
58
            skippedDays = saturdaysPast + sundaysPast + nonWeekendHolidays;
59
            endDate.add(Calendar.DAY_OF_YEAR, skippedDays);
60
            start = end + isNonWorkday(end, holidays);
61
        } while (skippedDays != 0);
62
        return endDate.getTime();
63
    }
64
65
    /**
66
     * Calculates how many days of week past between a start and an end date.
67
     * 
68
     * @param start start date.
69
     * @param end end date.
70
     * @param dayOfWeek a day of week as represented by {@link Calendar} constants.
71
     * @return how many days of week past in this interval.
72
     */
73
    protected int pastDaysOfWeek(double start, double end, int dayOfWeek) {
74
        int pastDaysOfWeek = 0;
75
        int startDay = (int) Math.floor(start < end ? start : end);
76
        int endDay = (int) Math.floor(end > start ? end : start);
77
        for (; startDay <= endDay; startDay++) {
78
            Calendar today = Calendar.getInstance();
79
            today.setTime(DateUtil.getJavaDate(startDay));
80
            if (today.get(Calendar.DAY_OF_WEEK) == dayOfWeek) {
81
                pastDaysOfWeek++;
82
            }
83
        }
84
        return start < end ? pastDaysOfWeek : -pastDaysOfWeek;
85
    }
86
87
    /**
88
     * Calculates how many holidays in a list are workdays, considering an interval of dates.
89
     * 
90
     * @param start start date.
91
     * @param end end date.
92
     * @param holidays an array of holidays.
93
     * @return number of holidays that occur in workdays, between start and end dates.
94
     */
95
    protected int calculateNonWeekendHolidays(double start, double end, double[] holidays) {
96
        int nonWeekendHolidays = 0;
97
        double startDay = start < end ? start : end;
98
        double endDay = end > start ? end : start;
99
        for (int i = 0; i < holidays.length; i++) {
100
            if (isInARange(startDay, endDay, holidays[i])) {
101
                if (!isWeekend(holidays[i])) {
102
                    nonWeekendHolidays++;
103
                }
104
            }
105
        }
106
        return start < end ? nonWeekendHolidays : -nonWeekendHolidays;
107
    }
108
109
    /**
110
     * @param aDate a given date.
111
     * @return <code>true</code> if date is weekend, <code>false</code> otherwise.
112
     */
113
    protected boolean isWeekend(double aDate) {
114
        Calendar date = Calendar.getInstance();
115
        date.setTime(DateUtil.getJavaDate(aDate));
116
        return date.get(Calendar.DAY_OF_WEEK) == Calendar.SATURDAY || date.get(Calendar.DAY_OF_WEEK) == Calendar.SUNDAY;
117
    }
118
119
    /**
120
     * @param aDate a given date.
121
     * @param holidays an array of holidays.
122
     * @return <code>true</code> if date is a holiday, <code>false</code> otherwise.
123
     */
124
    protected boolean isHoliday(double aDate, double[] holidays) {
125
        for (int i = 0; i < holidays.length; i++) {
126
            if (Math.round(holidays[i]) == Math.round(aDate)) {
127
                return true;
128
            }
129
        }
130
        return false;
131
    }
132
133
    /**
134
     * @param aDate a given date.
135
     * @param holidays an array of holidays.
136
     * @return <code>1</code> is not a workday, <code>0</code> otherwise.
137
     */
138
    protected int isNonWorkday(double aDate, double[] holidays) {
139
        return isWeekend(aDate) || isHoliday(aDate, holidays) ? 1 : 0;
140
    }
141
142
    /**
143
     * @param start start date.
144
     * @param end end date.
145
     * @param aDate a date to be analyzed.
146
     * @return <code>true</code> if aDate is between start and end dates, <code>false</code> otherwise.
147
     */
148
    protected boolean isInARange(double start, double end, double aDate) {
149
        return aDate >= start && aDate <= end;
150
    }
151
}
(-)src/java/org/apache/poi/ss/formula/atp/DateParser.java (+85 lines)
Line 0 Link Here
1
package org.apache.poi.ss.formula.atp;
2
3
import java.util.Calendar;
4
import java.util.GregorianCalendar;
5
import java.util.regex.Pattern;
6
7
import org.apache.poi.ss.formula.eval.ErrorEval;
8
import org.apache.poi.ss.formula.eval.EvaluationException;
9
10
/**
11
 * Parser for java dates.
12
 * 
13
 * @author jfaenomoto@gmail.com
14
 */
15
public class DateParser {
16
17
    public DateParser instance = new DateParser();
18
19
    private DateParser() {
20
        // enforcing singleton
21
    }
22
23
    /**
24
     * Parses a date from a string.
25
     * 
26
     * @param strVal a string with a date pattern.
27
     * @return a date parsed from argument.
28
     * @throws EvaluationException exception upon parsing.
29
     */
30
    public static Calendar parseDate(String strVal) throws EvaluationException {
31
        String[] parts = Pattern.compile("/").split(strVal);
32
        if (parts.length != 3) {
33
            throw new EvaluationException(ErrorEval.VALUE_INVALID);
34
        }
35
        String part2 = parts[2];
36
        int spacePos = part2.indexOf(' ');
37
        if (spacePos > 0) {
38
            // drop time portion if present
39
            part2 = part2.substring(0, spacePos);
40
        }
41
        int f0;
42
        int f1;
43
        int f2;
44
        try {
45
            f0 = Integer.parseInt(parts[0]);
46
            f1 = Integer.parseInt(parts[1]);
47
            f2 = Integer.parseInt(part2);
48
        } catch (NumberFormatException e) {
49
            throw new EvaluationException(ErrorEval.VALUE_INVALID);
50
        }
51
        if (f0 < 0 || f1 < 0 || f2 < 0 || (f0 > 12 && f1 > 12 && f2 > 12)) {
52
            // easy to see this cannot be a valid date
53
            throw new EvaluationException(ErrorEval.VALUE_INVALID);
54
        }
55
56
        if (f0 >= 1900 && f0 < 9999) {
57
            // when 4 digit value appears first, the format is YYYY/MM/DD, regardless of OS settings
58
            return makeDate(f0, f1, f2);
59
        }
60
        // otherwise the format seems to depend on OS settings (default date format)
61
        if (false) {
62
            // MM/DD/YYYY is probably a good guess, if the in the US
63
            return makeDate(f2, f0, f1);
64
        }
65
        // TODO - find a way to choose the correct date format
66
        throw new RuntimeException("Unable to determine date format for text '" + strVal + "'");
67
    }
68
69
    /**
70
     * @param month 1-based
71
     */
72
    private static Calendar makeDate(int year, int month, int day) throws EvaluationException {
73
        if (month < 1 || month > 12) {
74
            throw new EvaluationException(ErrorEval.VALUE_INVALID);
75
        }
76
        Calendar cal = new GregorianCalendar(year, month - 1, 1, 0, 0, 0);
77
        cal.set(Calendar.MILLISECOND, 0);
78
        if (day < 1 || day > cal.getActualMaximum(Calendar.DAY_OF_MONTH)) {
79
            throw new EvaluationException(ErrorEval.VALUE_INVALID);
80
        }
81
        cal.set(Calendar.DAY_OF_MONTH, day);
82
        return cal;
83
    }
84
85
}
(-)src/java/org/apache/poi/ss/formula/atp/YearFrac.java (-56 / +1 lines)
Lines 91-157 Link Here
91
			if (dVal != null) {
91
			if (dVal != null) {
92
				return dVal.doubleValue();
92
				return dVal.doubleValue();
93
			}
93
			}
94
			Calendar date = parseDate(strVal);
94
			Calendar date = DateParser.parseDate(strVal);
95
			return DateUtil.getExcelDate(date, false);
95
			return DateUtil.getExcelDate(date, false);
96
		}
96
		}
97
		return OperandResolver.coerceValueToDouble(ve);
97
		return OperandResolver.coerceValueToDouble(ve);
98
	}
98
	}
99
99
100
	private static Calendar parseDate(String strVal) throws EvaluationException {
101
		String[] parts = Pattern.compile("/").split(strVal);
102
		if (parts.length != 3) {
103
			throw new EvaluationException(ErrorEval.VALUE_INVALID);
104
		}
105
		String part2 = parts[2];
106
		int spacePos = part2.indexOf(' ');
107
		if (spacePos > 0) {
108
			// drop time portion if present
109
			part2 = part2.substring(0, spacePos);
110
		}
111
		int f0;
112
		int f1;
113
		int f2;
114
		try {
115
			f0 = Integer.parseInt(parts[0]);
116
			f1 = Integer.parseInt(parts[1]);
117
			f2 = Integer.parseInt(part2);
118
		} catch (NumberFormatException e) {
119
			throw new EvaluationException(ErrorEval.VALUE_INVALID);
120
		}
121
		if (f0<0 || f1<0 || f2<0 || (f0>12 && f1>12 && f2>12)) {
122
			// easy to see this cannot be a valid date
123
			throw new EvaluationException(ErrorEval.VALUE_INVALID);
124
		}
125
126
		if (f0 >= 1900 && f0 < 9999) {
127
			// when 4 digit value appears first, the format is YYYY/MM/DD, regardless of OS settings
128
			return makeDate(f0, f1, f2);
129
		}
130
		// otherwise the format seems to depend on OS settings (default date format)
131
		if (false) {
132
			// MM/DD/YYYY is probably a good guess, if the in the US
133
			return makeDate(f2, f0, f1);
134
		}
135
		// TODO - find a way to choose the correct date format
136
		throw new RuntimeException("Unable to determine date format for text '" + strVal + "'");
137
	}
138
139
	/**
140
	 * @param month 1-based
141
	 */
142
	private static Calendar makeDate(int year, int month, int day) throws EvaluationException {
143
		if (month < 1 || month > 12) {
144
			throw new EvaluationException(ErrorEval.VALUE_INVALID);
145
		}
146
		Calendar cal = new GregorianCalendar(year, month-1, 1, 0, 0, 0);
147
		cal.set(Calendar.MILLISECOND, 0);
148
		if (day <1 || day>cal.getActualMaximum(Calendar.DAY_OF_MONTH)) {
149
			throw new EvaluationException(ErrorEval.VALUE_INVALID);
150
		}
151
		cal.set(Calendar.DAY_OF_MONTH, day);
152
		return cal;
153
	}
154
155
	private static int evaluateIntArg(ValueEval arg, int srcCellRow, int srcCellCol) throws EvaluationException {
100
	private static int evaluateIntArg(ValueEval arg, int srcCellRow, int srcCellCol) throws EvaluationException {
156
		ValueEval ve = OperandResolver.getSingleValue(arg, srcCellRow, (short) srcCellCol);
101
		ValueEval ve = OperandResolver.getSingleValue(arg, srcCellRow, (short) srcCellCol);
157
		return OperandResolver.coerceValueToInt(ve);
102
		return OperandResolver.coerceValueToInt(ve);
(-)src/java/org/apache/poi/ss/formula/atp/AnalysisToolPak.java (-20 / +12 lines)
Lines 1-20 Link Here
1
/* ====================================================================
1
/*
2
   Licensed to the Apache Software Foundation (ASF) under one or more
2
 * ==================================================================== Licensed to the Apache Software Foundation (ASF) under one or more
3
   contributor license agreements.  See the NOTICE file distributed with
3
 * contributor license agreements. See the NOTICE file distributed with this work for additional information regarding copyright ownership.
4
   this work for additional information regarding copyright ownership.
4
 * 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
5
   The ASF licenses this file to You under the Apache License, Version 2.0
5
 * 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
6
   (the "License"); you may not use this file except in compliance with
6
 * agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
7
   the License.  You may obtain a copy of the License at
7
 * KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
8
 * ====================================================================
9
 */
8
10
9
       http://www.apache.org/licenses/LICENSE-2.0
10
11
   Unless required by applicable law or agreed to in writing, software
12
   distributed under the License is distributed on an "AS IS" BASIS,
13
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
   See the License for the specific language governing permissions and
15
   limitations under the License.
16
==================================================================== */
17
18
package org.apache.poi.ss.formula.atp;
11
package org.apache.poi.ss.formula.atp;
19
12
20
import java.util.HashMap;
13
import java.util.HashMap;
Lines 50-62 Link Here
50
43
51
    private final Map<String, FreeRefFunction> _functionsByName = createFunctionsMap();
44
    private final Map<String, FreeRefFunction> _functionsByName = createFunctionsMap();
52
45
53
54
    private AnalysisToolPak() {
46
    private AnalysisToolPak() {
55
        // enforce singleton
47
        // enforce singleton
56
    }
48
    }
57
49
58
    public FreeRefFunction findFunction(String name) {
50
    public FreeRefFunction findFunction(String name) {
59
        return _functionsByName.get(name);
51
        return _functionsByName.get(name.toUpperCase());
60
    }
52
    }
61
53
62
    private Map<String, FreeRefFunction> createFunctionsMap() {
54
    private Map<String, FreeRefFunction> createFunctionsMap() {
Lines 140-146 Link Here
140
        r(m, "MDURATION", null);
132
        r(m, "MDURATION", null);
141
        r(m, "MROUND", MRound.instance);
133
        r(m, "MROUND", MRound.instance);
142
        r(m, "MULTINOMIAL", null);
134
        r(m, "MULTINOMIAL", null);
143
        r(m, "NETWORKDAYS", null);
135
        r(m, "NETWORKDAYS", NetworkdaysFunction.instance);
144
        r(m, "NOMINAL", null);
136
        r(m, "NOMINAL", null);
145
        r(m, "OCT2BIN", null);
137
        r(m, "OCT2BIN", null);
146
        r(m, "OCT2DEC", null);
138
        r(m, "OCT2DEC", null);
Lines 163-169 Link Here
163
        r(m, "TBILLPRICE", null);
155
        r(m, "TBILLPRICE", null);
164
        r(m, "TBILLYIELD", null);
156
        r(m, "TBILLYIELD", null);
165
        r(m, "WEEKNUM", null);
157
        r(m, "WEEKNUM", null);
166
        r(m, "WORKDAY", null);
158
        r(m, "WORKDAY", WorkdayFunction.instance);
167
        r(m, "XIRR", null);
159
        r(m, "XIRR", null);
168
        r(m, "XNPV", null);
160
        r(m, "XNPV", null);
169
        r(m, "YEARFRAC", YearFrac.instance);
161
        r(m, "YEARFRAC", YearFrac.instance);
(-)src/testcases/org/apache/poi/ss/formula/atp/NetworkdaysFunctionTest.java (+118 lines)
Line 0 Link Here
1
package org.apache.poi.ss.formula.atp;
2
3
import static java.util.Calendar.DECEMBER;
4
import static java.util.Calendar.JANUARY;
5
import static java.util.Calendar.MARCH;
6
import static java.util.Calendar.NOVEMBER;
7
import static java.util.Calendar.OCTOBER;
8
import static org.apache.poi.ss.formula.eval.ErrorEval.NAME_INVALID;
9
import static org.apache.poi.ss.formula.eval.ErrorEval.VALUE_INVALID;
10
11
import java.text.SimpleDateFormat;
12
import java.util.ArrayList;
13
import java.util.Date;
14
import java.util.List;
15
16
import junit.framework.TestCase;
17
18
import org.apache.poi.ss.formula.OperationEvaluationContext;
19
import org.apache.poi.ss.formula.TwoDEval;
20
import org.apache.poi.ss.formula.eval.AreaEval;
21
import org.apache.poi.ss.formula.eval.AreaEvalBase;
22
import org.apache.poi.ss.formula.eval.NumericValueEval;
23
import org.apache.poi.ss.formula.eval.StringEval;
24
import org.apache.poi.ss.formula.eval.ValueEval;
25
26
/**
27
 * @author jfaenomoto@gmail.com
28
 */
29
public class NetworkdaysFunctionTest extends TestCase {
30
31
    private static final SimpleDateFormat formatter = new SimpleDateFormat("yyyy/MM/dd");
32
33
    private static final String STARTING_DATE = formatter.format(new Date(108, OCTOBER, 1));
34
35
    private static final String END_DATE = formatter.format(new Date(109, MARCH, 1));
36
37
    private static final String FIRST_HOLIDAY = formatter.format(new Date(108, NOVEMBER, 26));
38
39
    private static final String SECOND_HOLIDAY = formatter.format(new Date(108, DECEMBER, 4));
40
41
    private static final String THIRD_HOLIDAY = formatter.format(new Date(109, JANUARY, 21));
42
43
    private static final OperationEvaluationContext EC = new OperationEvaluationContext(null, null, 1, 1, 1, null);
44
45
    public void testFailWhenNoArguments() {
46
        assertEquals(VALUE_INVALID, NetworkdaysFunction.instance.evaluate(new ValueEval[0], null));
47
    }
48
49
    public void testFailWhenLessThan2Arguments() {
50
        assertEquals(VALUE_INVALID, NetworkdaysFunction.instance.evaluate(new ValueEval[1], null));
51
    }
52
53
    public void testFailWhenMoreThan3Arguments() {
54
        assertEquals(VALUE_INVALID, NetworkdaysFunction.instance.evaluate(new ValueEval[4], null));
55
    }
56
57
    public void testFailWhenArgumentsAreNotDates() {
58
        assertEquals(VALUE_INVALID, NetworkdaysFunction.instance.evaluate(new ValueEval[]{ new StringEval("Potato"),
59
                new StringEval("Cucumber") }, EC));
60
    }
61
62
    public void testFailWhenStartDateAfterEndDate() {
63
        assertEquals(NAME_INVALID, NetworkdaysFunction.instance.evaluate(new ValueEval[]{ new StringEval(END_DATE.toString()),
64
                new StringEval(STARTING_DATE.toString()) }, EC));
65
    }
66
67
    public void testReturnNetworkdays() {
68
        assertEquals(108, (int) ((NumericValueEval) NetworkdaysFunction.instance.evaluate(new ValueEval[]{
69
                new StringEval(STARTING_DATE.toString()), new StringEval(END_DATE.toString()) }, EC)).getNumberValue());
70
    }
71
72
    public void testReturnNetworkdaysWithAHoliday() {
73
        assertEquals(107, (int) ((NumericValueEval) NetworkdaysFunction.instance.evaluate(new ValueEval[]{
74
                new StringEval(STARTING_DATE.toString()), new StringEval(END_DATE.toString()), new StringEval(FIRST_HOLIDAY.toString()) },
75
                EC)).getNumberValue());
76
    }
77
78
    public void testReturnNetworkdaysWithManyHolidays() {
79
        assertEquals(105, (int) ((NumericValueEval) NetworkdaysFunction.instance.evaluate(new ValueEval[]{
80
                new StringEval(STARTING_DATE.toString()), new StringEval(END_DATE.toString()),
81
                new MockAreaEval(FIRST_HOLIDAY, SECOND_HOLIDAY, THIRD_HOLIDAY) }, EC)).getNumberValue());
82
    }
83
84
    private class MockAreaEval extends AreaEvalBase {
85
86
        private List<ValueEval> holidays;
87
88
        public MockAreaEval(String... holidays) {
89
            this(0, 0, 0, holidays.length - 1);
90
            this.holidays = new ArrayList<ValueEval>();
91
            for (String holiday : holidays) {
92
                this.holidays.add(new StringEval(holiday));
93
            }
94
        }
95
96
        protected MockAreaEval(int firstRow, int firstColumn, int lastRow, int lastColumn) {
97
            super(firstRow, firstColumn, lastRow, lastColumn);
98
        }
99
100
        @Override
101
        public ValueEval getRelativeValue(int relativeRowIndex, int relativeColumnIndex) {
102
            return this.holidays.get(relativeColumnIndex);
103
        }
104
105
        public AreaEval offset(int relFirstRowIx, int relLastRowIx, int relFirstColIx, int relLastColIx) {
106
            return null;
107
        }
108
109
        public TwoDEval getColumn(int columnIndex) {
110
            return null;
111
        }
112
113
        public TwoDEval getRow(int rowIndex) {
114
            return null;
115
        }
116
117
    }
118
}
(-)src/testcases/org/apache/poi/ss/formula/atp/WorkdayFunctionTest.java (+122 lines)
Line 0 Link Here
1
package org.apache.poi.ss.formula.atp;
2
3
import static java.util.Calendar.APRIL;
4
import static java.util.Calendar.DECEMBER;
5
import static java.util.Calendar.JANUARY;
6
import static java.util.Calendar.MAY;
7
import static java.util.Calendar.NOVEMBER;
8
import static java.util.Calendar.OCTOBER;
9
import static java.util.Calendar.SEPTEMBER;
10
import static org.apache.poi.ss.formula.eval.ErrorEval.NUM_ERROR;
11
import static org.apache.poi.ss.formula.eval.ErrorEval.VALUE_INVALID;
12
13
import java.text.SimpleDateFormat;
14
import java.util.ArrayList;
15
import java.util.Calendar;
16
import java.util.Date;
17
import java.util.List;
18
19
import junit.framework.TestCase;
20
21
import org.apache.poi.ss.formula.OperationEvaluationContext;
22
import org.apache.poi.ss.formula.TwoDEval;
23
import org.apache.poi.ss.formula.eval.AreaEval;
24
import org.apache.poi.ss.formula.eval.AreaEvalBase;
25
import org.apache.poi.ss.formula.eval.NumberEval;
26
import org.apache.poi.ss.formula.eval.StringEval;
27
import org.apache.poi.ss.formula.eval.ValueEval;
28
import org.apache.poi.ss.usermodel.DateUtil;
29
30
/**
31
 * @author jfaenomoto@gmail.com
32
 */
33
public class WorkdayFunctionTest extends TestCase {
34
35
    private static final SimpleDateFormat formatter = new SimpleDateFormat("yyyy/MM/dd");
36
37
    private static final String STARTING_DATE = formatter.format(new Date(108, OCTOBER, 1));
38
39
    private static final String FIRST_HOLIDAY = formatter.format(new Date(108, NOVEMBER, 26));
40
41
    private static final String SECOND_HOLIDAY = formatter.format(new Date(108, DECEMBER, 4));
42
43
    private static final String THIRD_HOLIDAY = formatter.format(new Date(109, JANUARY, 21));
44
45
    private static final String RETROATIVE_HOLIDAY = formatter.format(new Date(108, SEPTEMBER, 29));
46
47
    private static final OperationEvaluationContext EC = new OperationEvaluationContext(null, null, 1, 1, 1, null);
48
49
    public void testFailWhenNoArguments() {
50
        assertEquals(VALUE_INVALID, WorkdayFunction.instance.evaluate(new ValueEval[0], null));
51
    }
52
53
    public void testFailWhenLessThan2Arguments() {
54
        assertEquals(VALUE_INVALID, WorkdayFunction.instance.evaluate(new ValueEval[1], null));
55
    }
56
57
    public void testFailWhenMoreThan3Arguments() {
58
        assertEquals(VALUE_INVALID, WorkdayFunction.instance.evaluate(new ValueEval[4], null));
59
    }
60
61
    public void testFailWhenArgumentsAreNotDatesNorNumbers() {
62
        assertEquals(VALUE_INVALID, WorkdayFunction.instance.evaluate(
63
                new ValueEval[]{ new StringEval("Potato"), new StringEval("Cucumber") }, EC));
64
    }
65
66
    public void testReturnWorkdays() {
67
        assertEquals(new Date(109, APRIL, 30), DateUtil.getJavaDate(((NumberEval) WorkdayFunction.instance.evaluate(new ValueEval[]{
68
                new StringEval(STARTING_DATE.toString()), new NumberEval(151) }, EC)).getNumberValue()));
69
    }
70
71
    public void testReturnWorkdaysWithDaysTruncated() {
72
        assertEquals(new Date(109, APRIL, 30), DateUtil.getJavaDate(((NumberEval) WorkdayFunction.instance.evaluate(new ValueEval[]{
73
                new StringEval(STARTING_DATE.toString()), new NumberEval(151.99999) }, EC)).getNumberValue()));
74
    }
75
76
    public void testReturnRetroativeWorkday() {
77
        assertEquals(new Date(108, SEPTEMBER, 23), DateUtil.getJavaDate(((NumberEval) WorkdayFunction.instance.evaluate(new ValueEval[]{
78
                new StringEval(STARTING_DATE.toString()), new NumberEval(-5), new StringEval(RETROATIVE_HOLIDAY.toString()) }, EC))
79
                .getNumberValue()));
80
    }
81
82
    public void testReturnNetworkdaysWithManyHolidays() {
83
        assertEquals(new Date(109, MAY, 5), DateUtil.getJavaDate(((NumberEval) WorkdayFunction.instance.evaluate(new ValueEval[]{
84
                new StringEval(STARTING_DATE.toString()), new NumberEval(151),
85
                new MockAreaEval(FIRST_HOLIDAY, SECOND_HOLIDAY, THIRD_HOLIDAY) }, EC)).getNumberValue()));
86
    }
87
88
    private class MockAreaEval extends AreaEvalBase {
89
90
        private List<ValueEval> holidays;
91
92
        public MockAreaEval(String... holidays) {
93
            this(0, 0, 0, holidays.length - 1);
94
            this.holidays = new ArrayList<ValueEval>();
95
            for (String holiday : holidays) {
96
                this.holidays.add(new StringEval(holiday));
97
            }
98
        }
99
100
        protected MockAreaEval(int firstRow, int firstColumn, int lastRow, int lastColumn) {
101
            super(firstRow, firstColumn, lastRow, lastColumn);
102
        }
103
104
        @Override
105
        public ValueEval getRelativeValue(int relativeRowIndex, int relativeColumnIndex) {
106
            return this.holidays.get(relativeColumnIndex);
107
        }
108
109
        public AreaEval offset(int relFirstRowIx, int relLastRowIx, int relFirstColIx, int relLastColIx) {
110
            return null;
111
        }
112
113
        public TwoDEval getColumn(int columnIndex) {
114
            return null;
115
        }
116
117
        public TwoDEval getRow(int rowIndex) {
118
            return null;
119
        }
120
121
    }
122
}
(-)src/testcases/org/apache/poi/ss/formula/atp/WorkdayCalculatorTest.java (+79 lines)
Line 0 Link Here
1
package org.apache.poi.ss.formula.atp;
2
3
import static java.util.Calendar.DECEMBER;
4
import static java.util.Calendar.SATURDAY;
5
6
import java.util.Date;
7
8
import junit.framework.TestCase;
9
10
import org.apache.poi.ss.usermodel.DateUtil;
11
12
/**
13
 * @author jfaenomoto@gmail.com
14
 */
15
public class WorkdayCalculatorTest extends TestCase {
16
17
    public void testCalculateWorkdaysShouldReturnJustWeekdaysWhenNoWeekend() {
18
        final double A_MONDAY = DateUtil.getExcelDate(new Date(111, DECEMBER, 12));
19
        final double A_FRIDAY = DateUtil.getExcelDate(new Date(111, DECEMBER, 16));
20
        assertEquals(5, WorkdayCalculator.instance.calculateWorkdays(A_MONDAY, A_FRIDAY, new double[0]));
21
    }
22
23
    public void testCalculateWorkdaysShouldReturnAllDaysButNoSaturdays() {
24
        final double A_WEDNESDAY = DateUtil.getExcelDate(new Date(111, DECEMBER, 14));
25
        final double A_SATURDAY = DateUtil.getExcelDate(new Date(111, DECEMBER, 18));
26
        assertEquals(3, WorkdayCalculator.instance.calculateWorkdays(A_WEDNESDAY, A_SATURDAY, new double[0]));
27
    }
28
29
    public void testCalculateWorkdaysShouldReturnAllDaysButNoSundays() {
30
        final double A_SUNDAY = DateUtil.getExcelDate(new Date(111, DECEMBER, 11));
31
        final double A_THURSDAY = DateUtil.getExcelDate(new Date(111, DECEMBER, 15));
32
        assertEquals(4, WorkdayCalculator.instance.calculateWorkdays(A_SUNDAY, A_THURSDAY, new double[0]));
33
    }
34
35
    public void testCalculateWorkdaysShouldReturnAllDaysButNoHolidays() {
36
        final double A_MONDAY = DateUtil.getExcelDate(new Date(111, DECEMBER, 12));
37
        final double A_FRIDAY = DateUtil.getExcelDate(new Date(111, DECEMBER, 16));
38
        final double A_WEDNESDAY = DateUtil.getExcelDate(new Date(111, DECEMBER, 14));
39
        assertEquals(4, WorkdayCalculator.instance.calculateWorkdays(A_MONDAY, A_FRIDAY, new double[]{ A_WEDNESDAY }));
40
    }
41
42
    public void testCalculateWorkdaysShouldIgnoreWeekendHolidays() {
43
        final double A_FRIDAY = DateUtil.getExcelDate(new Date(111, DECEMBER, 16));
44
        final double A_SATURDAY = DateUtil.getExcelDate(new Date(111, DECEMBER, 17));
45
        final double A_SUNDAY = DateUtil.getExcelDate(new Date(111, DECEMBER, 18));
46
        final double A_WEDNESDAY = DateUtil.getExcelDate(new Date(111, DECEMBER, 21));
47
        assertEquals(4, WorkdayCalculator.instance.calculateWorkdays(A_FRIDAY, A_WEDNESDAY, new double[]{ A_SATURDAY, A_SUNDAY }));
48
    }
49
50
    public void testPastDaysOfWeekShouldReturn0Past0Saturdays() {
51
        final double A_WEDNESDAY = DateUtil.getExcelDate(new Date(111, DECEMBER, 7));
52
        final double A_FRIDAY = DateUtil.getExcelDate(new Date(111, DECEMBER, 9));
53
        assertEquals(0, WorkdayCalculator.instance.pastDaysOfWeek(A_WEDNESDAY, A_FRIDAY, SATURDAY));
54
    }
55
56
    public void testPastDaysOfWeekShouldReturn1Past1Saturdays() {
57
        final double A_WEDNESDAY = DateUtil.getExcelDate(new Date(111, DECEMBER, 7));
58
        final double A_SUNDAY = DateUtil.getExcelDate(new Date(111, DECEMBER, 11));
59
        assertEquals(1, WorkdayCalculator.instance.pastDaysOfWeek(A_WEDNESDAY, A_SUNDAY, SATURDAY));
60
    }
61
62
    public void testPastDaysOfWeekShouldReturn2Past2Saturdays() {
63
        final double A_THURSDAY = DateUtil.getExcelDate(new Date(111, DECEMBER, 8));
64
        final double A_MONDAY = DateUtil.getExcelDate(new Date(111, DECEMBER, 19));
65
        assertEquals(2, WorkdayCalculator.instance.pastDaysOfWeek(A_THURSDAY, A_MONDAY, SATURDAY));
66
    }
67
68
    public void testPastDaysOfWeekShouldReturn1BeginningFromASaturday() {
69
        final double A_SATURDAY = DateUtil.getExcelDate(new Date(111, DECEMBER, 10));
70
        final double A_SUNDAY = DateUtil.getExcelDate(new Date(111, DECEMBER, 11));
71
        assertEquals(1, WorkdayCalculator.instance.pastDaysOfWeek(A_SATURDAY, A_SUNDAY, SATURDAY));
72
    }
73
74
    public void testPastDaysOfWeekShouldReturn1EndingAtASaturday() {
75
        final double A_THURSDAY = DateUtil.getExcelDate(new Date(111, DECEMBER, 8));
76
        final double A_SATURDAY = DateUtil.getExcelDate(new Date(111, DECEMBER, 10));
77
        assertEquals(1, WorkdayCalculator.instance.pastDaysOfWeek(A_THURSDAY, A_SATURDAY, SATURDAY));
78
    }
79
}
(-)src/testcases/org/apache/poi/ss/formula/atp/DateParserTest.java (+57 lines)
Line 0 Link Here
1
package org.apache.poi.ss.formula.atp;
2
3
import static java.util.Calendar.OCTOBER;
4
5
import java.util.Calendar;
6
import java.util.Date;
7
8
import junit.framework.TestCase;
9
10
import org.apache.poi.ss.formula.eval.ErrorEval;
11
import org.apache.poi.ss.formula.eval.EvaluationException;
12
13
/**
14
 * @author jfaenomoto@gmail.com
15
 */
16
public class DateParserTest extends TestCase {
17
18
    public void testFailWhenNoDate() {
19
        try {
20
            DateParser.parseDate("potato");
21
            fail("Shouldn't parse potato!");
22
        } catch (EvaluationException e) {
23
            assertEquals(ErrorEval.VALUE_INVALID, e.getErrorEval());
24
        }
25
    }
26
27
    public void testFailWhenLooksLikeDateButItIsnt() {
28
        try {
29
            DateParser.parseDate("potato/cucumber/banana");
30
            fail("Shouldn't parse this thing!");
31
        } catch (EvaluationException e) {
32
            assertEquals(ErrorEval.VALUE_INVALID, e.getErrorEval());
33
        }
34
    }
35
36
    public void testFailWhenIsInvalidDate() {
37
        try {
38
            DateParser.parseDate("13/13/13");
39
            fail("Shouldn't parse this thing!");
40
        } catch (EvaluationException e) {
41
            assertEquals(ErrorEval.VALUE_INVALID, e.getErrorEval());
42
        }
43
    }
44
45
    public void testShouldParseValidDate() throws EvaluationException {
46
        Calendar aDate = Calendar.getInstance();
47
        aDate.setTime(new Date(84, OCTOBER, 20));
48
        assertEquals(aDate, DateParser.parseDate("1984/10/20"));
49
    }
50
51
    public void testShouldIgnoreTimestamp() throws EvaluationException {
52
        Calendar aDate = Calendar.getInstance();
53
        aDate.setTime(new Date(84, OCTOBER, 20));
54
        assertEquals(aDate, DateParser.parseDate("1984/10/20 12:34:56"));
55
    }
56
57
}

Return to bug 52378