1 | using System;
|
---|
2 | using System.Collections.Generic;
|
---|
3 | using System.Globalization;
|
---|
4 | using System.Linq;
|
---|
5 | using System.Text;
|
---|
6 | using OfficeOpenXml.FormulaParsing.Exceptions;
|
---|
7 | using OfficeOpenXml.FormulaParsing.ExpressionGraph;
|
---|
8 |
|
---|
9 | namespace OfficeOpenXml.FormulaParsing.Excel.Functions.DateTime
|
---|
10 | {
|
---|
11 | public class Yearfrac : ExcelFunction
|
---|
12 | {
|
---|
13 | public override CompileResult Execute(IEnumerable<FunctionArgument> arguments, ParsingContext context)
|
---|
14 | {
|
---|
15 | var functionArguments = arguments as FunctionArgument[] ?? arguments.ToArray();
|
---|
16 | ValidateArguments(functionArguments, 2);
|
---|
17 | var date1Num = ArgToDecimal(functionArguments, 0);
|
---|
18 | var date2Num = ArgToDecimal(functionArguments, 1);
|
---|
19 | if (date1Num > date2Num) //Switch to make date1 the lowest date
|
---|
20 | {
|
---|
21 | var t = date1Num;
|
---|
22 | date1Num = date2Num;
|
---|
23 | date2Num = t;
|
---|
24 | var fa = functionArguments[1];
|
---|
25 | functionArguments[1] = functionArguments[0];
|
---|
26 | functionArguments[0] = fa;
|
---|
27 | }
|
---|
28 | var date1 = System.DateTime.FromOADate(date1Num);
|
---|
29 | var date2 = System.DateTime.FromOADate(date2Num);
|
---|
30 |
|
---|
31 | var basis = 0;
|
---|
32 | if (functionArguments.Count() > 2)
|
---|
33 | {
|
---|
34 | basis = ArgToInt(functionArguments, 2);
|
---|
35 | ThrowExcelErrorValueExceptionIf(() => basis < 0 || basis > 4, eErrorType.Num);
|
---|
36 | }
|
---|
37 | var func = context.Configuration.FunctionRepository.GetFunction("days360");
|
---|
38 | var calendar = new GregorianCalendar();
|
---|
39 | switch (basis)
|
---|
40 | {
|
---|
41 | case 0:
|
---|
42 | var d360Result = System.Math.Abs(func.Execute(functionArguments, context).ResultNumeric);
|
---|
43 | // reproducing excels behaviour
|
---|
44 | if (date1.Month == 2 && date2.Day==31)
|
---|
45 | {
|
---|
46 | var daysInFeb = calendar.IsLeapYear(date1.Year) ? 29 : 28;
|
---|
47 | if (date1.Day == daysInFeb) d360Result++;
|
---|
48 | }
|
---|
49 | return CreateResult(d360Result / 360d, DataType.Decimal);
|
---|
50 | case 1:
|
---|
51 | return CreateResult(System.Math.Abs((date2 - date1).TotalDays / CalculateAcutalYear(date1, date2)), DataType.Decimal);
|
---|
52 | case 2:
|
---|
53 | return CreateResult(System.Math.Abs((date2 - date1).TotalDays / 360d), DataType.Decimal);
|
---|
54 | case 3:
|
---|
55 | return CreateResult(System.Math.Abs((date2 - date1).TotalDays / 365d), DataType.Decimal);
|
---|
56 | case 4:
|
---|
57 | var args = functionArguments.ToList();
|
---|
58 | args.Add(new FunctionArgument(true));
|
---|
59 | double? result = System.Math.Abs(func.Execute(args, context).ResultNumeric / 360d);
|
---|
60 | return CreateResult(result.Value, DataType.Decimal);
|
---|
61 | default:
|
---|
62 | return null;
|
---|
63 | }
|
---|
64 | }
|
---|
65 |
|
---|
66 | private double CalculateAcutalYear(System.DateTime dt1, System.DateTime dt2)
|
---|
67 | {
|
---|
68 | var calendar = new GregorianCalendar();
|
---|
69 | var perYear = 0d;
|
---|
70 | var nYears = dt2.Year - dt1.Year + 1;
|
---|
71 | for (var y = dt1.Year; y <= dt2.Year; ++y)
|
---|
72 | {
|
---|
73 | perYear += calendar.IsLeapYear(y) ? 366 : 365;
|
---|
74 | }
|
---|
75 | if (new System.DateTime(dt1.Year + 1, dt1.Month, dt1.Day) >= dt2)
|
---|
76 | {
|
---|
77 | nYears = 1;
|
---|
78 | perYear = 365;
|
---|
79 | if (calendar.IsLeapYear(dt1.Year) && dt1.Month <= 2)
|
---|
80 | perYear = 366;
|
---|
81 | else if (calendar.IsLeapYear(dt2.Year) && dt2.Month > 2)
|
---|
82 | perYear = 366;
|
---|
83 | else if (dt2.Month == 2 && dt2.Day == 29)
|
---|
84 | perYear = 366;
|
---|
85 | }
|
---|
86 | return perYear/(double) nYears;
|
---|
87 | }
|
---|
88 | }
|
---|
89 | }
|
---|