Рассчитать количество рабочих дней между двумя датами?

В C#, как я могу рассчитать количество рабочих (или рабочих дней) дней между двумя датами?

40 ответов

Решение

У меня была такая задача раньше, и у меня есть решение. Я бы не стал перечислять все промежуточные дни, когда этого можно избежать, как здесь. Я даже не упоминаю о создании группы экземпляров DateTime, как я видел в одном из ответов выше. Это действительно пустая трата вычислительной мощности. Особенно в реальной ситуации, когда вам нужно изучить временные интервалы в несколько месяцев. Смотрите мой код с комментариями ниже.

    /// <summary>
    /// Calculates number of business days, taking into account:
    ///  - weekends (Saturdays and Sundays)
    ///  - bank holidays in the middle of the week
    /// </summary>
    /// <param name="firstDay">First day in the time interval</param>
    /// <param name="lastDay">Last day in the time interval</param>
    /// <param name="bankHolidays">List of bank holidays excluding weekends</param>
    /// <returns>Number of business days during the 'span'</returns>
    public static int BusinessDaysUntil(this DateTime firstDay, DateTime lastDay, params DateTime[] bankHolidays)
    {
        firstDay = firstDay.Date;
        lastDay = lastDay.Date;
        if (firstDay > lastDay)
            throw new ArgumentException("Incorrect last day " + lastDay);

        TimeSpan span = lastDay - firstDay;
        int businessDays = span.Days + 1;
        int fullWeekCount = businessDays / 7;
        // find out if there are weekends during the time exceedng the full weeks
        if (businessDays > fullWeekCount*7)
        {
            // we are here to find out if there is a 1-day or 2-days weekend
            // in the time interval remaining after subtracting the complete weeks
            int firstDayOfWeek = (int) firstDay.DayOfWeek;
            int lastDayOfWeek = (int) lastDay.DayOfWeek;
            if (lastDayOfWeek < firstDayOfWeek)
                lastDayOfWeek += 7;
            if (firstDayOfWeek <= 6)
            {
                if (lastDayOfWeek >= 7)// Both Saturday and Sunday are in the remaining time interval
                    businessDays -= 2;
                else if (lastDayOfWeek >= 6)// Only Saturday is in the remaining time interval
                    businessDays -= 1;
            }
            else if (firstDayOfWeek <= 7 && lastDayOfWeek >= 7)// Only Sunday is in the remaining time interval
                businessDays -= 1;
        }

        // subtract the weekends during the full weeks in the interval
        businessDays -= fullWeekCount + fullWeekCount;

        // subtract the number of bank holidays during the time interval
        foreach (DateTime bankHoliday in bankHolidays)
        {
            DateTime bh = bankHoliday.Date;
            if (firstDay <= bh && bh <= lastDay)
                --businessDays;
        }

        return businessDays;
    }

Редакция Слаумы, август 2011

Отличный ответ! Есть небольшая ошибка, хотя. Я пользуюсь свободой, чтобы редактировать этот ответ, так как ответчик отсутствует с 2009 года.

Код выше предполагает, что DayOfWeek.Sunday имеет значение 7 что не так. Значение на самом деле 0, Это приводит к неправильному расчету, если, например, firstDay а также lastDay оба в одно и то же воскресенье. Метод возвращает 1 в этом случае, но это должно быть 0,

Самое простое исправление для этой ошибки: заменить в коде над строками где firstDayOfWeek а также lastDayOfWeek объявлены следующим:

int firstDayOfWeek = firstDay.DayOfWeek == DayOfWeek.Sunday 
    ? 7 : (int)firstDay.DayOfWeek;
int lastDayOfWeek = lastDay.DayOfWeek == DayOfWeek.Sunday
    ? 7 : (int)lastDay.DayOfWeek;

Теперь результат:

  • С пятницы по пятницу -> 1
  • С субботы по субботу -> 0
  • С воскресенья по воскресенье -> 0
  • С пятницы по субботу -> 1
  • С пятницы по воскресенье -> 1
  • С пятницы по понедельник -> 2
  • С субботы по понедельник -> 1
  • С воскресенья по понедельник -> 1
  • С понедельника по понедельник -> 1

Хорошо. Я думаю, что пришло время опубликовать правильный ответ:

public static double GetBusinessDays(DateTime startD, DateTime endD)
{
    double calcBusinessDays =
        1 + ((endD - startD).TotalDays * 5 -
        (startD.DayOfWeek - endD.DayOfWeek) * 2) / 7;

    if (endD.DayOfWeek == DayOfWeek.Saturday) calcBusinessDays--;
    if (startD.DayOfWeek == DayOfWeek.Sunday) calcBusinessDays--;

    return calcBusinessDays;
}

Первоначальный источник:

http://alecpojidaev.wordpress.com/2009/10/29/work-days-calculation-with-c/

PS Решения, опубликованные выше, заставляют меня злиться по некоторым причинам.

Я знаю, что этот вопрос уже решен, но я подумал, что смогу дать более простой ответ, который может помочь другим посетителям в будущем.

Вот мой взгляд на это:

public int GetWorkingDays(DateTime from, DateTime to)
{
    var dayDifference = (int)to.Subtract(from).TotalDays;
    return Enumerable
        .Range(1, dayDifference)
        .Select(x => from.AddDays(x))
        .Count(x => x.DayOfWeek != DayOfWeek.Saturday && x.DayOfWeek != DayOfWeek.Sunday);
}

Это было мое первоначальное представление:

public int GetWorkingDays(DateTime from, DateTime to)
{
    var totalDays = 0;
    for (var date = from; date < to; date = date.AddDays(1))
    {
        if (date.DayOfWeek != DayOfWeek.Saturday
            && date.DayOfWeek != DayOfWeek.Sunday)
            totalDays++;
    }

    return totalDays;
}

Определите метод расширения для DateTime следующим образом:

public static class DateTimeExtensions
{
    public static bool IsWorkingDay(this DateTime date)
    {
        return date.DayOfWeek != DayOfWeek.Saturday
            && date.DayOfWeek != DayOfWeek.Sunday;
    }
}

Затем используйте предложение Where для фильтрации более широкого списка дат:

var allDates = GetDates(); // method which returns a list of dates

// filter dates by working day's  
var countOfWorkDays = allDates
     .Where(day => day.IsWorkingDay())
     .Count() ;

Я также использовал следующий код для учета банковских праздников:

public class WorkingDays
{
    public List<DateTime> GetHolidays()
    {
        var client = new WebClient();
        var json = client.DownloadString("https://www.gov.uk/bank-holidays.json");
        var js = new JavaScriptSerializer();
        var holidays = js.Deserialize <Dictionary<string, Holidays>>(json);
        return holidays["england-and-wales"].events.Select(d => d.date).ToList();
    }

    public int GetWorkingDays(DateTime from, DateTime to)
    {
        var totalDays = 0;
        var holidays = GetHolidays();
        for (var date = from.AddDays(1); date <= to; date = date.AddDays(1))
        {
            if (date.DayOfWeek != DayOfWeek.Saturday
                && date.DayOfWeek != DayOfWeek.Sunday
                && !holidays.Contains(date))
                totalDays++;
        }

        return totalDays;
    }
}

public class Holidays
{
    public string division { get; set; }
    public List<Event> events { get; set; }
}

public class Event
{
    public DateTime date { get; set; }
    public string notes { get; set; }
    public string title { get; set; }
}

И юнит-тесты:

[TestClass]
public class WorkingDays
{
    [TestMethod]
    public void SameDayIsZero()
    {
        var service = new WorkingDays();

        var from = new DateTime(2013, 8, 12);

        Assert.AreEqual(0, service.GetWorkingDays(from, from));

    }

    [TestMethod]
    public void CalculateDaysInWorkingWeek()
    {
        var service = new WorkingDays();

        var from = new DateTime(2013, 8, 12);
        var to = new DateTime(2013, 8, 16);

        Assert.AreEqual(4, service.GetWorkingDays(from, to), "Mon - Fri = 4");

        Assert.AreEqual(1, service.GetWorkingDays(from, new DateTime(2013, 8, 13)), "Mon - Tues = 1");
    }

    [TestMethod]
    public void NotIncludeWeekends()
    {
        var service = new WorkingDays();

        var from = new DateTime(2013, 8, 9);
        var to = new DateTime(2013, 8, 16);

        Assert.AreEqual(5, service.GetWorkingDays(from, to), "Fri - Fri = 5");

        Assert.AreEqual(2, service.GetWorkingDays(from, new DateTime(2013, 8, 13)), "Fri - Tues = 2");
        Assert.AreEqual(1, service.GetWorkingDays(from, new DateTime(2013, 8, 12)), "Fri - Mon = 1");
    }

    [TestMethod]
    public void AccountForHolidays()
    {
        var service = new WorkingDays();

        var from = new DateTime(2013, 8, 23);

        Assert.AreEqual(0, service.GetWorkingDays(from, new DateTime(2013, 8, 26)), "Fri - Mon = 0");

        Assert.AreEqual(1, service.GetWorkingDays(from, new DateTime(2013, 8, 27)), "Fri - Tues = 1");
    }
}

Я много искал, легкий для усвоения, алгоритм для вычисления рабочих дней между двумя датами, а также для исключения национальных праздников, и, наконец, я решил использовать этот подход:

public static int NumberOfWorkingDaysBetween2Dates(DateTime start,DateTime due,IEnumerable<DateTime> holidays)
        {
            var dic = new Dictionary<DateTime, DayOfWeek>();
            var totalDays = (due - start).Days;
            for (int i = 0; i < totalDays + 1; i++)
            {
                if (!holidays.Any(x => x == start.AddDays(i)))
                    dic.Add(start.AddDays(i), start.AddDays(i).DayOfWeek);
            }

            return dic.Where(x => x.Value != DayOfWeek.Saturday && x.Value != DayOfWeek.Sunday).Count();
        } 

В основном я хотел пойти с каждой датой и оценить мои условия:

  1. Не суббота
  2. Не воскресенье
  3. Это не национальный праздник

но также я хотел избежать повторения дат.

Запустив и измерив время, необходимое для оценки 1 полного года, я получаю следующий результат:

static void Main(string[] args)
        {
            var start = new DateTime(2017, 1, 1);
            var due = new DateTime(2017, 12, 31);

            var sw = Stopwatch.StartNew();
            var days = NumberOfWorkingDaysBetween2Dates(start, due,NationalHolidays());
            sw.Stop();

            Console.WriteLine($"Total working days = {days} --- time: {sw.Elapsed}");
            Console.ReadLine();

            // result is:
           // Total working days = 249-- - time: 00:00:00.0269087
        }

Это решение позволяет избежать итераций, работает как для разниц по дням +ve, так и для -ve, и включает набор модульных тестов для регрессии против более медленного метода подсчета дней недели. Я также включил краткий метод добавления будних дней, который также работает не итеративно.

Модульные тесты охватывают несколько тысяч комбинаций дат, чтобы исчерпывающе протестировать все комбинации начала / конца дня недели с маленькими и большими диапазонами дат.

Важно: мы предполагаем, что мы считаем дни, исключая дату начала и включая дату окончания. Это важно при подсчете дней недели, поскольку конкретные дни начала / окончания, которые вы включаете / исключаете, влияют на результат. Это также гарантирует, что разница между двумя равными днями всегда равна нулю и что мы включаем только полные рабочие дни, поскольку обычно вы хотите, чтобы ответ был верным в любое время текущей текущей даты (часто сегодня) и включал полную конечную дату (например, срок выполнения).

ПРИМЕЧАНИЕ. Этот код нуждается в дополнительной корректировке для выходных, но в соответствии с вышеприведенным предположением этот код должен исключать праздничные дни в дату начала.

Добавить будни:

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);
}

Вычислить разницу в будние дни:

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];
}

Я обнаружил, что большинство других решений по переполнению стека были либо медленными (итеративными), либо слишком сложными, а многие были просто неверны. Мораль этой истории... Не верь этому, если ты не проверил его всесторонне!!

Модульные тесты, основанные на комбинаторном тестировании NUnit и расширении mustBe NUnit.

[TestFixture]
public class DateTimeExtensionsTests
{
    /// <summary>
    /// Exclude start date, Include end date
    /// </summary>
    /// <param name="dtStart"></param>
    /// <param name="dtEnd"></param>
    /// <returns></returns>
    private IEnumerable<DateTime> GetDateRange(DateTime dtStart, DateTime dtEnd)
    {
        Console.WriteLine(@"dtStart={0:yy-MMM-dd ddd}, dtEnd={1:yy-MMM-dd ddd}", 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 ddd}", dt);
                yield return dt;
            }
        }
        else
        {
            for (DateTime dt = dtStart.AddDays(-1); dt >= dtEnd; dt = dt.AddDays(-1))
            {
                Console.WriteLine(@"dt={0:yy-MMM-dd ddd}", 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);
    }
}

Ну, это было избито до смерти.:) Однако я все еще собираюсь дать другой ответ, потому что мне нужно что-то немного другое. Это решение отличается тем, что оно возвращает Business TimeSpan между началом и концом, и вы можете установить рабочее время дня и добавить праздничные дни. Таким образом, вы можете использовать его для расчета, если это произойдет в течение дня, по дням, в выходные и даже в праздничные дни. И вы можете получить только рабочие дни или нет, просто получив то, что вам нужно, от возвращенного объекта TimeSpan. И по тому, как он использует списки дней, вы можете видеть, как легко было бы добавить список нерабочих дней, если бы это не были обычные суббота и воскресенье. И я проверил в течение года, и, кажется, очень быстро.

Я просто надеюсь, что вставка кода является точной. Но я знаю, что это работает.

public static TimeSpan GetBusinessTimespanBetween(
    DateTime start, DateTime end,
    TimeSpan workdayStartTime, TimeSpan workdayEndTime,
    List<DateTime> 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<DateTime>();

    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>() { 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;
    }
}

И вот тестовый код: обратите внимание, что вам просто нужно поместить эту функцию в класс DateHelper, чтобы тестовый код работал.

[TestMethod]
public void TestGetBusinessTimespanBetween()
{
    var workdayStart = new TimeSpan(8, 0, 0);
    var workdayEnd = new TimeSpan(17, 0, 0);

    var holidays = new List<DateTime>()
    {
        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);
    }
}

Вот быстрый пример кода. Это метод класса, поэтому будет работать только внутри вашего класса. Если вы хотите, чтобы это было staticизмените подпись на private static (или же public static).

    private IEnumerable<DateTime> GetWorkingDays(DateTime sd, DateTime ed)
    {
        for (var d = sd; d <= ed; d.AddDays(1))
            if (d.DayOfWeek != DayOfWeek.Saturday && d.DayOfWeek != DayOfWeek.Sunday)
                yield return d;
    }

Этот метод создает переменную цикла d, инициализирует его в день начала, sd, затем увеличивается на один день за каждую итерацию (d.AddDays(1)).

Возвращает нужные значения, используя yield, который создает iterator, Крутая вещь об итераторах состоит в том, что они не содержат все значения IEnumerable в памяти, только вызывая каждый последовательно. Это означает, что вы можете вызывать этот метод с незапамятных времен до настоящего времени, не беспокоясь о нехватке памяти.

Работает и без петель

Этот метод не использует никаких циклов и на самом деле довольно прост. Это расширяет диапазон дат до полных недель, так как мы знаем, что у каждой недели есть 5 рабочих дней. Затем он использует таблицу поиска, чтобы найти количество рабочих дней, которые нужно вычесть из начала и конца, чтобы получить правильный результат. Я расширил расчет, чтобы показать, что происходит, но все это может быть сведено к одной строке, если это необходимо.

В любом случае, это работает для меня, и поэтому я решил опубликовать это здесь на случай, если это поможет другим. Удачного кодирования.

расчет

  • t: общее количество дней между датами (1, если min = max)
  • a + b: дополнительные дни, необходимые для увеличения общего количества недель до полных
  • k: 1.4 - количество дней в неделю, т. е. (t / 7) * 5
  • c: количество дней недели, которые нужно вычесть из общего числа
  • m: таблица поиска, используемая для поиска значения "c" для каждого дня недели

культура

Кодекс предполагает рабочую неделю с понедельника по пятницу. Для других культур, например, с воскресенья по четверг, вам необходимо сместить даты до расчета.

метод

public int Weekdays(DateTime min, DateTime max) 
{       
        if (min.Date > max.Date) throw new Exception("Invalid date span");
        var t = (max.AddDays(1).Date - min.Date).TotalDays;
        var a = (int) min.DayOfWeek;
        var b = 6 - (int) max.DayOfWeek;
        var k = 1.4;
        var m = new int[]{0, 0, 1, 2, 3, 4, 5}; 
        var c = m[a] + m[b];
        return (int)((t + a + b) / k) - c;
}

Вот некоторый код для этой цели со шведскими праздниками, но вы можете адаптировать, какие праздники считать. Обратите внимание, что я добавил ограничение, которое вы, возможно, захотите удалить, но это было для веб-системы, и я не хотел, чтобы кто-то вводил какую-то огромную дату, чтобы затормозить процесс

  public static int GetWorkdays(DateTime from ,DateTime to)
    {
        int limit = 9999;
        int counter = 0;
        DateTime current = from;
        int result = 0;

        if (from > to)
        {
            DateTime temp = from;
            from = to;
            to = temp;
        }

        if (from >= to)
        {
            return 0;
        }


        while (current <= to && counter < limit)
        {
            if (IsSwedishWorkday(current))
            {
                result++;
            }
            current = current.AddDays(1);
            counter++;

        }
        return result;
    }


    public static bool IsSwedishWorkday(DateTime date)
    {
        return (!IsSwedishHoliday(date) && date.DayOfWeek != DayOfWeek.Saturday && date.DayOfWeek != DayOfWeek.Sunday);
    }

    public static bool IsSwedishHoliday(DateTime date)
    {
        return (
        IsSameDay(GetEpiphanyDay(date.Year), date) ||
        IsSameDay(GetMayDay(date.Year), date) ||
        IsSameDay(GetSwedishNationalDay(date.Year), date) ||
        IsSameDay(GetChristmasDay(date.Year), date) ||
        IsSameDay(GetBoxingDay(date.Year), date) ||
        IsSameDay(GetGoodFriday(date.Year), date) ||
        IsSameDay(GetAscensionDay(date.Year), date) ||
        IsSameDay(GetAllSaintsDay(date.Year), date) ||
        IsSameDay(GetMidsummersDay(date.Year), date) ||
        IsSameDay(GetPentecostDay(date.Year), date) ||
        IsSameDay(GetEasterMonday(date.Year), date) ||
        IsSameDay(GetNewYearsDay(date.Year), date) ||
        IsSameDay(GetEasterDay(date.Year), date)
        );
    }

    // Trettondagen
    public static DateTime GetEpiphanyDay(int year)
    {
        return new DateTime(year, 1, 6);
    }

    // Första maj
    public static DateTime GetMayDay(int year)
    {
        return new DateTime(year,5,1);
    }

    // Juldagen
    public static DateTime GetSwedishNationalDay(int year)
    {
        return new DateTime(year, 6, 6);
    }


    // Juldagen
    public static DateTime GetNewYearsDay(int year)
    {
        return new DateTime(year,1,1);
    }

    // Juldagen
    public static DateTime GetChristmasDay(int year)
    {
        return new DateTime(year,12,25);
    }

    // Annandag jul
    public static DateTime GetBoxingDay(int year)
    {
        return new DateTime(year, 12, 26);
    }


    // Långfredagen
    public static DateTime GetGoodFriday(int year)
    {
        return GetEasterDay(year).AddDays(-3);
    }

    // Kristi himmelsfärdsdag
    public static DateTime GetAscensionDay(int year)
    {
        return GetEasterDay(year).AddDays(5*7+4);
    }

    // Midsommar
    public static DateTime GetAllSaintsDay(int year)
    {
        DateTime result = new DateTime(year,10,31);
        while (result.DayOfWeek != DayOfWeek.Saturday)
        {
            result = result.AddDays(1);
        }
        return result;
    }

    // Midsommar
    public static DateTime GetMidsummersDay(int year)
    {
        DateTime result = new DateTime(year, 6, 20);
        while (result.DayOfWeek != DayOfWeek.Saturday)
        {
            result = result.AddDays(1);
        }
        return result;
    }

    // Pingstdagen
    public static DateTime GetPentecostDay(int year)
    {
        return GetEasterDay(year).AddDays(7 * 7);
    }

    // Annandag påsk
    public static DateTime GetEasterMonday(int year)
    {
        return GetEasterDay(year).AddDays(1);
    }
    public static DateTime GetEasterDay(int y)
    {
        double c;
        double n;
        double k;
        double i;
        double j;
        double l;
        double m;
        double d;
        c = System.Math.Floor(y / 100.0);
        n = y - 19 * System.Math.Floor(y / 19.0);
        k = System.Math.Floor((c - 17) / 25.0);
        i = c - System.Math.Floor(c / 4) - System.Math.Floor((c - k) / 3) + 19 * n + 15;
        i = i - 30 * System.Math.Floor(i / 30);
        i = i - System.Math.Floor(i / 28) * (1 - System.Math.Floor(i / 28) * System.Math.Floor(29 / (i + 1)) * System.Math.Floor((21 - n) / 11));
        j = y + System.Math.Floor(y / 4.0) + i + 2 - c + System.Math.Floor(c / 4);
        j = j - 7 * System.Math.Floor(j / 7);
        l = i - j;
        m = 3 + System.Math.Floor((l + 40) / 44);// month
        d = l + 28 - 31 * System.Math.Floor(m / 4);// day

        double days = ((m == 3) ? d : d + 31);

        DateTime result = new DateTime(y, 3, 1).AddDays(days-1);

        return result;
    }

Этот метод возвращает количество рабочих дней между двумя датами:

Здесь я использую перечисление DayOfWeek для проверки выходных.

              private static int BusinessDaysLeft(DateTime first, DateTime last)
        {
            var count = 0;

            while (first.Date != last.Date)
            {
                if(first.DayOfWeek != DayOfWeek.Saturday && first.DayOfWeek != DayOfWeek.Sunday)
                    count++;
                
                first = first.AddDays(1);
            }

            return count;
        }

Вот вспомогательная функция, которую я написал для этой задачи.
он также возвращает количество выходных через out параметр.
если вы хотите, вы можете настроить дни выходных во время выполнения для стран, которые используют разные выходные дни или включить выходные дни через weekendDays[] необязательный параметр:

public static int GetNetworkDays(DateTime startDate, DateTime endDate,out int totalWeekenDays, DayOfWeek[] weekendDays = null)
{
    if (startDate >= endDate)
    {
        throw new Exception("start date can not be greater then or equel to end date");
    }

    DayOfWeek[] weekends = new DayOfWeek[] { DayOfWeek.Sunday, DayOfWeek.Saturday };
    if (weekendDays != null)
    {
        weekends = weekendDays;
    }

    var totaldays = (endDate - startDate).TotalDays + 1; // add one to include the first day to
    var counter = 0;
    var workdaysCounter = 0;
    var weekendsCounter = 0;

    for (int i = 0; i < totaldays; i++)
    {

        if (weekends.Contains(startDate.AddDays(counter).DayOfWeek))
        {
            weekendsCounter++;
        }
        else
        {
            workdaysCounter++;
        }

        counter++;
    }

    totalWeekenDays = weekendsCounter;
    return workdaysCounter;
}

Вот функция, которую мы можем использовать для расчета рабочих дней между двумя датами. Я не использую список праздников, так как он может варьироваться в зависимости от страны / региона.

Если мы хотим использовать его в любом случае, мы можем принять третий аргумент в качестве списка выходных, и прежде чем увеличивать количество, мы должны проверить, что список не содержит d

public static int GetBussinessDaysBetweenTwoDates(DateTime StartDate,   DateTime EndDate)
    {
        if (StartDate > EndDate)
            return -1;

        int bd = 0;

        for (DateTime d = StartDate; d < EndDate; d = d.AddDays(1))
        {
            if (d.DayOfWeek != DayOfWeek.Saturday && d.DayOfWeek != DayOfWeek.Sunday)
                bd++;
        }

        return bd;
    }

Я думаю, что ни один из приведенных выше ответов на самом деле не является правильным. Ни один из них не решает все особые случаи, например, когда даты начинаются и заканчиваются в середине выходных, когда дата начинается в пятницу и заканчивается в следующий понедельник и т. Д. Кроме того, они все округляют вычисления до целого дней, поэтому, если, например, начальная дата находится в середине субботы, она вычтет целый рабочий день из рабочих дней, давая неверные результаты...

Во всяком случае, вот мое решение, которое является достаточно эффективным и простым и работает для всех случаев. Хитрость заключается в том, чтобы просто найти предыдущий понедельник для дат начала и окончания, а затем сделать небольшую компенсацию, когда начало и конец происходят в выходные дни:

public double WorkDays(DateTime startDate, DateTime endDate){
        double weekendDays;

        double days = endDate.Subtract(startDate).TotalDays;

        if(days<0) return 0;

        DateTime startMonday = startDate.AddDays(DayOfWeek.Monday - startDate.DayOfWeek).Date;
        DateTime endMonday = endDate.AddDays(DayOfWeek.Monday - endDate.DayOfWeek).Date;

        weekendDays = ((endMonday.Subtract(startMonday).TotalDays) / 7) * 2;

        // compute fractionary part of weekend days
        double diffStart = startDate.Subtract(startMonday).TotalDays - 5;
        double diffEnd = endDate.Subtract(endMonday).TotalDays - 5;

        // compensate weekenddays
        if(diffStart>0) weekendDays -= diffStart;
        if(diffEnd>0) weekendDays += diffEnd;

        return days - weekendDays;
    }

Используя библиотеку marinjw и решение, предоставленное Алеком Пожидаевым, которое я создал ниже, это позволит пропустить праздничные дни (необходимо указать страну):

Обратите внимание, что если вы используете фактическую дату и время и у вас есть часы, вам нужно добавить.Dateи лучше переформатироватьFromDateиToDateкакFromDate = FromDate.DateиToDate = ToDate.Date

      public static int GetProcessingTime(this DateTime? FromDate, DateTime? ToDate)
{
    if (FromDate == null) return 0;
    if (ToDate == null) return 0;

    List<DateTime> Holidays = new List<DateTime>();

    int startyear = FromDate.Value.Year;
    int endyear = ToDate.Value.Year;

    //loop years to extract all holidays
    for(int i = startyear; i <= endyear; i++)
    {
        IList<DateTime> Temp = new PolandPublicHoliday().PublicHolidays(i);
        Holidays.AddRange(Temp.ToList());
    }

    //exclude holidays outside of range
    Holidays = Holidays.Where(x => x >= FromDate && x <= ToDate).ToList();

    //exclude holidays that are set to be on sunday/saturday
    Holidays = Holidays.Where(x => x.DayOfWeek != DayOfWeek.Sunday && x.DayOfWeek != DayOfWeek.Saturday).ToList();

    //calculate date difference without sundays and saturdays Value need to be added as I am using nullable DateTime
    double calcBusinessDays = 1 + ((FromDate.Value - ToDate.Value).TotalDays * 5 - (FromDate.Value.DayOfWeek - ToDate.Value.DayOfWeek) * 2) / 7;

    if (FromDate.Value.DayOfWeek == DayOfWeek.Saturday) calcBusinessDays--;
    if (ToDate.Value.DayOfWeek == DayOfWeek.Sunday) calcBusinessDays--;

    //remove left holidays
    if (Holidays!=null)
        calcBusinessDays -= Holidays.Count;

    return (int)calcBusinessDays;
}
using System;

namespace ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {
            DateTime start = new DateTime(2014, 1, 1);
            DateTime stop = new DateTime(2014, 12, 31);

            int totalWorkingDays = GetNumberOfWorkingDays(start, stop);

            Console.WriteLine("There are {0} working days.", totalWorkingDays);
        }

        private static int GetNumberOfWorkingDays(DateTime start, DateTime stop)
        {
            TimeSpan interval = stop - start;

            int totalWeek = interval.Days / 7;
            int totalWorkingDays = 5 * totalWeek;

            int remainingDays = interval.Days % 7;


            for (int i = 0; i <= remainingDays; i++)
            {
                DayOfWeek test = (DayOfWeek)(((int)start.DayOfWeek + i) % 7);
                if (test >= DayOfWeek.Monday && test <= DayOfWeek.Friday)
                    totalWorkingDays++;
            }

            return totalWorkingDays;
        }
    }
}

На основании комментария, помеченного как ответ и рекомендуемого исправления, а также -> Эта версия хочет преобразовать дни в рабочие часы... Рассматривает также часы того же дня.

 /// <summary>
    /// Calculates number of business days, taking into account:
    ///  - weekends (Saturdays and Sundays)
    ///  - bank holidays in the middle of the week
    /// </summary>
    /// <param name="firstDay">First day in the time interval</param>
    /// <param name="lastDay">Last day in the time interval</param>
    /// <param name="bankHolidays">List of bank holidays excluding weekends</param>
    /// <returns>Number of business hours during the 'span'</returns>
    public static int BusinessHoursUntil(DateTime firstDay, DateTime lastDay, params DateTime[] bankHolidays)
    {
        var original_firstDay = firstDay;
        var original_lastDay = lastDay;
        firstDay = firstDay.Date;
        lastDay = lastDay.Date;
        if (firstDay > lastDay)
            return -1; //// throw new ArgumentException("Incorrect last day " + lastDay);

        TimeSpan span = lastDay - firstDay;
        int businessDays = span.Days + 1;
        int fullWeekCount = businessDays / 7;
        // find out if there are weekends during the time exceedng the full weeks
        if (businessDays > fullWeekCount * 7)
        {
            // we are here to find out if there is a 1-day or 2-days weekend
            // in the time interval remaining after subtracting the complete weeks
            int firstDayOfWeek = firstDay.DayOfWeek == DayOfWeek.Sunday ? 7 : (int)firstDay.DayOfWeek;
            int lastDayOfWeek = lastDay.DayOfWeek == DayOfWeek.Sunday ? 7 : (int)lastDay.DayOfWeek;

            if (lastDayOfWeek < firstDayOfWeek)
                lastDayOfWeek += 7;
            if (firstDayOfWeek <= 6)
            {
                if (lastDayOfWeek >= 7)// Both Saturday and Sunday are in the remaining time interval
                    businessDays -= 2;
                else if (lastDayOfWeek >= 6)// Only Saturday is in the remaining time interval
                    businessDays -= 1;
            }
            else if (firstDayOfWeek <= 7 && lastDayOfWeek >= 7)// Only Sunday is in the remaining time interval
                businessDays -= 1;
        }

        // subtract the weekends during the full weeks in the interval
        businessDays -= fullWeekCount + fullWeekCount;

        if (bankHolidays != null && bankHolidays.Any())
        {
            // subtract the number of bank holidays during the time interval
            foreach (DateTime bankHoliday in bankHolidays)
            {
                DateTime bh = bankHoliday.Date;
                if (firstDay <= bh && bh <= lastDay)
                    --businessDays;
            }
        }

        int total_business_hours = 0;
        if (firstDay.Date == lastDay.Date)
        {//If on the same day, go granular with Hours from the Orginial_*Day values
            total_business_hours = (int)(original_lastDay - original_firstDay).TotalHours;
        }
        else
        {//Convert Business-Days to TotalHours
            total_business_hours = (int)(firstDay.AddDays(businessDays).AddHours(firstDay.Hour) - firstDay).TotalHours;
        }
        return total_business_hours;
    }

public static int CalculateBusinessDaysInRange(this DateTime startDate, DateTime endDate, params DateTime[] holidayDates)
{
    endDate = endDate.Date;
    if(startDate > endDate)
        throw new ArgumentException("The end date can not be before the start date!", nameof(endDate));
    int accumulator = 0;
    DateTime itterator = startDate.Date;
    do 
    {
        if(itterator.DayOfWeek != DayOfWeek.Saturday && itterator.DayOfWeek != DayOfWeek.Sunday && !holidayDates.Any(hol => hol.Date == itterator))
        { accumulator++; }
    } 
    while((itterator = itterator.AddDays(1)).Date <= endDate);
    return accumulator
}

Я публикую это только потому, что, несмотря на все полученные отличные ответы, ни одна математика не имела для меня смысла. Это определенно метод KISS, который должен работать и быть достаточно поддерживаемым. Конечно, если вы рассчитываете диапазоны, превышающие 2-3 месяца, это не будет наиболее эффективным способом. Мы просто определяем, суббота это или воскресенье, или это дата праздника. Если это не так, мы добавляем рабочий день. Если да, то все в порядке.

Я уверен, что это можно было бы еще больше упростить с помощью LINQ, но этот способ намного проще для понимания.

Вот еще один пример: вы также можете выбрать даты выходных,

        public int GetBuisnessDays(DateTime StartDate, DateTime EndDate)
    {
        int counter = 0;

        if (StartDate.Date == EndDate.Date)
        {
            if (StartDate.DayOfWeek != DayOfWeek.Saturday && StartDate.DayOfWeek != DayOfWeek.Friday)
                return 1;
            return 0;
        }

        while (StartDate <= EndDate)
        {
            if (StartDate.DayOfWeek != DayOfWeek.Saturday && StartDate.DayOfWeek != DayOfWeek.Friday)
                ++counter;
            StartDate = StartDate.AddDays(1);
        }

        return counter;
    }

У меня возникли проблемы с поиском надежной TSQL-версии этого кода. Ниже приведено преобразование кода C# с добавлением таблицы выходных, которая должна использоваться для предварительного расчета выходных.

CREATE TABLE dbo.Holiday
(
    HolidayDt       DATE NOT NULL,
    Name            NVARCHAR(50) NOT NULL,
    IsWeekday       BIT NOT NULL,
    CONSTRAINT PK_Holiday PRIMARY KEY (HolidayDt)
)
GO
CREATE INDEX IDX_Holiday ON Holiday (HolidayDt, IsWeekday)

GO

CREATE function dbo.GetBusinessDays
(
     @FirstDay  datetime,
     @LastDay   datetime
) 
RETURNS INT
 AS
BEGIN
    DECLARE @BusinessDays INT, @FullWeekCount INT 
    SELECT  @FirstDay = CONVERT(DATETIME,CONVERT(DATE,@FirstDay))
        ,   @LastDay = CONVERT(DATETIME,CONVERT(DATE,@LastDay))

    IF @FirstDay > @LastDay
        RETURN NULL;

    SELECT @BusinessDays = DATEDIFF(DAY, @FirstDay, @LastDay) + 1 
    SELECT @FullWeekCount = @BusinessDays / 7;

    -- find out if there are weekends during the time exceedng the full weeks
    IF @BusinessDays > (@FullWeekCount * 7)
    BEGIN
    -- we are here to find out if there is a 1-day or 2-days weekend
    -- in the time interval remaining after subtracting the complete weeks
        DECLARE @firstDayOfWeek INT, @lastDayOfWeek INT;
        SELECT @firstDayOfWeek = DATEPART(DW, @FirstDay), @lastDayOfWeek = DATEPART(DW, @LastDay);

        IF @lastDayOfWeek < @firstDayOfWeek
                SELECT @lastDayOfWeek = @lastDayOfWeek + 7;

        IF @firstDayOfWeek <= 6 
            BEGIN
                IF (@lastDayOfWeek >= 7) --Both Saturday and Sunday are in the remaining time interval
                    BEGIN 
                        SELECT @BusinessDays = @BusinessDays - 2
                    END
                ELSE IF @lastDayOfWeek>=6 --Only Saturday is in the remaining time interval
                    BEGIN
                        SELECT @BusinessDays = @BusinessDays - 1
                    END

            END
        ELSE IF @firstDayOfWeek <= 7 AND @lastDayOfWeek >=7 -- Only Sunday is in the remaining time interval
        BEGIN 
            SELECT @BusinessDays = @BusinessDays - 1
        END
    END

    -- subtract the weekends during the full weeks in the interval
    DECLARE @Holidays INT;
    SELECT  @Holidays = COUNT(*) 
    FROM    Holiday 
    WHERE   HolidayDt BETWEEN @FirstDay AND @LastDay 
    AND     IsWeekday = CAST(1 AS BIT)

    SELECT @BusinessDays = @BusinessDays - (@FullWeekCount + @FullWeekCount) -- - @Holidays

    RETURN @BusinessDays
END

Вам просто нужно перебирать каждый день во временном диапазоне и вычитать день из счетчика, если это суббота или воскресенье.

    private float SubtractWeekend(DateTime start, DateTime end) {
        float totaldays = (end.Date - start.Date).Days;
        var iterationVal = totalDays;
        for (int i = 0; i <= iterationVal; i++) {
            int dayVal = (int)start.Date.AddDays(i).DayOfWeek;
            if(dayVal == 6 || dayVal == 0) {
                // saturday or sunday
                totalDays--;
            }
        }
        return totalDays;
    }

Многие ответы хороши, но они не принимают во внимание время начала / окончания. Вот мои улучшенные решения (на C#), основанные на этих ответах, но с учетом времени начала и окончания. Например:

  • С понедельника 8/2/2021 по среду 8/4/2021 составляет 2 дня - в этом случае 8/4/2021 исключается, потому что время не указано, а в .Net оно автоматически назначается 12:00 AM.

  • С понедельника, 02.08.2021, 12.00 до среды, 04.08.2021, 12.00 - это 2 дня.

  • Пятница, 06.08.2021, 12:00 - вторник, 10.08.2021, 18:00 - это 2,25 дня.

            public static double GetWorkingDays(DateTime startDate, DateTime endDate)
    {
      // Get next full day from start date and last full day of end date
      var nextFullStartDate = startDate.AddDays(1).Date;
      var lastFullEndDate = endDate.Date;
    
      // Calculate total days based on start and end's day of week
      var dayOfWeekFactor = (nextFullStartDate.DayOfWeek - lastFullEndDate.DayOfWeek) * 2;
      if (lastFullEndDate.DayOfWeek == DayOfWeek.Sunday)
      {
          // In .net, day of week for Sunday is 0, we need to change it to 7
          dayOfWeekFactor = ((int)nextFullStartDate.DayOfWeek - 7) * 2;
      }
    
      // Calculate working days (ie: Monday, Tuesday, Wednesday, Thursday and Friday)
      var workingDayCount = ((lastFullEndDate - nextFullStartDate).TotalDays * 5 - dayOfWeekFactor) / 7;
    
      // Subtract working days if full start date or last full end date is Sunday
      // Saturday is not included here because if startDate is Saturday the formula above would have excluded Saturday. If the endDate is Saturday, it would have been excluded too because the time would be Saturday 12:00 AM
      if (nextFullStartDate.DayOfWeek == DayOfWeek.Sunday || lastFullEndDate.DayOfWeek == DayOfWeek.Sunday)
      {
           workingDayCount--;
      }
    
      // Count the real start/end date
      if (startDate.DayOfWeek != DayOfWeek.Saturday &&
          startDate.DayOfWeek != DayOfWeek.Sunday)
      {
          var startDateFraction = (nextFullStartDate - startDate).TotalHours / 24;
          workingDayCount += startDateFraction;
      }
    
      if (endDate.DayOfWeek != DayOfWeek.Saturday &&
        endDate.DayOfWeek != DayOfWeek.Sunday)
      {
          var endDateFraction = (endDate - lastFullEndDate).TotalHours / 24;
          workingDayCount += endDateFraction;
      }
    
      return workingDayCount;
    }
    

Попробуйте это, без итерации через все дни! До сих пор работало хорошо:

        private int CalculateDateDifference(DateTime startdate, DateTime endDate)
    {
        int businessDays = (endDate - startdate).Days;
        var weekendCount = 0;

        if (businessDays > 6)
        {
            weekendCount += 2;
            startdate = startdate.AddDays(7);
            while (startdate <= endDate)
            {
                startdate = startdate.AddDays(7);
                if (startdate <= endDate || ( (int)endDate.DayOfWeek < (int)startdate.DayOfWeek && endDate.DayOfWeek!=DayOfWeek.Sunday))
                    weekendCount += 2;
            }
           
        }
        else if ((int)endDate.DayOfWeek < (int)startdate.DayOfWeek)
        {
            weekendCount += 2;
        }

        //Exclude weekends
        businessDays = businessDays - weekendCount;

        //Check if Last day was on Weekend
        if (endDate.DayOfWeek == DayOfWeek.Saturday) businessDays -= 1;
        if (endDate.DayOfWeek == DayOfWeek.Sunday) businessDays -= 2;


        return businessDays;

    }

Примечание. В этом решении предполагается, что startDate приходится на день недели. Также можно настроить для обработки дня начала выходных.

      public static int CalculateWorkDays(DateTime startDate, DateTime endDate)
    {
        //Step0: remove hours, minutes, seconds of startDate and endDate.
        startDate = new DateTime(startDate.Year, startDate.Month, [![enter image description here][1]][1]startDate.Day);
        endDate = new DateTime(endDate.Year, endDate.Month, endDate.Day);
        //Step1: fill days to whole weeks. ps.Sunday is the first day, and its DayOfWeek is 0.
        int wholeWeekDays = (endDate - startDate).Days + ((int)startDate.DayOfWeek + 1) + (6 - (int)endDate.DayOfWeek);
        //Step2: calculate work days of the whole weeks
        int filledWorkDays = wholeWeekDays / 7 * 5;
        //Step3: minus prefilled work days
        int filledWorkDaysForStartDate = startDate.DayOfWeek == DayOfWeek.Sunday ? 0 : (int)startDate.DayOfWeek - 1; //Sunday should be excluded, because it's not a work day
        int filledWorkDaysForEndDate = endDate.DayOfWeek == DayOfWeek.Sunday ? 5 : 6 - (int)endDate.DayOfWeek;
        int workDays = filledWorkDays - filledWorkDaysForStartDate - filledWorkDaysForEndDate;
        return workDays;
    }

Дата окончания: 9 марта 2023 г.
Тестовые примеры прикреплены ниже:

    int BusinessDayDifference(DateTime Date1, DateTime Date2)
    {
        int Sign = 1;
        if (Date2 > Date1)
        {
            Sign = -1;
            DateTime TempDate = Date1;
            Date1 = Date2;
            Date2 = TempDate;
        }
        int BusDayDiff = (int)(Date1.Date - Date2.Date).TotalDays;
        if (Date1.DayOfWeek == DayOfWeek.Saturday)
            BusDayDiff -= 1;
        if (Date2.DayOfWeek == DayOfWeek.Sunday)
            BusDayDiff -= 1;
        int Week1 = GetWeekNum(Date1);
        int Week2 = GetWeekNum(Date2);
        int WeekDiff = Week1 - Week2;
        BusDayDiff -= WeekDiff * 2;
        foreach (DateTime Holiday in Holidays)
            if (Date1 >= Holiday && Date2 <= Holiday)
                BusDayDiff--;
        BusDayDiff *= Sign;
        return BusDayDiff;
    }

    private int GetWeekNum(DateTime Date)
    {
        return (int)(Date.AddDays(-(int)Date.DayOfWeek).Ticks / TimeSpan.TicksPerDay / 7);
    }

Вот подход, если вы используете MVC. Я также подсчитал, какие национальные праздники или какие-либо праздничные дни нужно исключить, выбрав их из праздничного календаря, который вам нужно будет сделать.

        foreach (DateTime day in EachDay(model))
        {
            bool key = false;
            foreach (LeaveModel ln in holidaycalendar)
            {
                if (day.Date == ln.Date && day.DayOfWeek != DayOfWeek.Saturday && day.DayOfWeek != DayOfWeek.Sunday)
                {
                    key = true; break;
                }
            }
            if (day.DayOfWeek == DayOfWeek.Saturday || day.DayOfWeek == DayOfWeek.Sunday)
            {
                key = true;
            }
            if (key != true)
            {
                leavecount++;
            }
        }

Leavemodel это список здесь

Еще один подход для расчета рабочих дней, не считая праздников, но с учетом времени суток, возвращающего дробное количество дней:

public static double GetBusinessDays(DateTime startD, DateTime endD)
{
    while (IsWeekend(startD))
        startD = startD.Date.AddDays(1);

    while (IsWeekend(endD))
        endD = endD.Date.AddDays(-1);

    var bussDays = (endD - startD).TotalDays -
        (2 * ((int)(endD - startD).TotalDays / 7)) -
        (startD.DayOfWeek > endD.DayOfWeek ? 2 : 0);

    return bussDays;
}

public static bool IsWeekend(DateTime d)
{
    return d.DayOfWeek == DayOfWeek.Saturday || d.DayOfWeek == DayOfWeek.Sunday;
}

Вы можете поиграть с этим здесь: https://rextester.com/ASHRS53997

Вот одно очень простое решение этой проблемы. У нас есть дата начала, дата окончания и цикл for для увеличения дня и вычисления, чтобы увидеть, является ли это рабочий день или выходные, путем преобразования в строку DayOfWeek.

class Program
{
    static void Main(string[] args)
    {
        DateTime day = new DateTime();
        Console.Write("Inser your end date (example: 01/30/2015): ");
        DateTime endDate = DateTime.Parse(Console.ReadLine());
        int numberOfDays = 0;
        for (day = DateTime.Now.Date; day.Date < endDate.Date; day = day.Date.AddDays(1))
        {
            string dayToString = Convert.ToString(day.DayOfWeek);
            if (dayToString != "Saturday" && dayToString != "Sunday") numberOfDays++;
        }
        Console.WriteLine("Number of working days (not including local holidays) between two dates is "+numberOfDays);
    }
}
Другие вопросы по тегам