Calculate the number of business days between two dates?

后端 未结 30 1671
悲&欢浪女
悲&欢浪女 2020-11-22 14:54

In C#, how can I calculate the number of business (or weekdays) days between two dates?

30条回答
  •  情深已故
    2020-11-22 15:41

    This solution avoids iteration, works for both +ve and -ve weekday differences and includes a unit test suite to regression against the slower method of counting weekdays. I've also include a concise method to add weekdays also works in the same non-iterative way.

    Unit tests cover a few thousand date combinations in order to exhaustively test all start/end weekday combinations with both small and large date ranges.

    Important: We make the assumption that we are counting days by excluding the start date and including the end date. This is important when counting weekdays as the specific start/end days that you include/exclude affect the result. This also ensures that the difference between two equal days is always zero and that we only include full working days as typically you want the answer to be correct for any time on the current start date (often today) and include the full end date (e.g. a due date).

    NOTE: This code needs an additional adjustment for holidays but in keeping with the above assumption, this code must exclude holidays on the start date.

    Add weekdays:

    private static readonly int[,] _addOffset = 
    {
      // 0  1  2  3  4
        {0, 1, 2, 3, 4}, // Su  0
        {0, 1, 2, 3, 4}, // M   1
        {0, 1, 2, 3, 6}, // Tu  2
        {0, 1, 4, 5, 6}, // W   3
        {0, 1, 4, 5, 6}, // Th  4
        {0, 3, 4, 5, 6}, // F   5
        {0, 2, 3, 4, 5}, // Sa  6
    };
    
    public static DateTime AddWeekdays(this DateTime date, int weekdays)
    {
        int extraDays = weekdays % 5;
        int addDays = weekdays >= 0
            ? (weekdays / 5) * 7 + _addOffset[(int)date.DayOfWeek, extraDays]
            : (weekdays / 5) * 7 - _addOffset[6 - (int)date.DayOfWeek, -extraDays];
        return date.AddDays(addDays);
    }
    

    Compute weekday difference:

    static readonly int[,] _diffOffset = 
    {
      // Su M  Tu W  Th F  Sa
        {0, 1, 2, 3, 4, 5, 5}, // Su
        {4, 0, 1, 2, 3, 4, 4}, // M 
        {3, 4, 0, 1, 2, 3, 3}, // Tu
        {2, 3, 4, 0, 1, 2, 2}, // W 
        {1, 2, 3, 4, 0, 1, 1}, // Th
        {0, 1, 2, 3, 4, 0, 0}, // F 
        {0, 1, 2, 3, 4, 5, 0}, // Sa
    };
    
    public static int GetWeekdaysDiff(this DateTime dtStart, DateTime dtEnd)
    {
        int daysDiff = (int)(dtEnd - dtStart).TotalDays;
        return daysDiff >= 0
            ? 5 * (daysDiff / 7) + _diffOffset[(int) dtStart.DayOfWeek, (int) dtEnd.DayOfWeek]
            : 5 * (daysDiff / 7) - _diffOffset[6 - (int) dtStart.DayOfWeek, 6 - (int) dtEnd.DayOfWeek];
    }
    

    I found that most other solutions on stack overflow were either slow (iterative) or overly complex and many were just plain incorrect. Moral of the story is ... Don't trust it unless you've exhaustively tested it!!

    Unit tests based on NUnit Combinatorial testing and ShouldBe NUnit extension.

    [TestFixture]
    public class DateTimeExtensionsTests
    {
        /// 
        /// Exclude start date, Include end date
        /// 
        /// 
        /// 
        /// 
        private IEnumerable GetDateRange(DateTime dtStart, DateTime dtEnd)
        {
            Console.WriteLine(@"dtStart={0:yy-MMM-dd ffffd}, dtEnd={1:yy-MMM-dd ffffd}", dtStart, dtEnd);
    
            TimeSpan diff = dtEnd - dtStart;
            Console.WriteLine(diff);
    
            if (dtStart <= dtEnd)
            {
                for (DateTime dt = dtStart.AddDays(1); dt <= dtEnd; dt = dt.AddDays(1))
                {
                    Console.WriteLine(@"dt={0:yy-MMM-dd ffffd}", dt);
                    yield return dt;
                }
            }
            else
            {
                for (DateTime dt = dtStart.AddDays(-1); dt >= dtEnd; dt = dt.AddDays(-1))
                {
                    Console.WriteLine(@"dt={0:yy-MMM-dd ffffd}", dt);
                    yield return dt;
                }
            }
        }
    
        [Test, Combinatorial]
        public void TestGetWeekdaysDiff(
            [Values(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 20, 30)]
            int startDay,
            [Values(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 20, 30)]
            int endDay,
            [Values(7)]
            int startMonth,
            [Values(7)]
            int endMonth)
        {
            // Arrange
            DateTime dtStart = new DateTime(2016, startMonth, startDay);
            DateTime dtEnd = new DateTime(2016, endMonth, endDay);
    
            int nDays = GetDateRange(dtStart, dtEnd)
                .Count(dt => dt.DayOfWeek != DayOfWeek.Saturday && dt.DayOfWeek != DayOfWeek.Sunday);
    
            if (dtEnd < dtStart) nDays = -nDays;
    
            Console.WriteLine(@"countBusDays={0}", nDays);
    
            // Act / Assert
            dtStart.GetWeekdaysDiff(dtEnd).ShouldBe(nDays);
        }
    
        [Test, Combinatorial]
        public void TestAddWeekdays(
            [Values(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 20, 30)]
            int startDay,
            [Values(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 20, 30)]
            int weekdays)
        {
            DateTime dtStart = new DateTime(2016, 7, startDay);
            DateTime dtEnd1 = dtStart.AddWeekdays(weekdays);     // ADD
            dtStart.GetWeekdaysDiff(dtEnd1).ShouldBe(weekdays);  
    
            DateTime dtEnd2 = dtStart.AddWeekdays(-weekdays);    // SUBTRACT
            dtStart.GetWeekdaysDiff(dtEnd2).ShouldBe(-weekdays);
        }
    }
    

提交回复
热议问题