Учитывая две даты. Как лучше всего посчитать количество рабочих часов между ними. Учитывая, что часы работы: пн 8 - 5.30 и вт-пт 8.30 - 5.30, потенциально любой день может быть государственным праздником.
Это мои усилия, которые кажутся ужасно неэффективными, но с точки зрения количества итераций и того, что метод IsWorkingDay обращается к базе данных, чтобы увидеть, является ли это datetime государственным праздником.
Может ли кто-нибудь предложить какие-либо оптимизации или альтернативы.
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;
}





especially considering the IsWorkingDay method hits the DB to see if that day is a public holiday
Если проблема заключается в количестве запросов, а не в количестве данных, запросите данные рабочего дня из базы данных для всего диапазона дней, который вам нужен в начале, вместо запроса в каждой итерации цикла.
Конечно, концептуально я понимаю то, что вы говорите. Но проблема в том, что это чистая реализация. И я думаю, что даже если вы проигнорируете запросы, мой подход все равно будет плохим.
Вместо IsWorkingDay (), который принимает один день, реализуйте метод HolidayCount (), который принимает диапазон дат и возвращает количество праздников в этом диапазоне.
Если что-то делает решение менее эффективным с точки зрения кода, мне придется увеличивать количество дней.
Прежде чем приступить к его оптимизации, задайте себе два вопроса.
а) Это работает?
б) Это слишком медленно?
Только если ответ на оба вопроса - «да», вы готовы приступить к оптимизации.
Помимо этого
Вот как бы я это сделал
// 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 ГРУППА ПО ДНЯМ
для подсчета количества выходных, приходящихся на понедельник, вторник, среду и т. д. Вероятно, это способ заставить SQL считать только понедельники и не понедельники, хотя в данный момент ничего не могу придумать.
Да и да, это как бы избегая моего вопроса.
это зависит от того, что вы делаете. Если он работает достаточно быстро, то нет смысла оптимизировать его дальше, по крайней мере, с точки зрения бизнеса.
@CynicalTyler, поэтому я задаю этот вопрос, пожалуйста, предложите лучший способ, иначе вы зря потратите свое и мое время.
Слишком много гипотез. Я легко могу сконструировать корпус, который, как я знаю, не требует масштабирования. Вы можете построить корпус, где он будет. Кроме того, медленный алгоритм все еще может быть правильным для использования в то время, особенно если его легко заменить позже. Как вы правильно сказали, это часть бизнес-кейса.
Взгляните на класс TimeSpan. Это даст вам часы между любыми двумя временами.
Одиночный вызов БД может также получить праздники между вашими двумя временами; что-то вроде:
SELECT COUNT(*) FROM HOLIDAY WHERE HOLIDAY BETWEEN @Start AND @End
Умножьте полученный результат на 8 и вычтите его из общего количества часов.
-Ян
РЕДАКТИРОВАТЬ: В ответ на нижеприведенный вопрос, если ваш отпуск - это не постоянное количество часов. вы можете сохранить HolidayStart и HolidayEnd Time в своей БД, а также просто вернуть их из вызова в БД. Подсчитайте час так же, как любой метод, который вы выбрали для основной рутины.
1. Этот запрос не помогает реализации 2. Не все дни равны 8, а даты начала и окончания могут быть в середине дня.
В защиту Яна, что касается 2, в вашем вопросе говорится: «Учитывая две даты», а не две даты.
Основываясь на том, что сказал @OregonGhost, вместо использования функции IsWorkingDay () в принимает день и возвращает логическое значение, используйте функцию HolidayCount (), которая принимает диапазон и возвращает целое число, дающее количество праздников в диапазоне. Уловка здесь в том, что если вы имеете дело с частичной датой начала и окончания границы, вам все равно может потребоваться определить, являются ли эти даты праздниками. Но даже тогда вы можете использовать новый метод, чтобы убедиться, что вам нужны три вызова в большинстве для DB.
Попробуйте что-нибудь в этом роде:
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 )
Это не учитывает праздники по понедельникам.
Или доли дней. Или если даты не в рабочее время
Также существует рекурсивное решение. Не обязательно эффективно, но очень весело:
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);
}
Самый эффективный способ сделать это - вычислить общую разницу во времени, а затем вычесть время выходных или праздничных дней. Есть довольно много крайних случаев, которые следует учитывать, но вы можете упростить это, взяв первый и последний дни диапазона и вычислив их по отдельности.
Метод COUNT (*), предложенный Ян Джейкобс, кажется хорошим способом подсчета праздников. Что бы вы ни использовали, он будет обрабатывать целые дни, вам нужно указать даты начала и окончания отдельно.
Считать выходные легко; если у вас есть функция Weekday (date), которая возвращает 0 с понедельника по 6 для воскресенья, это выглядит так:
saturdays = ((finish - start) + Weekday(start) + 2) / 7;
sundays = ((finish - start) + Weekday(start) + 1) / 7;
Примечание: (конец - начало) не следует понимать буквально, замените его чем-то, что рассчитывает временной интервал в днях.
Используйте запрос @Ian, чтобы проверить между датами, чтобы узнать, какие дни не являются рабочими днями. Затем произведите математические вычисления, чтобы выяснить, приходится ли ваше время начала или окончания на нерабочий день, и вычтите разницу.
Итак, если начало - полдень субботы, а конец - полдень понедельника, запрос должен вернуть вам 2 дня, из которых вы рассчитываете 48 часов (2 x 24). Если ваш запрос на IsWorkingDay (start) возвращает false, вычтите из 24 время от начала до полуночи, что даст вам 12 часов или 36 часов всего нерабочих часов.
Теперь, если у вас одинаковые рабочие часы на каждый день, вы делаете то же самое. Если ваши рабочие часы немного разбросаны, у вас будет больше проблем.
В идеале, сделайте один запрос к базе данных, который даст вам все часы работы между двумя временами (или даже датами). Затем выполните вычисления локально из этого набора.
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
Кусок пирога!
Ваше здоровье!
Сколько у вас государственных праздников? ;П