diff --git a/src/java/org/apache/poi/ss/formula/functions/Days360.java b/src/java/org/apache/poi/ss/formula/functions/Days360.java index 8dba97656..087e16b42 100644 --- a/src/java/org/apache/poi/ss/formula/functions/Days360.java +++ b/src/java/org/apache/poi/ss/formula/functions/Days360.java @@ -67,38 +67,33 @@ import org.apache.poi.util.LocaleUtil; */ public class Days360 extends Var2or3ArgFunction { public ValueEval evaluate(int srcRowIndex, int srcColumnIndex, ValueEval arg0, ValueEval arg1) { - double result; try { double d0 = NumericFunction.singleOperandEvaluate(arg0, srcRowIndex, srcColumnIndex); double d1 = NumericFunction.singleOperandEvaluate(arg1, srcRowIndex, srcColumnIndex); - result = evaluate(d0, d1, false); + return new NumberEval(evaluate(d0, d1, false)); } catch (EvaluationException e) { return e.getErrorEval(); } - return new NumberEval(result); } public ValueEval evaluate(int srcRowIndex, int srcColumnIndex, ValueEval arg0, ValueEval arg1, ValueEval arg2) { - double result; try { double d0 = NumericFunction.singleOperandEvaluate(arg0, srcRowIndex, srcColumnIndex); double d1 = NumericFunction.singleOperandEvaluate(arg1, srcRowIndex, srcColumnIndex); ValueEval ve = OperandResolver.getSingleValue(arg2, srcRowIndex, srcColumnIndex); Boolean method = OperandResolver.coerceValueToBoolean(ve, false); - result = evaluate(d0, d1, method == null ? false : method.booleanValue()); + return new NumberEval(evaluate(d0, d1, method != null && method.booleanValue())); } catch (EvaluationException e) { return e.getErrorEval(); } - return new NumberEval(result); } private static double evaluate(double d0, double d1, boolean method) { Calendar realStart = getDate(d0); Calendar realEnd = getDate(d1); int startingDate[] = getStartingDate(realStart, method); - int endingDate[] = getEndingDate(realEnd, realStart, method); - + int endingDate[] = getEndingDate(realEnd, startingDate, method); return (endingDate[0]*360+endingDate[1]*30+endingDate[2])- (startingDate[0]*360+startingDate[1]*30+startingDate[2]); @@ -111,34 +106,32 @@ public class Days360 extends Var2or3ArgFunction { } private static int[] getStartingDate(Calendar realStart, boolean method) { - Calendar d = realStart; - int yyyy = d.get(Calendar.YEAR); - int mm = d.get(Calendar.MONTH); - int dd = Math.min(30, d.get(Calendar.DAY_OF_MONTH)); + int yyyy = realStart.get(Calendar.YEAR); + int mm = realStart.get(Calendar.MONTH); + int dd = Math.min(30, realStart.get(Calendar.DAY_OF_MONTH)); - if (method == false && isLastDayOfMonth(d)) dd = 30; + if (!method && isLastDayOfMonth(realStart)) dd = 30; return new int[]{yyyy,mm,dd}; } - private static int[] getEndingDate(Calendar realEnd, Calendar realStart, boolean method) { - Calendar d = realEnd; - int yyyy = d.get(Calendar.YEAR); - int mm = d.get(Calendar.MONTH); - int dd = Math.min(30, d.get(Calendar.DAY_OF_MONTH)); + private static int[] getEndingDate(Calendar realEnd, int startingDate[], boolean method) { + int yyyy = realEnd.get(Calendar.YEAR); + int mm = realEnd.get(Calendar.MONTH); + int dd = Math.min(30, realEnd.get(Calendar.DAY_OF_MONTH)); - if (method == false && realEnd.get(Calendar.DAY_OF_MONTH) == 31) { - if (realStart.get(Calendar.DAY_OF_MONTH) < 30) { - d.set(Calendar.DAY_OF_MONTH, 1); - d.add(Calendar.MONTH, 1); - yyyy = d.get(Calendar.YEAR); - mm = d.get(Calendar.MONTH); + if (!method && realEnd.get(Calendar.DAY_OF_MONTH) == 31) { + if (startingDate[2] < 30) { + realEnd.set(Calendar.DAY_OF_MONTH, 1); + realEnd.add(Calendar.MONTH, 1); + yyyy = realEnd.get(Calendar.YEAR); + mm = realEnd.get(Calendar.MONTH); dd = 1; } else { dd = 30; } } - + return new int[]{yyyy,mm,dd}; } diff --git a/src/testcases/org/apache/poi/ss/formula/functions/TestDays360.java b/src/testcases/org/apache/poi/ss/formula/functions/TestDays360.java index dbe41a883..159477057 100644 --- a/src/testcases/org/apache/poi/ss/formula/functions/TestDays360.java +++ b/src/testcases/org/apache/poi/ss/formula/functions/TestDays360.java @@ -33,114 +33,133 @@ import org.junit.Test; public final class TestDays360 { - /** - * @param month 1-based - */ - private static Date makeDate(int year, int month, int day) { - Calendar cal = LocaleUtil.getLocaleCalendar(year, month-1, day); - return cal.getTime(); - } + /** + * @param month 1-based + */ + private static Date makeDate(int year, int month, int day) { + Calendar cal = LocaleUtil.getLocaleCalendar(year, month-1, day); + return cal.getTime(); + } - private static Date decrementDay(Date d) { - Calendar c = LocaleUtil.getLocaleCalendar(); - c.setTime(d); - c.add(Calendar.DAY_OF_MONTH, -1); - return c.getTime(); - } + private static Date decrementDay(Date d) { + Calendar c = LocaleUtil.getLocaleCalendar(); + c.setTime(d); + c.add(Calendar.DAY_OF_MONTH, -1); + return c.getTime(); + } - @Test - public void testBasic() { - confirm(120, 2009, 1, 15, 2009, 5, 15); - confirm(158, 2009, 1, 26, 2009, 7, 4); + @Test + public void testBasic() { + confirm(120, 2009, 1, 15, 2009, 5, 15); + confirm(158, 2009, 1, 26, 2009, 7, 4); - // same results in leap years - confirm(120, 2008, 1, 15, 2008, 5, 15); - confirm(158, 2008, 1, 26, 2008, 7, 4); + // same results in leap years + confirm(120, 2008, 1, 15, 2008, 5, 15); + confirm(158, 2008, 1, 26, 2008, 7, 4); - // longer time spans - confirm(562, 2008, 8, 11, 2010, 3, 3); - confirm(916, 2007, 2, 23, 2009, 9, 9); - - // other tests - confirm(1, makeDate(1993, 2, 28), makeDate(1993, 3, 1), false); + // longer time spans + confirm(562, 2008, 8, 11, 2010, 3, 3); + confirm(916, 2007, 2, 23, 2009, 9, 9); + + // other tests + confirm(1, makeDate(1993, 2, 28), makeDate(1993, 3, 1), false); confirm(1, makeDate(1996, 2, 29), makeDate(1996, 3, 1), false); confirm(-2, makeDate(1993, 2, 28), makeDate(1993, 2, 28), false); confirm(3, makeDate(1993, 2, 28), makeDate(1993, 3, 1), true); confirm(2, makeDate(1996, 2, 29), makeDate(1996, 3, 1), true); - } - private static void confirm(int expResult, int y1, int m1, int d1, int y2, int m2, int d2) { - confirm(expResult, makeDate(y1, m1, d1), makeDate(y2, m2, d2), false); - confirm(-expResult, makeDate(y2, m2, d2), makeDate(y1, m1, d1), false); - } - - /** - * The method parameter only makes a difference when the second parameter - * is the last day of the month that does not have 30 days. - */ - @Test - public void testMonthBoundaries() { - // jan - confirmMonthBoundary(false, 2001, 1, 0, 0, 2, 3, 4); - confirmMonthBoundary(true, 2001, 1, 0, 0, 1, 2, 3); - // feb - confirmMonthBoundary(false, 2001, 2,-2, 1, 2, 3, 4); - confirmMonthBoundary(true, 2001, 2, 0, 1, 2, 3, 4); - // mar - confirmMonthBoundary(false, 2001, 3, 0, 0, 2, 3, 4); - confirmMonthBoundary(true, 2001, 3, 0, 0, 1, 2, 3); - // apr - confirmMonthBoundary(false, 2001, 4, 0, 1, 2, 3, 4); - confirmMonthBoundary(true, 2001, 4, 0, 1, 2, 3, 4); - // may - confirmMonthBoundary(false, 2001, 5, 0, 0, 2, 3, 4); - confirmMonthBoundary(true, 2001, 5, 0, 0, 1, 2, 3); - // jun - confirmMonthBoundary(false, 2001, 6, 0, 1, 2, 3, 4); - confirmMonthBoundary(true, 2001, 6, 0, 1, 2, 3, 4); + // from https://support.office.com/en-us/article/DAYS360-function-B9A509FD-49EF-407E-94DF-0CBDA5718C2A + confirm(1, makeDate(2011, 1, 30), makeDate(2011, 2, 1), false); + confirm(360, makeDate(2011, 1, 1), makeDate(2011, 12, 31), false); + confirm(30, makeDate(2011, 1, 1), makeDate(2011, 2, 1), false); + } + + private static void confirm(int expResult, int y1, int m1, int d1, int y2, int m2, int d2) { + confirm(expResult, makeDate(y1, m1, d1), makeDate(y2, m2, d2), false); + confirm(-expResult, makeDate(y2, m2, d2), makeDate(y1, m1, d1), false); + } + + /** + * The method parameter only makes a difference when the second parameter + * is the last day of the month that does not have 30 days. + */ + @Test + public void testMonthBoundaries() { + // jan + confirmMonthBoundary(false, 2001, 1, 0, 0, 2, 3, 4); + confirmMonthBoundary(true, 2001, 1, 0, 0, 1, 2, 3); + // feb + confirmMonthBoundary(false, 2001, 2,-2, 1, 2, 3, 4); + confirmMonthBoundary(true, 2001, 2, 0, 1, 2, 3, 4); + // mar + confirmMonthBoundary(false, 2001, 3, 0, 0, 2, 3, 4); + confirmMonthBoundary(true, 2001, 3, 0, 0, 1, 2, 3); + // apr + confirmMonthBoundary(false, 2001, 4, 0, 1, 2, 3, 4); + confirmMonthBoundary(true, 2001, 4, 0, 1, 2, 3, 4); + // may + confirmMonthBoundary(false, 2001, 5, 0, 0, 2, 3, 4); + confirmMonthBoundary(true, 2001, 5, 0, 0, 1, 2, 3); + // jun + confirmMonthBoundary(false, 2001, 6, 0, 1, 2, 3, 4); + confirmMonthBoundary(true, 2001, 6, 0, 1, 2, 3, 4); // leap year confirmMonthBoundary(false, 2012, 2, -1, 1, 2, 3, 4); confirmMonthBoundary(true, 2012, 2, 0, 1, 2, 3, 4); - } + + // bug 60029 + Date start = makeDate(2018, 2, 28); + Date end = makeDate(2018, 3, 31); + confirm(30, start, end, false); + + // examples from https://support.office.com/en-us/article/DAYS360-function-B9A509FD-49EF-407E-94DF-0CBDA5718C2A + start = makeDate(2011, 1, 30); + end = makeDate(2011, 2, 1); + confirm(1, start, end, false); + + start = makeDate(2011, 1, 1); + end = makeDate(2011, 12, 31); + confirm(360, start, end, false); + + start = makeDate(2011, 1, 1); + end = makeDate(2011, 2, 1); + confirm(30, start, end, false); + } - /** - * @param monthNo 1-based - * @param diffs - */ - private static void confirmMonthBoundary(boolean method, int year, int monthNo, int...diffs) { - Date firstDayOfNextMonth = makeDate(year, monthNo+1, 1); - Date secondArg = decrementDay(firstDayOfNextMonth); - Date firstArg = secondArg; + /** + * @param monthNo 1-based + */ + private static void confirmMonthBoundary(boolean method, int year, int monthNo, int...diffs) { + Date firstDayOfNextMonth = makeDate(year, monthNo+1, 1); + Date secondArg = decrementDay(firstDayOfNextMonth); + Date firstArg = secondArg; - for (int expResult : diffs) { - confirm(expResult, firstArg, secondArg, method); - firstArg = decrementDay(firstArg); - } + for (int expResult : diffs) { + confirm(expResult, firstArg, secondArg, method); + firstArg = decrementDay(firstArg); + } - } - private static void confirm(int expResult, Date firstArg, Date secondArg, boolean method) { + } + private static void confirm(int expResult, Date firstArg, Date secondArg, boolean method) { + ValueEval ve; + if (method) { + ve = invokeDays360(convert(firstArg), convert(secondArg), BoolEval.TRUE); + } else { + ve = invokeDays360(convert(firstArg), convert(secondArg)); + } + assertTrue("wrong return type (" + ve.getClass().getName() + ")", ve instanceof NumberEval); - ValueEval ve; - if (method) { - // TODO enable 3rd arg - - ve = invokeDays360(convert(firstArg), convert(secondArg), BoolEval.valueOf(method)); - } else { - ve = invokeDays360(convert(firstArg), convert(secondArg)); - } - assertTrue("wrong return type (" + ve.getClass().getName() + ")", ve instanceof NumberEval); - - NumberEval numberEval = (NumberEval) ve; - String err = String.format(Locale.ROOT, "days360(%tF,%tF,%b) wrong result", firstArg, secondArg, method); - assertEquals(err, expResult, numberEval.getNumberValue(), 0); - } - - private static ValueEval invokeDays360(ValueEval...args) { - return new Days360().evaluate(args, -1, -1); - } - - private static NumberEval convert(Date d) { - return new NumberEval(HSSFDateUtil.getExcelDate(d)); - } + NumberEval numberEval = (NumberEval) ve; + String err = String.format(Locale.ROOT, "days360(%tF,%tF,%b) wrong result", firstArg, secondArg, method); + assertEquals(err, expResult, numberEval.getNumberValue(), 0); + } + + private static ValueEval invokeDays360(ValueEval...args) { + return new Days360().evaluate(args, -1, -1); + } + + private static NumberEval convert(Date d) { + return new NumberEval(HSSFDateUtil.getExcelDate(d)); + } } -