diff --git a/ClosedXML/Excel/CalcEngine/Functions/DateAndTime.cs b/ClosedXML/Excel/CalcEngine/Functions/DateAndTime.cs index e1678b6..1a388e6 100644 --- a/ClosedXML/Excel/CalcEngine/Functions/DateAndTime.cs +++ b/ClosedXML/Excel/CalcEngine/Functions/DateAndTime.cs @@ -1,9 +1,7 @@ using System; -using System.Collections; using System.Collections.Generic; using System.Globalization; using System.Linq; -using System.Text; namespace ClosedXML.Excel.CalcEngine.Functions { @@ -33,7 +31,6 @@ ce.RegisterFunction("WORKDAY", 2, 3, Workday); // Returns the serial number of the date before or after a specified number of workdays ce.RegisterFunction("YEAR", 1, Year); // Converts a serial number to a year ce.RegisterFunction("YEARFRAC", 2, 3, Yearfrac); // Returns the year fraction representing the number of whole days between start_date and end_date - } /// @@ -50,7 +47,7 @@ firstDay = firstDay.Date; lastDay = lastDay.Date; if (firstDay > lastDay) - throw new ArgumentException("Incorrect last day " + lastDay); + return -BusinessDaysUntil(lastDay, firstDay, bankHolidays); TimeSpan span = lastDay - firstDay; int businessDays = span.Days + 1; @@ -90,23 +87,23 @@ private static object Date(List p) { - var year = (int) p[0]; - var month = (int) p[1]; - var day = (int) p[2]; + var year = (int)p[0]; + var month = (int)p[1]; + var day = (int)p[2]; - return (int) Math.Floor(new DateTime(year, month, day).ToOADate()); + return (int)Math.Floor(new DateTime(year, month, day).ToOADate()); } private static object Datevalue(List p) { - var date = (string) p[0]; + var date = (string)p[0]; - return (int) Math.Floor(DateTime.Parse(date).ToOADate()); + return (int)Math.Floor(DateTime.Parse(date).ToOADate()); } private static object Day(List p) { - var date = (DateTime) p[0]; + var date = (DateTime)p[0]; return date.Day; } @@ -164,7 +161,6 @@ } return 360 * (y2 - y1) + 30 * (m2 - m1) + (d2 - d1); - } private static object Edate(List p) @@ -227,7 +223,7 @@ private static object Month(List p) { - var date = (DateTime) p[0]; + var date = (DateTime)p[0]; return date.Month; } @@ -309,7 +305,6 @@ var daysRequired = (int)p[1]; if (daysRequired == 0) return startDate; - if (daysRequired < 0) throw new ArgumentOutOfRangeException("DaysRequired must be >= 0."); var bankHolidays = new List(); if (p.Count == 3) @@ -318,13 +313,18 @@ bankHolidays.AddRange(t.Select(XLHelper.GetDate)); } - var testDate = startDate.AddDays(((daysRequired / 7) + 2) * 7); - return Workday(startDate, testDate, daysRequired, bankHolidays).NextWorkday(bankHolidays); + var testDate = startDate.AddDays(((daysRequired / 7) + 2) * 7 * Math.Sign(daysRequired)); + var return_date = Workday(startDate, testDate, daysRequired, bankHolidays); + if (Math.Sign(daysRequired) == 1) + return_date = return_date.NextWorkday(bankHolidays); + else + return_date = return_date.PreviousWorkDay(bankHolidays); + + return return_date; } private static DateTime Workday(DateTime startDate, DateTime testDate, int daysRequired, IEnumerable bankHolidays) { - var businessDays = BusinessDaysUntil(startDate, testDate, bankHolidays); if (businessDays == daysRequired) return testDate; @@ -336,14 +336,15 @@ private static object Year(List p) { - var date = (DateTime) p[0]; + var date = (DateTime)p[0]; return date.Year; } + private static object Yearfrac(List p) { - var date1 = (DateTime) p[0]; - var date2 = (DateTime) p[1]; + var date1 = (DateTime)p[0]; + var date2 = (DateTime)p[1]; var option = p.Count == 3 ? (int)p[2] : 0; if (option == 0) @@ -358,5 +359,4 @@ return Days360(date1, date2, true) / 360.0; } } - } diff --git a/ClosedXML/Extensions.cs b/ClosedXML/Extensions.cs index a37daf3..334b57b 100644 --- a/ClosedXML/Extensions.cs +++ b/ClosedXML/Extensions.cs @@ -149,17 +149,31 @@ } } - public static DateTime NextWorkday(this DateTime date, List bankHolidays) + public static DateTime NextWorkday(this DateTime date, IEnumerable bankHolidays) { var nextDate = date.AddDays(1); - while (nextDate.DayOfWeek == DayOfWeek.Saturday - || nextDate.DayOfWeek == DayOfWeek.Sunday - || bankHolidays.Contains(nextDate) - ) + while (!nextDate.IsWorkDay(bankHolidays)) nextDate = nextDate.AddDays(1); return nextDate; } + + public static DateTime PreviousWorkDay(this DateTime date, IEnumerable bankHolidays) + { + var previousDate = date.AddDays(-1); + while (!previousDate.IsWorkDay(bankHolidays)) + previousDate = previousDate.AddDays(-1); + + return previousDate; + } + + public static bool IsWorkDay(this DateTime date, IEnumerable bankHolidays) + { + return date.DayOfWeek != DayOfWeek.Saturday + && date.DayOfWeek != DayOfWeek.Sunday + && !bankHolidays.Contains(date); + } + } public static class IntegerExtensions diff --git a/ClosedXML_Tests/Excel/CalcEngine/DateAndTimeTests.cs b/ClosedXML_Tests/Excel/CalcEngine/DateAndTimeTests.cs index b6202d9..08e874b 100644 --- a/ClosedXML_Tests/Excel/CalcEngine/DateAndTimeTests.cs +++ b/ClosedXML_Tests/Excel/CalcEngine/DateAndTimeTests.cs @@ -190,6 +190,16 @@ } [Test] + public void Networkdays_NegativeResult() + { + Object actual = XLWorkbook.EvaluateExpr("Networkdays(\"3/01/2009\", \"10/01/2008\")"); + Assert.AreEqual(-108, actual); + + actual = XLWorkbook.EvaluateExpr("Networkdays(\"2016-01-01\", \"2015-12-23\")"); + Assert.AreEqual(-8, actual); + } + + [Test] public void Networkdays_OneHolidaysGiven() { Object actual = XLWorkbook.EvaluateExpr("Networkdays(\"10/01/2008\", \"3/01/2009\", \"11/26/2008\")"); @@ -407,6 +417,9 @@ { Object actual = XLWorkbook.EvaluateExpr("Workday(\"10/01/2008\", 151)"); Assert.AreEqual(new DateTime(2009, 4, 30), actual); + + actual = XLWorkbook.EvaluateExpr("Workday(\"2016-01-01\", -10)"); + Assert.AreEqual(new DateTime(2015, 12, 18), actual); } [Test]