рассчитать рабочее время между двумя датами в PHP

У меня есть функция, чтобы вернуть разницу между двумя датами, однако мне нужно разобраться в разнице в рабочих часах, предполагая с понедельника по пятницу (с 9:00 до 17:30):

//DATE DIFF FUNCTION // Set timezone date_default_timezone_set("GMT"); // Time format is UNIX timestamp or // PHP strtotime compatible strings function dateDiff($time1, $time2, $precision = 6) { // If not numeric then convert texts to unix timestamps if (!is_int($time1)) { $time1 = strtotime($time1); } if (!is_int($time2)) { $time2 = strtotime($time2); } // If time1 is bigger than time2 // Then swap time1 and time2 if ($time1 > $time2) { $ttime = $time1; $time1 = $time2; $time2 = $ttime; } // Set up intervals and diffs arrays $intervals = array('year','month','day','hour','minute','second'); $diffs = array(); // Loop thru all intervals foreach ($intervals as $interval) { // Set default diff to 0 $diffs[$interval] = 0; // Create temp time from time1 and interval $ttime = strtotime("+1 " . $interval, $time1); // Loop until temp time is smaller than time2 while ($time2 >= $ttime) { $time1 = $ttime; $diffs[$interval]++; // Create new temp time from time1 and interval $ttime = strtotime("+1 " . $interval, $time1); } } $count = 0; $times = array(); // Loop thru all diffs foreach ($diffs as $interval => $value) { // Break if we have needed precission if ($count >= $precision) { break; } // Add value and interval // if value is bigger than 0 if ($value > 0) { // Add s if value is not 1 if ($value != 1) { $interval .= "s"; } // Add value and interval to times array $times[] = $value . " " . $interval; $count++; } } // Return string with times return implode(", ", $times); } 

Дата 1 = 2012-03-24 03:58:58
Дата 2 = 2012-03-22 11:29:16

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

Или есть лучший способ …?

Этот пример использует встроенные в PHP классы DateTime для выполнения математики даты. Как я подошел к этому, нужно было начать с подсчета числа полных рабочих дней между двумя датами, а затем умножить это на 8 (см. Примечания). Затем он получает часы, отработанные в частичные дни, и добавляет их в общее количество отработанных часов. Включение этого в функцию было бы достаточно простым.

Заметки:

  • Не учитывает временные метки. Но вы уже знаете, как это сделать.
  • Не обрабатывает праздники. (Это можно легко добавить, используя множество праздников и добавив их туда, где вы отфильтровываете субботы и воскресенья).
  • Требует PHP 5.3.6+
  • Предполагается 8-часовой рабочий день. Если сотрудники не берут обед, замените $hours = $days * 8; до $hours = $days * 8.5;

,

 <?php // Initial datetimes $date1 = new DateTime('2012-03-22 11:29:16'); $date2 = new DateTime('2012-03-24 03:58:58'); // Set first datetime to midnight of next day $start = clone $date1; $start->modify('+1 day'); $start->modify('midnight'); // Set second datetime to midnight of that day $end = clone $date2; $end->modify('midnight'); // Count the number of full days between both dates $days = 0; // Loop through each day between two dates $interval = new DateInterval('P1D'); $period = new DatePeriod($start, $interval, $end); foreach ($period as $dt) { // If it is a weekend don't count it if (!in_array($dt->format('l'), array('Saturday', 'Sunday'))) { $days++; } } // Assume 8 hour workdays $hours = $days * 8; // Get the number of hours worked on the first day $date1->modify('5:30 PM'); $diff = $date1->diff($start); $hours += $diff->h; // Get the number of hours worked the second day $date1->modify('8 AM'); $diff = $date2->diff($end); $hours += $diff->h; echo $hours; 

Смотрите это в действии

Справка

  • Класс DateTime
  • Класс DatePeriod
  • Класс DateInterval

Вот что я придумал.

Мое решение проверяет время начала и окончания исходных дат и настраивает их в соответствии с фактическим временем начала и окончания рабочего дня (если исходное время начала до начала работы, оно устанавливает его в последнее).

После этого, как для начала, так и для окончания, время сравнивается с получением параметра DateInterval , вычислением общих дней, часов и т. Д. Затем диапазон дат проверяется на выходные дни, и если найден, общий день уменьшается от diff.

Наконец, часы вычисляются как прокомментированные. 🙂

Приветствия Джону за то, что он вдохновил некоторые из этих решений, особенно DatePeriod чтобы проверить выходные.

Золотая звезда для всех, кто нарушает это; Я буду рад обновить, если кто-нибудь найдет лазейку!


Золотая звезда для себя, я сломал ее! Да, выходные по-прежнему не работают (попробуйте начать в 16:00 в субботу и заканчивая в 13:00 понедельника). Я покорю вас, проблема рабочих часов!

Ninja edit # 2: Я думаю, что я позаботился о ошибках выходных дней, возвращая время начала и окончания к самому последнему соответствующему буднему дню, если они выпадают на выходные. Получил хорошие результаты после тестирования нескольких диапазонов дат (начиная и заканчивая на тех же выходных барах, как и ожидалось). Я не совсем убежден, что это так же оптимизировано / просто, как могло бы быть, но по крайней мере теперь это работает лучше.


 // Settings $workStartHour = 9; $workStartMin = 0; $workEndHour = 17; $workEndMin = 30; $workdayHours = 8.5; $weekends = ['Saturday', 'Sunday']; $hours = 0; // Original start and end times, and their clones that we'll modify. $originalStart = new DateTime('2012-03-22 11:29:16'); $start = clone $originalStart; // Starting on a weekend? Skip to a weekday. while (in_array($start->format('l'), $weekends)) { $start->modify('midnight tomorrow'); } $originalEnd = new DateTime('2012-03-24 03:58:58'); $end = clone $originalEnd; // Ending on a weekend? Go back to a weekday. while (in_array($end->format('l'), $weekends)) { $end->modify('-1 day')->setTime(23, 59); } // Is the start date after the end date? Might happen if start and end // are on the same weekend (whoops). if ($start > $end) throw new Exception('Start date is AFTER end date!'); // Are the times outside of normal work hours? If so, adjust. $startAdj = clone $start; if ($start < $startAdj->setTime($workStartHour, $workStartMin)) { // Start is earlier; adjust to real start time. $start = $startAdj; } else if ($start > $startAdj->setTime($workEndHour, $workEndMin)) { // Start is after close of that day, move to tomorrow. $start = $startAdj->setTime($workStartHour, $workStartMin)->modify('+1 day'); } $endAdj = clone $end; if ($end > $endAdj->setTime($workEndHour, $workEndMin)) { // End is after; adjust to real end time. $end = $endAdj; } else if ($end < $endAdj->setTime($workStartHour, $workStartMin)) { // End is before start of that day, move to day before. $end = $endAdj->setTime($workEndHour, $workEndMin)->modify('-1 day'); } // Calculate the difference between our modified days. $diff = $start->diff($end); // Go through each day using the original values, so we can check for weekends. $period = new DatePeriod($start, new DateInterval('P1D'), $end); foreach ($period as $day) { // If it's a weekend day, take it out of our total days in the diff. if (in_array($day->format('l'), ['Saturday', 'Sunday'])) $diff->d--; } // Calculate! Days * Hours in a day + hours + minutes converted to hours. $hours = ($diff->d * $workdayHours) + $diff->h + round($diff->i / 60, 2); 

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

 function biss_hours($start, $end){ $startDate = new DateTime($start); $endDate = new DateTime($end); $periodInterval = new DateInterval( "PT1H" ); $period = new DatePeriod( $startDate, $periodInterval, $endDate ); $count = 0; foreach($period as $date){ $startofday = clone $date; $startofday->setTime(8,30); $endofday = clone $date; $endofday->setTime(17,30); if($date > $startofday && $date <= $endofday && !in_array($date->format('l'), array('Sunday','Saturday'))){ $count++; } } //Get seconds of Start time $start_d = date("Ymd H:00:00", strtotime($start)); $start_d_seconds = strtotime($start_d); $start_t_seconds = strtotime($start); $start_seconds = $start_t_seconds - $start_d_seconds; //Get seconds of End time $end_d = date("Ymd H:00:00", strtotime($end)); $end_d_seconds = strtotime($end_d); $end_t_seconds = strtotime($end); $end_seconds = $end_t_seconds - $end_d_seconds; $diff = $end_seconds-$start_seconds; if($diff!=0): $count--; endif; $total_min_sec = date('i:s',$diff); return $count .":".$total_min_sec; } $start = '2014-06-23 12:30:00'; $end = '2014-06-27 15:45:00'; $go = biss_hours($start,$end); echo $go;