Расчет прошедшего рабочего времени между двумя датами и временем

Учитывая две даты. Как лучше всего посчитать количество рабочих часов между ними. Учитывая, что часы работы: пн 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;
        }

Сколько у вас государственных праздников? ;П

leppie 26.09.2008 23:20
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
6
1
12 540
9

Ответы 9

especially considering the IsWorkingDay method hits the DB to see if that day is a public holiday

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

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

Dan 26.09.2008 23:21

Вместо IsWorkingDay (), который принимает один день, реализуйте метод HolidayCount (), который принимает диапазон дат и возвращает количество праздников в этом диапазоне.

Joel Coehoorn 26.09.2008 23:23

Если что-то делает решение менее эффективным с точки зрения кода, мне придется увеличивать количество дней.

Dan 26.09.2008 23:28

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

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

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

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

Помимо этого

  • вам нужно беспокоиться только о минутах и ​​часах в день начала и день окончания. Промежуточные дни, очевидно, будут составлять полные 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 ГРУППА ПО ДНЯМ

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

Да и да, это как бы избегая моего вопроса.

Dan 26.09.2008 23:23

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

Airsource Ltd 26.09.2008 23:48

@CynicalTyler, поэтому я задаю этот вопрос, пожалуйста, предложите лучший способ, иначе вы зря потратите свое и мое время.

Dan 26.09.2008 23:50

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

Airsource Ltd 28.09.2008 19:18

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

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

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

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

-Ян

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

1. Этот запрос не помогает реализации 2. Не все дни равны 8, а даты начала и окончания могут быть в середине дня.

Dan 26.09.2008 23:26

В защиту Яна, что касается 2, в вашем вопросе говорится: «Учитывая две даты», а не две даты.

Powerlord 26.09.2008 23:32

Основываясь на том, что сказал @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 )

Это не учитывает праздники по понедельникам.

Airsource Ltd 26.09.2008 23:44

Или доли дней. Или если даты не в рабочее время

Dan 26.09.2008 23:48

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

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

Кусок пирога!

Ваше здоровье!

Другие вопросы по теме