| using System.Collections.Generic; | |
| using System.Globalization; | |
| using System.Linq; | |
| using OfficeOpenXml.FormulaParsing.ExpressionGraph; | |
| namespace OfficeOpenXml.FormulaParsing.Excel.Functions.DateTime; | |
| public class Yearfrac : ExcelFunction { | |
| public override CompileResult Execute( | |
| IEnumerable<FunctionArgument> arguments, | |
| ParsingContext context) { | |
| var functionArguments = arguments as FunctionArgument[] ?? arguments.ToArray(); | |
| ValidateArguments(functionArguments, 2); | |
| var date1Num = ArgToDecimal(functionArguments, 0); | |
| var date2Num = ArgToDecimal(functionArguments, 1); | |
| if (date1Num | |
| > date2Num) //Switch to make date1 the lowest date | |
| { | |
| var t = date1Num; | |
| date1Num = date2Num; | |
| date2Num = t; | |
| var fa = functionArguments[1]; | |
| functionArguments[1] = functionArguments[0]; | |
| functionArguments[0] = fa; | |
| } | |
| var date1 = System.DateTime.FromOADate(date1Num); | |
| var date2 = System.DateTime.FromOADate(date2Num); | |
| var basis = 0; | |
| if (functionArguments.Count() > 2) { | |
| basis = ArgToInt(functionArguments, 2); | |
| ThrowExcelErrorValueExceptionIf(() => basis < 0 || basis > 4, eErrorType.Num); | |
| } | |
| var func = context.Configuration.FunctionRepository.GetFunction("days360"); | |
| var calendar = new GregorianCalendar(); | |
| switch (basis) { | |
| case 0: | |
| var d360Result = System.Math.Abs(func.Execute(functionArguments, context).ResultNumeric); | |
| // reproducing excels behaviour | |
| if (date1.Month == 2 && date2.Day == 31) { | |
| var daysInFeb = calendar.IsLeapYear(date1.Year) ? 29 : 28; | |
| if (date1.Day == daysInFeb) { | |
| d360Result++; | |
| } | |
| } | |
| return CreateResult(d360Result / 360d, DataType.Decimal); | |
| case 1: | |
| return CreateResult( | |
| System.Math.Abs((date2 - date1).TotalDays / CalculateAcutalYear(date1, date2)), | |
| DataType.Decimal); | |
| case 2: | |
| return CreateResult(System.Math.Abs((date2 - date1).TotalDays / 360d), DataType.Decimal); | |
| case 3: | |
| return CreateResult(System.Math.Abs((date2 - date1).TotalDays / 365d), DataType.Decimal); | |
| case 4: | |
| var args = functionArguments.ToList(); | |
| args.Add(new(true)); | |
| double? result = System.Math.Abs(func.Execute(args, context).ResultNumeric / 360d); | |
| return CreateResult(result.Value, DataType.Decimal); | |
| default: | |
| return null; | |
| } | |
| } | |
| private double CalculateAcutalYear(System.DateTime dt1, System.DateTime dt2) { | |
| var calendar = new GregorianCalendar(); | |
| var perYear = 0d; | |
| var nYears = dt2.Year - dt1.Year + 1; | |
| for (var y = dt1.Year; y <= dt2.Year; ++y) { | |
| perYear += calendar.IsLeapYear(y) ? 366 : 365; | |
| } | |
| if (new System.DateTime(dt1.Year + 1, dt1.Month, dt1.Day) >= dt2) { | |
| nYears = 1; | |
| perYear = 365; | |
| if (calendar.IsLeapYear(dt1.Year) && dt1.Month <= 2) { | |
| perYear = 366; | |
| } else if (calendar.IsLeapYear(dt2.Year) && dt2.Month > 2) { | |
| perYear = 366; | |
| } else if (dt2.Month == 2 && dt2.Day == 29) { | |
| perYear = 366; | |
| } | |
| } | |
| return perYear / nYears; | |
| } | |
| } |