Мне нужен метод PHP для расчета рабочего времени между двумя датами на основе 8-часового рабочего дня и исключая выходные и праздничные дни.
Например, разница между 2012-01-01T08:30:00
И 2012-01-05T10:30:00
в рабочее время составляет 26 рабочих часов, потому что первые два дня – выходные / праздничные дни, которые просто уходят с 3 рабочих дней, а разность времени 2 часа, т. е. 3*8+2=26
.
Я использовал @flamingLogos отличный ответ на предыдущий вопрос, но не могу заставить его учитывать время и дату.
Функция ниже вычисляет рабочее время между двумя датами, представленное в текстовом формате, например «2013-11-27 13:40», с рабочим днем от 9 до 17 (можно изменить).
function get_working_hours($from,$to) { // timestamps $from_timestamp = strtotime($from); $to_timestamp = strtotime($to); // work day seconds $workday_start_hour = 9; $workday_end_hour = 17; $workday_seconds = ($workday_end_hour - $workday_start_hour)*3600; // work days beetwen dates, minus 1 day $from_date = date('Ym-d',$from_timestamp); $to_date = date('Ym-d',$to_timestamp); $workdays_number = count(get_workdays($from_date,$to_date))-1; $workdays_number = $workdays_number<0 ? 0 : $workdays_number; // start and end time $start_time_in_seconds = date("H",$from_timestamp)*3600+date("i",$from_timestamp)*60; $end_time_in_seconds = date("H",$to_timestamp)*3600+date("i",$to_timestamp)*60; // final calculations $working_hours = ($workdays_number * $workday_seconds + $end_time_in_seconds - $start_time_in_seconds) / 86400 * 24; return $working_hours; }
Есть две дополнительные функции. Один возвращает массив рабочих дней …
function get_workdays($from,$to) { // arrays $days_array = array(); $skipdays = array("Saturday", "Sunday"); $skipdates = get_holidays(); // other variables $i = 0; $current = $from; if($current == $to) // same dates { $timestamp = strtotime($from); if (!in_array(date("l", $timestamp), $skipdays)&&!in_array(date("Ymd", $timestamp), $skipdates)) { $days_array[] = date("Ymd",$timestamp); } } elseif($current < $to) // different dates { while ($current < $to) { $timestamp = strtotime($from." +".$i." day"); if (!in_array(date("l", $timestamp), $skipdays)&&!in_array(date("Ymd", $timestamp), $skipdates)) { $days_array[] = date("Ymd",$timestamp); } $current = date("Ymd",$timestamp); $i++; } } return $days_array; }
и второй – возвращает массив праздников
function get_holidays() { // arrays $days_array = array(); // You have to put there your source of holidays and make them as array... // For example, database in Codeigniter: // $days_array = $this->my_model->get_holidays_array(); return $days_array; }
Возможно, вы можете использовать эту функцию:
function work_hours_diff($date1,$date2) { if ($date1>$date2) { $tmp=$date1; $date1=$date2; $date2=$tmp; unset($tmp); $sign=-1; } else $sign = 1; if ($date1==$date2) return 0; $days = 0; $working_days = array(1,2,3,4,5); // Monday-->Friday $working_hours = array(8.5, 17.5); // from 8:30(am) to 17:30 $current_date = $date1; $beg_h = floor($working_hours[0]); $beg_m = ($working_hours[0]*60)%60; $end_h = floor($working_hours[1]); $end_m = ($working_hours[1]*60)%60; // setup the very next first working timestamp if (!in_array(date('w',$current_date) , $working_days)) { // the current day is not a working day // the current timestamp is set at the begining of the working day $current_date = mktime( $beg_h, $beg_m, 0, date('n',$current_date), date('j',$current_date), date('Y',$current_date) ); // search for the next working day while ( !in_array(date('w',$current_date) , $working_days) ) { $current_date += 24*3600; // next day } } else { // check if the current timestamp is inside working hours $date0 = mktime( $beg_h, $beg_m, 0, date('n',$current_date), date('j',$current_date), date('Y',$current_date) ); // it's before working hours, let's update it if ($current_date<$date0) $current_date = $date0; $date3 = mktime( $end_h, $end_m, 59, date('n',$current_date), date('j',$current_date), date('Y',$current_date) ); if ($date3<$current_date) { // outch ! it's after working hours, let's find the next working day $current_date += 24*3600; // the day after // and set timestamp as the begining of the working day $current_date = mktime( $beg_h, $beg_m, 0, date('n',$current_date), date('j',$current_date), date('Y',$current_date) ); while ( !in_array(date('w',$current_date) , $working_days) ) { $current_date += 24*3600; // next day } } } // so, $current_date is now the first working timestamp available... // calculate the number of seconds from current timestamp to the end of the working day $date0 = mktime( $end_h, $end_m, 59, date('n',$current_date), date('j',$current_date), date('Y',$current_date) ); $seconds = $date0-$current_date+1; printf("\nFrom %s To %s : %d hours\n",date('d/m/y H:i',$date1),date('d/m/y H:i',$date0),$seconds/3600); // calculate the number of days from the current day to the end day $date3 = mktime( $beg_h, $beg_m, 0, date('n',$date2), date('j',$date2), date('Y',$date2) ); while ( $current_date < $date3 ) { $current_date += 24*3600; // next day if (in_array(date('w',$current_date) , $working_days) ) $days++; // it's a working day } if ($days>0) $days--; //because we've allready count the first day (in $seconds) printf("\nFrom %s To %s : %d working days\n",date('d/m/y H:i',$date1),date('d/m/y H:i',$date3),$days); // check if end's timestamp is inside working hours $date0 = mktime( $beg_h, 0, 0, date('n',$date2), date('j',$date2), date('Y',$date2) ); if ($date2<$date0) { // it's before, so nothing more ! } else { // is it after ? $date3 = mktime( $end_h, $end_m, 59, date('n',$date2), date('j',$date2), date('Y',$date2) ); if ($date2>$date3) $date2=$date3; // calculate the number of seconds from current timestamp to the final timestamp $tmp = $date2-$date0+1; $seconds += $tmp; printf("\nFrom %s To %s : %d hours\n",date('d/m/y H:i',$date2),date('d/m/y H:i',$date3),$tmp/3600); } // calculate the working days in seconds $seconds += 3600*($working_hours[1]-$working_hours[0])*$days; printf("\nFrom %s To %s : %d hours\n",date('d/m/y H:i',$date1),date('d/m/y H:i',$date2),$seconds/3600); return $sign * $seconds/3600; // to get hours }
сfunction work_hours_diff($date1,$date2) { if ($date1>$date2) { $tmp=$date1; $date1=$date2; $date2=$tmp; unset($tmp); $sign=-1; } else $sign = 1; if ($date1==$date2) return 0; $days = 0; $working_days = array(1,2,3,4,5); // Monday-->Friday $working_hours = array(8.5, 17.5); // from 8:30(am) to 17:30 $current_date = $date1; $beg_h = floor($working_hours[0]); $beg_m = ($working_hours[0]*60)%60; $end_h = floor($working_hours[1]); $end_m = ($working_hours[1]*60)%60; // setup the very next first working timestamp if (!in_array(date('w',$current_date) , $working_days)) { // the current day is not a working day // the current timestamp is set at the begining of the working day $current_date = mktime( $beg_h, $beg_m, 0, date('n',$current_date), date('j',$current_date), date('Y',$current_date) ); // search for the next working day while ( !in_array(date('w',$current_date) , $working_days) ) { $current_date += 24*3600; // next day } } else { // check if the current timestamp is inside working hours $date0 = mktime( $beg_h, $beg_m, 0, date('n',$current_date), date('j',$current_date), date('Y',$current_date) ); // it's before working hours, let's update it if ($current_date<$date0) $current_date = $date0; $date3 = mktime( $end_h, $end_m, 59, date('n',$current_date), date('j',$current_date), date('Y',$current_date) ); if ($date3<$current_date) { // outch ! it's after working hours, let's find the next working day $current_date += 24*3600; // the day after // and set timestamp as the begining of the working day $current_date = mktime( $beg_h, $beg_m, 0, date('n',$current_date), date('j',$current_date), date('Y',$current_date) ); while ( !in_array(date('w',$current_date) , $working_days) ) { $current_date += 24*3600; // next day } } } // so, $current_date is now the first working timestamp available... // calculate the number of seconds from current timestamp to the end of the working day $date0 = mktime( $end_h, $end_m, 59, date('n',$current_date), date('j',$current_date), date('Y',$current_date) ); $seconds = $date0-$current_date+1; printf("\nFrom %s To %s : %d hours\n",date('d/m/y H:i',$date1),date('d/m/y H:i',$date0),$seconds/3600); // calculate the number of days from the current day to the end day $date3 = mktime( $beg_h, $beg_m, 0, date('n',$date2), date('j',$date2), date('Y',$date2) ); while ( $current_date < $date3 ) { $current_date += 24*3600; // next day if (in_array(date('w',$current_date) , $working_days) ) $days++; // it's a working day } if ($days>0) $days--; //because we've allready count the first day (in $seconds) printf("\nFrom %s To %s : %d working days\n",date('d/m/y H:i',$date1),date('d/m/y H:i',$date3),$days); // check if end's timestamp is inside working hours $date0 = mktime( $beg_h, 0, 0, date('n',$date2), date('j',$date2), date('Y',$date2) ); if ($date2<$date0) { // it's before, so nothing more ! } else { // is it after ? $date3 = mktime( $end_h, $end_m, 59, date('n',$date2), date('j',$date2), date('Y',$date2) ); if ($date2>$date3) $date2=$date3; // calculate the number of seconds from current timestamp to the final timestamp $tmp = $date2-$date0+1; $seconds += $tmp; printf("\nFrom %s To %s : %d hours\n",date('d/m/y H:i',$date2),date('d/m/y H:i',$date3),$tmp/3600); } // calculate the working days in seconds $seconds += 3600*($working_hours[1]-$working_hours[0])*$days; printf("\nFrom %s To %s : %d hours\n",date('d/m/y H:i',$date1),date('d/m/y H:i',$date2),$seconds/3600); return $sign * $seconds/3600; // to get hours }
Я помещаю printf (), чтобы показать, что это делается (вы можете удалить их)
Вы называете это так:
date_default_timezone_set("America/Los_Angeles"); $dt2 = strtotime("2012-01-01 05:25:00"); $dt1 = strtotime("2012-01-19 12:40:00"); echo work_hours_diff($dt1 , $dt2 );
Другие два предложения не работают, если вы выбираете начало или конец в нерабочий день или время. Это результаты, которые мой код получает с рабочего дня с 9:00 до 20:00 и дней отдыха в субботу и воскресенье.
get_working_hours('2016-10-08 08:00:00', '2016-10-08 21:00:00'); //Saturday: 0 hrs get_working_hours('2016-10-10 08:00:00', '2016-10-10 21:00:00'); //Monday: 11 hrs get_working_hours('2016-10-10 10:00:00', '2016-10-10 19:00:00'); //Monday: 9 hrs get_working_hours('2016-10-07 19:00:00', '2016-10-10 10:00:00'); //fri-mon: 2 hrs get_working_hours('2016-10-08 19:00:00', '2016-10-10 10:00:00'); //sat-mon: 1 hrs get_working_hours('2016-10-07 19:00:00', '2016-10-09 10:00:00'); //fri-sun: 1 hrs function get_working_hours($ini_str,$end_str){ //config $ini_time = [9,0]; //hr, min $end_time = [20,0]; //hr, min //date objects $ini = date_create($ini_str); $ini_wk = date_time_set(date_create($ini_str),$ini_time[0],$ini_time[1]); $end = date_create($end_str); $end_wk = date_time_set(date_create($end_str),$end_time[0],$end_time[1]); //days $workdays_arr = get_workdays($ini,$end); $workdays_count = count($workdays_arr); $workday_seconds = (($end_time[0] * 60 + $end_time[1]) - ($ini_time[0] * 60 + $ini_time[1])) * 60; //get time difference $ini_seconds = 0; $end_seconds = 0; if(in_array($ini->format('Ym-d'),$workdays_arr)) $ini_seconds = $ini->format('U') - $ini_wk->format('U'); if(in_array($end->format('Ym-d'),$workdays_arr)) $end_seconds = $end_wk->format('U') - $end->format('U'); $seconds_dif = $ini_seconds > 0 ? $ini_seconds : 0; if($end_seconds > 0) $seconds_dif += $end_seconds; //final calculations $working_seconds = ($workdays_count * $workday_seconds) - $seconds_dif; echo $ini_str.' - '.$end_str.'; Working Hours:'.($working_seconds / 3600).b(); return $working_seconds / 3600; //return hrs } function get_workdays($ini,$end){ //config $skipdays = [6,0]; //saturday:6; sunday:0 $skipdates = []; //eg: ['2016-10-10']; //vars $current = clone $ini; $current_disp = $current->format('Ym-d'); $end_disp = $end->format('Ym-d'); $days_arr = []; //days range while($current_disp <= $end_disp){ if(!in_array($current->format('w'),$skipdays) && !in_array($current_disp,$skipdates)){ $days_arr[] = $current_disp; } $current->add(new DateInterval('P1D')); //adds one day $current_disp = $current->format('Ym-d'); } return $days_arr; }