Calculate the number of business days between two dates?

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

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

30条回答
  •  萌比男神i
    2020-11-22 15:40

    Well this has been beaten to death. :) However I'm still going to provide another answer because I needed something a bit different. This solution is different in that it returns a Business TimeSpan between the start and end, and you can set the business hours of the day, and add holidays. So you can use it to calculate if it happens within a day, across days, over weekends, and even holidays. And you can get just the business days or not by just getting what you need from the returned TimeSpan object. And the way it uses lists of days, you can see how very easy it would be to add the list of non-work days if it's not the typical Sat and Sun. And I tested for a year, and it seems super fast.

    I just hope the pasting of the code is accurate. But I know it works.

    public static TimeSpan GetBusinessTimespanBetween(
        DateTime start, DateTime end,
        TimeSpan workdayStartTime, TimeSpan workdayEndTime,
        List holidays = null)
    {
        if (end < start)
            throw new ArgumentException("start datetime must be before end datetime.");
    
        // Just create an empty list for easier coding.
        if (holidays == null) holidays = new List();
    
        if (holidays.Where(x => x.TimeOfDay.Ticks > 0).Any())
            throw new ArgumentException("holidays can not have a TimeOfDay, only the Date.");
    
        var nonWorkDays = new List() { DayOfWeek.Saturday, DayOfWeek.Sunday };
    
        var startTime = start.TimeOfDay;
    
        // If the start time is before the starting hours, set it to the starting hour.
        if (startTime < workdayStartTime) startTime = workdayStartTime;
    
        var timeBeforeEndOfWorkDay = workdayEndTime - startTime;
    
        // If it's after the end of the day, then this time lapse doesn't count.
        if (timeBeforeEndOfWorkDay.TotalSeconds < 0) timeBeforeEndOfWorkDay = new TimeSpan();
        // If start is during a non work day, it doesn't count.
        if (nonWorkDays.Contains(start.DayOfWeek)) timeBeforeEndOfWorkDay = new TimeSpan();
        else if (holidays.Contains(start.Date)) timeBeforeEndOfWorkDay = new TimeSpan();
    
        var endTime = end.TimeOfDay;
    
        // If the end time is after the ending hours, set it to the ending hour.
        if (endTime > workdayEndTime) endTime = workdayEndTime;
    
        var timeAfterStartOfWorkDay = endTime - workdayStartTime;
    
        // If it's before the start of the day, then this time lapse doesn't count.
        if (timeAfterStartOfWorkDay.TotalSeconds < 0) timeAfterStartOfWorkDay = new TimeSpan();
        // If end is during a non work day, it doesn't count.
        if (nonWorkDays.Contains(end.DayOfWeek)) timeAfterStartOfWorkDay = new TimeSpan();
        else if (holidays.Contains(end.Date)) timeAfterStartOfWorkDay = new TimeSpan();
    
        // Easy scenario if the times are during the day day.
        if (start.Date.CompareTo(end.Date) == 0)
        {
            if (nonWorkDays.Contains(start.DayOfWeek)) return new TimeSpan();
            else if (holidays.Contains(start.Date)) return new TimeSpan();
            return endTime - startTime;
        }
        else
        {
            var timeBetween = end - start;
            var daysBetween = (int)Math.Floor(timeBetween.TotalDays);
            var dailyWorkSeconds = (int)Math.Floor((workdayEndTime - workdayStartTime).TotalSeconds);
    
            var businessDaysBetween = 0;
    
            // Now the fun begins with calculating the actual Business days.
            if (daysBetween > 0)
            {
                var nextStartDay = start.AddDays(1).Date;
                var dayBeforeEnd = end.AddDays(-1).Date;
                for (DateTime d = nextStartDay; d <= dayBeforeEnd; d = d.AddDays(1))
                {
                    if (nonWorkDays.Contains(d.DayOfWeek)) continue;
                    else if (holidays.Contains(d.Date)) continue;
                    businessDaysBetween++;
                }
            }
    
            var dailyWorkSecondsToAdd = dailyWorkSeconds * businessDaysBetween;
    
            var output = timeBeforeEndOfWorkDay + timeAfterStartOfWorkDay;
            output = output + new TimeSpan(0, 0, dailyWorkSecondsToAdd);
    
            return output;
        }
    }
    

    And here is test code: Note that you just have to put this function in a class called DateHelper for the test code to work.

    [TestMethod]
    public void TestGetBusinessTimespanBetween()
    {
        var workdayStart = new TimeSpan(8, 0, 0);
        var workdayEnd = new TimeSpan(17, 0, 0);
    
        var holidays = new List()
        {
            new DateTime(2018, 1, 15), // a Monday
            new DateTime(2018, 2, 15) // a Thursday
        };
    
        var testdata = new[]
        {
            new
            {
                expectedMinutes = 0,
                start = new DateTime(2016, 10, 19, 9, 50, 0),
                end = new DateTime(2016, 10, 19, 9, 50, 0)
            },
            new
            {
                expectedMinutes = 10,
                start = new DateTime(2016, 10, 19, 9, 50, 0),
                end = new DateTime(2016, 10, 19, 10, 0, 0)
            },
            new
            {
                expectedMinutes = 5,
                start = new DateTime(2016, 10, 19, 7, 50, 0),
                end = new DateTime(2016, 10, 19, 8, 5, 0)
            },
            new
            {
                expectedMinutes = 5,
                start = new DateTime(2016, 10, 19, 16, 55, 0),
                end = new DateTime(2016, 10, 19, 17, 5, 0)
            },
            new
            {
                expectedMinutes = 15,
                start = new DateTime(2016, 10, 19, 16, 50, 0),
                end = new DateTime(2016, 10, 20, 8, 5, 0)
            },
            new
            {
                expectedMinutes = 10,
                start = new DateTime(2016, 10, 19, 16, 50, 0),
                end = new DateTime(2016, 10, 20, 7, 55, 0)
            },
            new
            {
                expectedMinutes = 5,
                start = new DateTime(2016, 10, 19, 17, 10, 0),
                end = new DateTime(2016, 10, 20, 8, 5, 0)
            },
            new
            {
                expectedMinutes = 0,
                start = new DateTime(2016, 10, 19, 17, 10, 0),
                end = new DateTime(2016, 10, 20, 7, 5, 0)
            },
            new
            {
                expectedMinutes = 545,
                start = new DateTime(2016, 10, 19, 12, 10, 0),
                end = new DateTime(2016, 10, 20, 12, 15, 0)
            },
            // Spanning multiple weekdays
            new
            {
                expectedMinutes = 835,
                start = new DateTime(2016, 10, 19, 12, 10, 0),
                end = new DateTime(2016, 10, 21, 8, 5, 0)
            },
            // Spanning multiple weekdays
            new
            {
                expectedMinutes = 1375,
                start = new DateTime(2016, 10, 18, 12, 10, 0),
                end = new DateTime(2016, 10, 21, 8, 5, 0)
            },
            // Spanning from a Thursday to a Tuesday, 5 mins short of complete day.
            new
            {
                expectedMinutes = 1615,
                start = new DateTime(2016, 10, 20, 12, 10, 0),
                end = new DateTime(2016, 10, 25, 12, 5, 0)
            },
            // Spanning from a Thursday to a Tuesday, 5 mins beyond complete day.
            new
            {
                expectedMinutes = 1625,
                start = new DateTime(2016, 10, 20, 12, 10, 0),
                end = new DateTime(2016, 10, 25, 12, 15, 0)
            },
            // Spanning from a Friday to a Monday, 5 mins beyond complete day.
            new
            {
                expectedMinutes = 545,
                start = new DateTime(2016, 10, 21, 12, 10, 0),
                end = new DateTime(2016, 10, 24, 12, 15, 0)
            },
            // Spanning from a Friday to a Monday, 5 mins short complete day.
            new
            {
                expectedMinutes = 535,
                start = new DateTime(2016, 10, 21, 12, 10, 0),
                end = new DateTime(2016, 10, 24, 12, 5, 0)
            },
            // Spanning from a Saturday to a Monday, 5 mins short complete day.
            new
            {
                expectedMinutes = 245,
                start = new DateTime(2016, 10, 22, 12, 10, 0),
                end = new DateTime(2016, 10, 24, 12, 5, 0)
            },
            // Spanning from a Saturday to a Sunday, 5 mins beyond complete day.
            new
            {
                expectedMinutes = 0,
                start = new DateTime(2016, 10, 22, 12, 10, 0),
                end = new DateTime(2016, 10, 23, 12, 15, 0)
            },
            // Times within the same Saturday.
            new
            {
                expectedMinutes = 0,
                start = new DateTime(2016, 10, 22, 12, 10, 0),
                end = new DateTime(2016, 10, 23, 12, 15, 0)
            },
            // Spanning from a Saturday to the Sunday next week.
            new
            {
                expectedMinutes = 2700,
                start = new DateTime(2016, 10, 22, 12, 10, 0),
                end = new DateTime(2016, 10, 30, 12, 15, 0)
            },
            // Spanning a year.
            new
            {
                expectedMinutes = 143355,
                start = new DateTime(2016, 10, 22, 12, 10, 0),
                end = new DateTime(2017, 10, 30, 12, 15, 0)
            },
            // Spanning a year with 2 holidays.
            new
            {
                expectedMinutes = 142815,
                start = new DateTime(2017, 10, 22, 12, 10, 0),
                end = new DateTime(2018, 10, 30, 12, 15, 0)
            },
        };
    
        foreach (var item in testdata)
        {
            Assert.AreEqual(item.expectedMinutes,
                DateHelper.GetBusinessTimespanBetween(
                    item.start, item.end,
                    workdayStart, workdayEnd,
                    holidays)
                    .TotalMinutes);
        }
    }
    

提交回复
热议问题