Расчет прошедших рабочих часов между 2 датами и временем - PullRequest
6 голосов
/ 26 сентября 2008

Дано две даты. Как лучше всего рассчитать количество рабочих часов между ними. С учетом того, что рабочие часы: понедельник с 8 до 5.30 и вторник-пятница с 8.30 до 5.30, и что потенциально любой день может быть выходным днем.

Это мои усилия, кажущиеся чудовищно неэффективными, но с точки зрения количества итераций и того, что метод IsWorkingDay обращается к БД, чтобы увидеть, является ли эта дата и время выходным днем.

Может кто-нибудь предложить какие-либо оптимизации или альтернативы.

 public decimal ElapsedWorkingHours(DateTime start, DateTime finish)
        {

            decimal counter = 0;

            while (start.CompareTo(finish) <= 0)
            {   
                if (IsWorkingDay(start) && IsOfficeHours(start))
                {
                    start = start.AddMinutes(1);
                    counter++;
                }
                else
                {
                    start = start.AddMinutes(1);
                }
            }

            decimal hours;

            if (counter != 0)
            {
                hours = counter/60;
            }

            return hours;
        }

Ответы [ 9 ]

3 голосов
/ 26 сентября 2008

Прежде чем приступить к оптимизации, задайте себе два вопроса.

а) Это работает?

б) Это слишком медленно?

Только если вы ответите на оба вопроса «да», вы готовы начать оптимизацию.

Помимо этого

  • вам нужно беспокоиться только о минутах и ​​часах в день начала и день окончания. Промежуточные дни, очевидно, будут полными 9 / 9,5 часов, если они не являются выходными или выходными
  • Нет необходимости проверять выходные дни, чтобы узнать, выходной ли это

Вот как бы я это сделал

// Normalise start and end    
while start.day is weekend or holiday, start.day++, start.time = 0.00am
    if start.day is monday,
        start.time = max(start.time, 8am)
    else
        start.time = max(start.time, 8.30am)
while end.day is weekend or holiday, end.day--, end.time = 11.59pm
end.time = min(end.time, 5.30pm)

// Now we've normalised, is there any time left?    
if start > end
   return 0

// Calculate time in first day    
timediff = 5.30pm - start.time
day = start.day + 1
// Add time on all intervening days
while(day < end.day)
   // returns 9 or 9.30hrs or 0 as appropriate, could be optimised to grab all records
   // from the database in 1 or 2 hits, by counting all intervening mondays, and all
   // intervening tue-fris (non-holidays)
   timediff += duration(day) 

// Add time on last day
timediff += end.time - 08.30am
if end.day is Monday then
    timediff += end.time - 08.00am
else
    timediff += end.time - 08.30am

return timediff

Вы могли бы сделать что-то вроде ВЫБЕРИТЕ СЧЕТ (ДЕНЬ) ОТ ПРАЗДНИКА, ГДЕ ПРАЗДНИК МЕЖДУ @Start И @End GROUP BY DAY

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

1 голос
/ 26 сентября 2008

Есть также рекурсивное решение. Не обязательно эффективно, но очень весело:

public decimal ElapseddWorkingHours(DateTime start, DateTime finish)
{
    if (start.Date == finish.Date)
        return (finish - start).TotalHours;

    if (IsWorkingDay(start.Date))
        return ElapsedWorkingHours(start, new DateTime(start.Year, start.Month, start.Day, 17, 30, 0))
            + ElapsedWorkingHours(start.Date.AddDays(1).AddHours(DateStartTime(start.Date.AddDays(1)), finish);
    else
        return ElapsedWorkingHours(start.Date.AddDays(1), finish);
}
1 голос
/ 26 сентября 2008

Взгляните на класс TimeSpan. Это даст вам часы между любыми 2 раза.

Один вызов БД также может получить выходные дни между вашими двумя; что-то вроде:

SELECT COUNT(*) FROM HOLIDAY WHERE HOLIDAY BETWEEN @Start AND @End

Умножьте это число на 8 и вычтите его из общего количества часов.

-ian

РЕДАКТИРОВАТЬ : В ответ на ниже, если вы не в отпуске не постоянное количество часов. вы можете хранить HolidayStart и HolidayEnd Time в вашей БД и просто возвращать их из вызова в БД. Выполните подсчет часов, аналогичный тому, который вы используете для основной процедуры.

1 голос
/ 26 сентября 2008

особенно с учетом того, что метод IsWorkingDay обращается к БД, чтобы узнать, является ли этот день выходным

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

0 голосов
/ 30 августа 2010
Dim totalMinutes As Integer = 0

For minute As Integer = 0 To DateDiff(DateInterval.Minute, contextInParameter1, contextInParameter2)
    Dim d As Date = contextInParameter1.AddMinutes(minute)
    If d.DayOfWeek <= DayOfWeek.Friday AndAlso _
       d.DayOfWeek >= DayOfWeek.Monday AndAlso _
       d.Hour >= 8 AndAlso _
       d.Hour <= 17 Then
        totalMinutes += 1
    Else
        Dim test = ""
    End If
Next minute

Dim totalHours = totalMinutes / 60

Кусочек торта!

Ура! * * 1004

0 голосов
/ 26 сентября 2008

Используйте запрос @ Ian для проверки дат, чтобы выяснить, какие дни не являются рабочими. Затем сделайте некоторую математику, чтобы выяснить, попадает ли ваше время начала или окончания в нерабочий день, и вычтите разницу.

Таким образом, если начало - полдень субботы, а конец - полдень понедельника, запрос должен вернуть вам 2 дня, из которых вы рассчитываете 48 часов (2 x 24). Если ваш запрос по IsWorkingDay (start) возвращает false, вычтите из 24 время с начала до полуночи, что даст вам 12 часов или 36 часов общего нерабочего времени.

Теперь, если ваши рабочие часы одинаковы для каждого дня, вы делаете то же самое. Если часы вашего офиса немного разбросаны, у вас будет больше проблем.

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

0 голосов
/ 26 сентября 2008

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

Метод COUNT (*), предложенный Ian Jacobs , кажется хорошим способом подсчета праздников. Что бы вы ни использовали, оно будет обрабатывать целые дни, вам нужно отдельно указать даты начала и окончания.

Подсчет выходных дней прост; если у вас есть функция Weekday (дата), которая возвращает 0 для понедельника или 6 для воскресенья, она выглядит следующим образом:

saturdays = ((finish - start) + Weekday(start) + 2) / 7;
sundays = ((finish - start) + Weekday(start) + 1) / 7;

Примечание: (финиш - старт) не следует понимать буквально, замените его на то, что рассчитывает промежуток времени в днях.

0 голосов
/ 26 сентября 2008

Попробуйте что-нибудь вроде этого:

TimeSpan = TimeSpan Between Date1 And Date2
cntDays = TimeSpan.Days 
cntNumberMondays = Iterate Between Date1 And Date2 Counting Mondays 
cntdays = cntdays - cntnumbermondays
NumHolidays = DBCall To Get # Holidays BETWEEN Date1 AND Date2
Cntdays = cntdays - numholidays 
numberhours = ((decimal)cntdays * NumberHoursInWorkingDay )+((decimal)cntNumberMondays * NumberHoursInMondayWorkDay )
0 голосов
/ 26 сентября 2008

Основываясь на том, что сказал @OregonGhost, вместо использования функции IsWorkingDay () at принимает день и возвращает логическое значение, имейте функцию HolidayCount (), которая принимает диапазон и возвращает целое число, указывающее число праздников в диапазоне. Хитрость здесь в том, что если вы имеете дело с частичной датой для ваших начальных и конечных дней, вам все равно может понадобиться определить, являются ли эти даты самими праздниками. Но даже тогда вы можете использовать новый метод, чтобы убедиться, что вам нужно самое большее три вызова в DB.

...