Рассчитать количество рабочих дней между двумя датами? - PullRequest
79 голосов
/ 24 октября 2009

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

Ответы [ 29 ]

1 голос
/ 31 декабря 2016

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

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
        }
1 голос
/ 29 июня 2018

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

Этот метод не использует никаких циклов и на самом деле довольно прост. Это расширяет диапазон дат до полных недель, так как мы знаем, что у каждой недели есть 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 голос
/ 28 мая 2015

Вот быстрый пример кода. Это метод класса, поэтому будет работать только внутри вашего класса. Если вы хотите, чтобы это было 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. Крутая вещь в итераторах заключается в том, что они не хранят все значения IEnumerable в памяти, а только вызывают их последовательно. Это означает, что вы можете вызывать этот метод с незапамятных времен до сегодняшнего дня, не беспокоясь о нехватке памяти.

0 голосов
/ 15 мая 2018

Вот подход, если вы используете 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 голосов
/ 30 августа 2017

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

Поэтому foreach должен быть:

    // 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 голосов
/ 11 января 2017

Вот еще одна идея - этот метод позволяет указать любую рабочую неделю и праздничные дни.

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

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 голосов
/ 05 апреля 2013

Я просто поделюсь своим решением. Это сработало для меня, может быть, я просто не замечаю / не знаю, что это ошибка. Я начал с получения первой неполной недели, если есть. полная неделя была с воскресенья на субботу, поэтому, если (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 голосов
/ 26 апреля 2013

Это общее решение.

startdayvalue - номер дня с начальной датой.

daysday_1 - номер дня конца недели.

номер дня - ПН - 1, ТИ - 2, ... СБ - 6, СОЛНЦ-7.

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

Пример: дата начала: 4 апреля 2013 года, дата окончания: 14 апреля 2013 года

Разница: 10, начальное значение: 4, выходной_1: 7 (если воскресенье для вас выходной.)

Это даст вам количество выходных.

Нет рабочего дня = (Разница + 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;
    }
0 голосов
/ 22 мая 2014
 public enum NonWorkingDays { SaturdaySunday = 0, FridaySaturday = 1 };
        public int getBusinessDates(DateTime dateSt, DateTime dateNd, NonWorkingDays nonWorkingDays = NonWorkingDays.SaturdaySunday)
        {
            List<DateTime> datelist = new List<DateTime>();
            while (dateSt.Date < dateNd.Date)
            {
                datelist.Add((dateSt = dateSt.AddDays(1)));
            }
            if (nonWorkingDays == NonWorkingDays.SaturdaySunday)
            {
                return datelist.Count(d => d.DayOfWeek != DayOfWeek.Saturday &&
                       d.DayOfWeek != DayOfWeek.Friday);
            }
            else
            {
                return datelist.Count(d => d.DayOfWeek != DayOfWeek.Friday &&
                       d.DayOfWeek != DayOfWeek.Saturday);
            }
        }
0 голосов
/ 02 марта 2016

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

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