Обчислити кількість робочих днів між двома датами?


96

Як розрахувати кількість робочих (або робочих днів) днів між двома датами в C # ?


1
Ось приклад у C #, який також охоплює отримання годин. codeproject.com/KB/datetime/CalculatingBusinessHours.aspx
WesleyJohnson

1
Подивіться на infopathdev.com/forums/t/7156.aspx
adatapost

4
так ти не повинен виключати свят?
pxl

Відповіді:


120

Я мав таке завдання раніше і маю рішення. Я б уникав перераховувати всі дні між ними, коли цього можна уникнути, що тут має місце. Я навіть не згадую про створення групи екземплярів 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

1
+1 Це, мабуть, найпростіший та найефективніший спосіб зробити це (моє рішення, що надходить із С ++, не використовує підтримку TimeSpan, C # значно полегшує деякі завдання). BankHolidays - теж приємний штрих!
RedGlyph

2
Також переконайтесь, що державні свята такі: if (firstDay <= bh && bh <= lastDay && bh.IsWorkingDay ())
Tawani

5
Дякую за метод. Хоча, мені довелося додати наступне до вилучення / ітерації банківських свят: if-statement:, && !(bh.DayOfWeek == DayOfWeek.Sunday || bh.DayOfWeek == DayOfWeek.Saturday)інакше це двічі відніме той же день, якщо свято припадає на вихідні.
KristianB

Я змінив останній цикл для виписки Linq: businessDays - = bankHolidays.Select (bankHoliday => bankHoliday.Date) .Count (bh => firstDay <= bh && bh <= lastDay);
JoanComasFdz

1
Також це країни, у яких немає вихідних у суботу, неділю. Дивіться це посилання для отримання додаткової інформації: en.wikipedia.org/wiki/Workweek_and_weekend
Gatej Alexandru

104

Гаразд. Я думаю, що пора опублікувати правильну відповідь:

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 Solutions, розміщені вище, з якихось причин роблять мене сиком.


10
Хороша робота, але, можливо, скористатися переліченнями DayOfWeek, а не кидати їх по інтах?
Нео,

3
Серйозно, найкраще рішення там. Cheers Alec
Mizmor

6
Зауважте, що, хоча ця функція повертає подвійну величину, їй слід довіряти лише цілі робочі дні. Це не дає правильної відповіді на дробові дні, коли йдеться про час.
Пакман

4
Тільки на зауваження, при "1+" він передбачає початок першого дня до кінця останнього дня, без "1+" він передбачає кінець першого дня до кінця останнього дня. Мені знадобився деякий час, щоб це зрозуміти, оскільки я передбачав початок першого дня до початку останнього дня, що для мене було більше сенсом.
Джеффрі ван де Вурст,

11
Це НЕ правильна відповідь. Кількість днів може закінчитися до 4. Майже правильно, не враховується, коли день початку і кінця обертається навколо вихідних, що є найскладнішим елементом. Початок - кінець також не повинен бути в дужках. Це не має нічого спільного з проблемою. 60% випадків це рішення НЕПРАВИЛЬНО .
Сова

47

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

Ось мій погляд на це:

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

"де" можна було "порахувати", щоб скоротити його
bygrace

1
Набагато чіткіше, і перелічені рішення піддаються ліквідації державних свят. Набагато вони набагато повільніші; У LINQPad обчислення робочих днів для 90-денних прогалин у 1 мільйонному циклі ітерації займає 10 секунд, використовуючи це рішення, і лише близько 0,2 секунд, використовуючи прийняту відповідь або набагато приємнішу відповідь Алека Пожидаєва.
Whelkaholism

Щоб бути включено, код повинен бути: повернення перелічуваних .Range (0, dayDifference + 1) ...
Edza

не повертає днів у минулому. Як -18 робочих днів.
iwtu

@iwtu Це передбачає, що to > from. Може, в цьому проблема?
Альфа

22

Визначте метод розширення на 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() ;

Чи не могли б ви просто продовжити і продовжити часовий проміжок, щоб ви могли цим скористатися - оскільки він сказав, що хоче використовувати відстань між двома датами, а не список дат?
WesleyJohnson

Відстань між двома датами - це кількість днів між ними, тому підрахунку () достатньо.
компанія Карлес,

3
Я не впевнений, чому це відповідна відповідь ... у нього немає списку окремих днів, у нього є дві дати, і він хоче знайти кількість робочих днів між ними. Для того, щоб скористатися цим рішенням, вам доведеться надати іншу функцію, яка створює список кожної дати між twyp.
Адам Робінсон

1
Адаме, це простий приклад з мінімальною кількістю коду, необхідного для демонстрації концепції. У своїй оригінальній відповіді я також включив цикл, який доповнив список allDates, який я з тих пір абстрагував у функцію "GetDates". Тест IsWorkingDay можна легко перемістити з оператора LINQ у цей цикл. Мені особисто подобається, як зараз, хоча це дуже зручно для читання щодо того, що відбувається.
Qwerty

10
Може бути вкорочений, змінивши Куди рахувати, та усунувши Граф
рекурсивно

12

Я використовував такий код, щоб також брати до уваги державні свята:

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

чому ви починаєте рахувати, додаючи 1 день до "від" @ for (var date = from.AddDays (1); date <= to; date = date.AddDays (1))?
Oncel Umut TURER

6

Ну це було побито до смерті. :) Однак я все-таки надам іншу відповідь, тому що мені потрібно було дещо інше. Це рішення відрізняється тим, що повертає часовий інтервал між початком та кінцем, і ви можете встановити робочий час дня та додати святкові дні. Отже, ви можете використовувати його для обчислення, чи це відбувається протягом доби, днів, вихідних та навіть свят. І ви можете отримати лише робочі дні чи ні, просто отримавши те, що вам потрібно, від поверненого об’єкта 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);
    }
}

5

Це рішення дозволяє уникнути ітерацій, працює як для різниць буднів + 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 тестування комбінаторної і ShouldBe розширення 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);
    }
}

Ідея цього виникла із рішення SQL, яке я знайшов при переповненні стека. Їхня ідея була твердою, але, на жаль, вона теж мала помилку. Це спрацювало для значень + ve, але їх відображення таблиці пошуку було неправильним для значень -ve.
Тоні О'Хеган

4

Ось код для цієї мети зі шведськими святами, але ви можете адаптувати, які свята вважати. Зверніть увагу, що я додав обмеження, яке ви, можливо, захочете видалити, але це стосувалося веб-системи, і я не хотів, щоб хтось вводив якусь величезну дату, щоб зупинити процес

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

функція issamedate відсутня, а є просто публічним статичним булем IsSameDay (DateTime date1, DateTime date2) {return date1.Date == date2.Date; }
Choco Smith

Ви можете використовувати таблицю пошуку масиву int замість створення нових об'єктів Date.
TheRealChx101

3

Ось короткий зразок коду. Це метод класу, тому він буде працювати лише у вашому класі. Якщо ви хочете, щоб це було static, змініть підпис на private static(або public static).

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

Цей метод створює змінну циклу d, ініціалізує її до початкового дня sd, а потім збільшує на один день кожну ітерацію ( d = d.AddDays(1)).

Він повертає потрібні значення за допомогою yield, що створює iterator. Найцікавіше в ітераторах полягає в тому, що вони не містять усіх значень IEnumerablein в пам'яті, а викликають кожне з них послідовно. Це означає, що ви можете викликати цей метод з самого початку часу і дотепер, не турбуючись про те, що закінчиться пам’ять.


1
Цей метод не повертає кількість робочих днів між двома датами, він повертає робочі дати між двома датами. Код, який ви пропонуєте, дуже чистий, і мені подобається використовувати yield, але він не відповідає на питання.
Мартін

3

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

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
        }

Змінити: новий спосіб, простіший:

public static int ToBusinessWorkingDays(this DateTime start, DateTime due, DateTime[] holidays)
        {
            return Enumerable.Range(0, (due - start).Days)
                            .Select(a => start.AddDays(a))
                            .Where(a => a.DayOfWeek != DayOfWeek.Sunday)
                            .Where(a => a.DayOfWeek != DayOfWeek.Saturday)
                            .Count(a => !holidays.Any(x => x == a));

        }

1

Я думаю, що жодна з наведених відповідей насправді не є правильною. Жоден з них не вирішує всіх особливих випадків, наприклад, коли дати починаються та закінчуються в середині вихідних, коли дата починається у п’ятницю та закінчується у наступний понеділок тощо. На додачу до цього, всі вони обчислюють обчислення до цілого днів, отже, якщо дата початку знаходиться в середині суботи, наприклад, це відніме цілий день від робочих днів, даючи неправильні результати ...

У будь-якому випадку, ось моє рішення, яке є досить ефективним і простим і працює для всіх випадків. Фокус у тому, щоб просто знайти попередній понеділок для дати початку та закінчення, а потім зробити невелику компенсацію, коли початок і кінець трапляються протягом вихідних:

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

2
Це повертає -1, якщо дзвонити з суботи та неділі.
Whelkaholism

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

1

Працює і без петель

Цей метод не використовує жодних циклів і насправді досить простий. Він розширює діапазон дат до повних тижнів, оскільки ми знаємо, що кожен тиждень має 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;
}

1
як ви можете отримати K зі значенням 1,4?
Тоха

0

Я просто поділюсь своїм рішенням. Це спрацювало для мене, можливо, я просто не помічаю / не знаю, що є помилка. Я розпочав з першого неповного тижня, якщо такий є. повний тиждень був з неділі на суботу, тому, якщо (int) _now.DayOfWeek не становив 0 (неділя), перший тиждень був неповним.

Я просто віднімаю від 1 до числа перших тижнів для суботи першого тижня, а потім додаю його до нового рахунку;

Тоді я отримую останній неповний тиждень, потім віднімаю 1 для неділі, а потім додаю до нового підрахунку.

Потім, нарешті, кількість нових тижнів, помножена на 5 (будні), додалася до нового підрахунку.

public int RemoveNonWorkingDays(int numberOfDays){

            int workingDays = 0;

            int firstWeek = 7 - (int)_now.DayOfWeek;

            if(firstWeek < 7){

                if(firstWeek > numberOfDays)
                    return numberOfDays;

                workingDays += firstWeek-1;
                numberOfDays -= firstWeek;
            }


            int lastWeek = numberOfDays % 7;

            if(lastWeek > 0){

                numberOfDays -= lastWeek;
                workingDays += lastWeek - 1;

            }

            workingDays += (numberOfDays/7)*5;

            return workingDays;
        }

0

У мене були проблеми з пошуком надійної 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

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

0

Ось одне дуже просте рішення цієї проблеми. У нас є дата початку, дата закінчення та "для циклу" для збільшення дня та обчислення, щоб перевірити, це робочий день чи вихідні, перетворивши на рядок 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);
    }
}

0

На основі коментаря, позначеного як відповідь та рекомендований патч, а також -> Ця версія хоче перетворити дні на робочі години ... Враховує також години того самого дня.

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

0

Я щойно покращив відповіді @Alexander та @Slauma, щоб підтримати робочий тиждень як параметр, для випадків, коли субота є робочим днем, або навіть випадків, коли є лише кілька днів тижня, які вважаються робочими днями:

/// <summary>
/// Calculate the number of business days between two dates, considering:
///  - Days of the week that are not considered business days.
///  - Holidays between these two dates.
/// </summary>
/// <param name="fDay">First day of the desired 'span'.</param>
/// <param name="lDay">Last day of the desired 'span'.</param>
/// <param name="BusinessDaysOfWeek">Days of the week that are considered to be business days, if NULL considers monday, tuesday, wednesday, thursday and friday as business days of the week.</param>
/// <param name="Holidays">Holidays, if NULL, considers no holiday.</param>
/// <returns>Number of business days during the 'span'</returns>
public static int BusinessDaysUntil(this DateTime fDay, DateTime lDay, DayOfWeek[] BusinessDaysOfWeek = null, DateTime[] Holidays = null)
{
    if (BusinessDaysOfWeek == null)
        BusinessDaysOfWeek = new DayOfWeek[] { DayOfWeek.Monday, DayOfWeek.Tuesday, DayOfWeek.Wednesday, DayOfWeek.Thursday, DayOfWeek.Friday };
    if (Holidays == null)
        Holidays = new DateTime[] { };

    fDay = fDay.Date;
    lDay = lDay.Date;

    if (fDay > lDay)
        throw new ArgumentException("Incorrect last day " + lDay);

    int bDays = (lDay - fDay).Days + 1;
    int fullWeekCount = bDays / 7;
    int fullWeekCountMult = 7 - WeekDays.Length;
    //  Find out if there are weekends during the time exceedng the full weeks
    if (bDays > (fullWeekCount * 7))
    {
        int fDayOfWeek = (int)fDay.DayOfWeek;
        int lDayOfWeek = (int)lDay.DayOfWeek;

        if (fDayOfWeek > lDayOfWeek)
            lDayOfWeek += 7;

        // If they are the same, we already covered it right before the Holiday subtraction
        if (lDayOfWeek != fDayOfWeek)
        {
            //  Here we need to see if any of the days between are considered business days
            for (int i = fDayOfWeek; i <= lDayOfWeek; i++)
                if (!WeekDays.Contains((DayOfWeek)(i > 6 ? i - 7 : i)))
                    bDays -= 1;
        }
    }

    //  Subtract the days that are not in WeekDays[] during the full weeks in the interval
    bDays -= (fullWeekCount * fullWeekCountMult);
    //  Subtract the number of bank holidays during the time interval
    bDays = bDays - Holidays.Select(x => x.Date).Count(x => fDay <= x && x <= lDay);

    return bDays;
}

0

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

Якщо ми все одно хочемо його використовувати, ми можемо взяти третій аргумент як список відпусток, і перед збільшенням підрахунку ми повинні перевірити, що список не містить 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;
    }

0

Я вважаю, що це може бути простішим способом:

    public int BusinessDaysUntil(DateTime start, DateTime end, params DateTime[] bankHolidays)
    {
        int tld = (int)((end - start).TotalDays) + 1; //including end day
        int not_buss_day = 2 * (tld / 7); //Saturday and Sunday
        int rest = tld % 7; //rest.

        if (rest > 0)
        {
            int tmp = (int)start.DayOfWeek - 1 + rest;
            if (tmp == 6 || start.DayOfWeek == DayOfWeek.Sunday) not_buss_day++; else if (tmp > 6) not_buss_day += 2;
        }

        foreach (DateTime bankHoliday in bankHolidays)
        {
            DateTime bh = bankHoliday.Date;
            if (!(bh.DayOfWeek == DayOfWeek.Saturday || bh.DayOfWeek == DayOfWeek.Sunday) && (start <= bh && bh <= end))
            {
                not_buss_day++;
            }
        }
        return tld - not_buss_day;
    }

0

Ось ще одна ідея - цей метод дозволяє вказати будь-який робочий тиждень та святкові дні.

Ідея тут полягає в тому, що ми знаходимо суть діапазону дат від першого робочого дня тижня до останнього вихідного дня тижня. Це дозволяє нам легко обчислювати цілі тижні ( без перебору всіх дат). Все, що нам тоді потрібно зробити, це додати робочі дні, які припадають на початок і кінець цього основного діапазону.

public static int CalculateWorkingDays(
    DateTime startDate, 
    DateTime endDate, 
    IList<DateTime> holidays, 
    DayOfWeek firstDayOfWeek,
    DayOfWeek lastDayOfWeek)
{
    // Make sure the defined working days run contiguously
    if (lastDayOfWeek < firstDayOfWeek)
    {
        throw new Exception("Last day of week cannot fall before first day of week!");
    }

    // Create a list of the days of the week that make-up the weekend by working back
    // from the firstDayOfWeek and forward from lastDayOfWeek to get the start and end
    // the weekend
    var weekendStart = lastDayOfWeek == DayOfWeek.Saturday ? DayOfWeek.Sunday : lastDayOfWeek + 1;
    var weekendEnd = firstDayOfWeek == DayOfWeek.Sunday ? DayOfWeek.Saturday : firstDayOfWeek - 1;
    var weekendDays = new List<DayOfWeek>();

    var w = weekendStart;
    do {
        weekendDays.Add(w);
        if (w == weekendEnd) break;
        w = (w == DayOfWeek.Saturday) ? DayOfWeek.Sunday : w + 1;
    } while (true);


    // Force simple dates - no time
    startDate = startDate.Date;
    endDate = endDate.Date;

    // Ensure a progessive date range
    if (endDate < startDate)
    {
        var t = startDate;
        startDate = endDate;
        endDate = t;
    }

    // setup some working variables and constants
    const int daysInWeek = 7;           // yeah - really!
    var actualStartDate = startDate;    // this will end up on startOfWeek boundary
    var actualEndDate = endDate;        // this will end up on weekendEnd boundary
    int workingDaysInWeek = daysInWeek - weekendDays.Count;

    int workingDays = 0;        // the result we are trying to find
    int leadingDays = 0;        // the number of working days leading up to the firstDayOfWeek boundary
    int trailingDays = 0;       // the number of working days counting back to the weekendEnd boundary

    // Calculate leading working days
    // if we aren't on the firstDayOfWeek we need to step forward to the nearest
    if (startDate.DayOfWeek != firstDayOfWeek)
    {
        var d = startDate;
        do {
            if (d.DayOfWeek == firstDayOfWeek || d >= endDate)
            {
                actualStartDate = d;
                break;  
            }
            if (!weekendDays.Contains(d.DayOfWeek))
            {
                leadingDays++;
            }
            d = d.AddDays(1);
        } while(true);
    }

    // Calculate trailing working days
    // if we aren't on the weekendEnd we step back to the nearest
    if (endDate >= actualStartDate && endDate.DayOfWeek != weekendEnd)
    {
        var d = endDate;
        do {
            if (d.DayOfWeek == weekendEnd || d < actualStartDate)
            {
                actualEndDate = d;
                break;  
            }
            if (!weekendDays.Contains(d.DayOfWeek))
            {
                trailingDays++;
            }
            d = d.AddDays(-1);
        } while(true);
    }

    // Calculate the inclusive number of days between the actualStartDate and the actualEndDate
    var coreDays = (actualEndDate - actualStartDate).Days + 1;
    var noWeeks =  coreDays / daysInWeek;

    // add together leading, core and trailing days
    workingDays +=  noWeeks * workingDaysInWeek;
    workingDays += leadingDays;
    workingDays += trailingDays;

    // Finally remove any holidays that fall within the range.
    if (holidays != null)
    {
        workingDays -= holidays.Count(h => h >= startDate && (h <= endDate));
    }

    return workingDays;
}

0

Оскільки я не можу коментувати. Є ще одна проблема із прийнятим рішенням, коли державні свята віднімаються, навіть коли вони розташовані у вихідні. Побачивши, як перевіряється інший вхід, цілком підходить, що це теж.

Тому передбачення повинно бути:

    // subtract the number of bank holidays during the time interval
    foreach (DateTime bankHoliday in bankHolidays)
    {
        DateTime bh = bankHoliday.Date;

        // Do not subtract bank holidays when they fall in the weekend to avoid double subtraction
        if (bh.DayOfWeek == DayOfWeek.Saturday || bh.DayOfWeek == DayOfWeek.Sunday)
                continue;

        if (firstDay <= bh && bh <= lastDay)
            --businessDays;
    }

0

Ось підхід, якщо ви використовуєте 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 - список тут


0

Ось допоміжна функція, яку я написав для цього завдання.
він також повертає кількість вихідних через 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;
}

0

Я придумав таке рішення

var dateStart = new DateTime(2019,01,10);
var dateEnd = new DateTime(2019,01,31);

var timeBetween = (dateEnd - dateStart).TotalDays + 1;
int numberOf7DayWeeks = (int)(timeBetween / 7);
int numberOfWeekendDays = numberOf7DayWeeks * 2;
int workingDays =(int)( timeBetween - numberOfWeekendDays);

if(dateStart.DayOfWeek == DayOfWeek.Saturday || dateEnd.DayOfWeek == DayOfWeek.Sunday){
    workingDays -=2;
}       
if(dateStart.DayOfWeek == DayOfWeek.Sunday || dateEnd.DayOfWeek == DayOfWeek.Saturday){
    workingDays -=1;
}

0

Вам просто потрібно переглядати кожен день у часовому діапазоні і віднімати день від лічильника, якщо це субота чи неділя.

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

0
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, але цей спосіб набагато легший для розуміння.


0

Ще один підхід до розрахунку робочих днів, не враховуючи свят, а враховуючи час доби, що повертає дробову кількість днів:

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


-1

Це загальне рішення.

startdayvalue - номер дня дати початку.

weekendday_1 - число дня кінця тижня.

номер дня - ПН - 1, ЧТ - 2, ... СБ - 6, НД -7.

різниця - це різниця між двома датами ..

Приклад: Дата початку: 4 квітня 2013 р., Дата завершення: 14 квітня 2013 р

Різниця: 10, значення початкового дня: 4, вихідний день_1: 7 (якщо НЕДІЛЯ для вас вихідні.)

Це дасть вам кількість свят.

No робочого дня = (Різниця + 1) - свято1

    if (startdayvalue > weekendday_1)
    {

        if (difference > ((7 - startdayvalue) + weekendday_1))
        {
            holiday1 = (difference - ((7 - startdayvalue) + weekendday_1)) / 7;
            holiday1 = holiday1 + 1;
        }
        else
        {
            holiday1 = 0;
        }
    }
    else if (startdayvalue < weekendday_1)
    {

        if (difference > (weekendday_1 - startdayvalue))
        {
            holiday1 = (difference - (weekendday_1 - startdayvalue)) / 7;
            holiday1 = holiday1 + 1;
        }
        else if (difference == (weekendday_1 - startdayvalue))
        {
            holiday1 = 1;
        }
        else
        {
            holiday1 = 0;
        }
    }
    else
    {
        holiday1 = difference / 7;
        holiday1 = holiday1 + 1;
    }
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.