Bug 60029 - Problem with Days360 method for the month of february
Summary: Problem with Days360 method for the month of february
Status: RESOLVED FIXED
Alias: None
Product: POI
Classification: Unclassified
Component: POI Overall (show other bugs)
Version: 3.14-FINAL
Hardware: PC All
: P2 normal (vote)
Target Milestone: ---
Assignee: POI Developers List
URL:
Keywords:
Depends on:
Blocks:
 
Reported: 2016-08-22 09:39 UTC by Julien Houeix
Modified: 2016-10-15 06:57 UTC (History)
0 users



Attachments
Result in Excel 2016 (36.38 KB, image/png)
2016-08-22 11:55 UTC, Julien Houeix
Details
Failing unit test (1.19 KB, patch)
2016-08-22 16:13 UTC, Javen O'Neal
Details | Diff

Note You need to log in before you can comment on or make changes to this bug.
Description Julien Houeix 2016-08-22 09:39:08 UTC
Hello,

There is a problem with the Days360 class in POI (org.apache.poi.ss.formula.functions.Days360.class).

The result of the evaluation is not the same as the Excel method when the start date is the last day of february and the end date is the last day of the month:
Start date: 02/28/2018
End date: 03/31/2018
Result in Excel: 30
Result in Apache POI: 31

The problem is in the method getEndingDate: you should not be using realStart but the result of getStartingDate.

Here is a solution:

    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, startingDate, method);
        return
            (endingDate[0]*360+endingDate[1]*30+endingDate[2])-
            (startingDate[0]*360+startingDate[1]*30+startingDate[2]);
    }

    private static int[] getEndingDate(Calendar realEnd, int startingDate[], 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));

        if (method == false && realEnd.get(Calendar.DAY_OF_MONTH) == 31) {
            if (startingDate[2] < 30) {
                d.set(Calendar.DAY_OF_MONTH, 1);
                d.add(Calendar.MONTH, 1);
                yyyy = d.get(Calendar.YEAR);
                mm = d.get(Calendar.MONTH);
                dd = 1;
            } else {
                dd = 30;
            }
        }
        
        return new int[]{yyyy,mm,dd};
    }
Comment 1 Mark Murphy 2016-08-22 11:08:57 UTC
(In reply to Julien Houeix from comment #0)
> Hello,
> 
> There is a problem with the Days360 class in POI
> (org.apache.poi.ss.formula.functions.Days360.class).
> 
> The result of the evaluation is not the same as the Excel method when the
> start date is the last day of february and the end date is the last day of
> the month:
> Start date: 02/28/2018
> End date: 03/31/2018
> Result in Excel: 30
> Result in Apache POI: 31

I just tried this in Excel 2016, and it gave me 31. Maybe your build of Excel has a bug? 31 is indeed the correct answer.
Comment 2 Julien Houeix 2016-08-22 11:55:43 UTC
Created attachment 34168 [details]
Result in Excel 2016
Comment 3 Julien Houeix 2016-08-22 11:57:18 UTC
I get 30 with several versions of Excel, including Excel 2016. I attached a screenshot.
Comment 4 Mark Murphy 2016-08-22 14:47:08 UTC
Ok, I see what you are looking at.
Comment 5 Javen O'Neal 2016-08-22 16:13:52 UTC
Created attachment 34169 [details]
Failing unit test

I can confirm the described behavior is a bug in the latest trunk build (r1757092, pre-3.16). See attached failing test case.

For reference, Microsoft's official documentation for this function is here:
https://support.office.com/en-us/article/DAYS360-function-B9A509FD-49EF-407E-94DF-0CBDA5718C2A

Days360.java: https://svn.apache.org/viewvc/poi/trunk/src/java/org/apache/poi/ss/formula/functions/Days360.java
TestDays360: https://svn.apache.org/viewvc/poi/trunk/src/testcases/org/apache/poi/ss/formula/functions/TestDays360.java
Comment 6 Dominik Stadler 2016-10-15 06:57:12 UTC
Fixed via r1765018 with some additional adjustments to reduce the size of the code and fix IDE warnings. Also some more tests added.