Рассчитать рабочие дни

Мне нужен метод добавления «рабочих дней» в PHP. Например, пятница 12/5 + 3 рабочих дня = среда 12/10.

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

Спасибо.

О, я думаю, мы должны упомянуть, что в настоящее время мы можем использовать функцию DateTime :: modify, чтобы сразу добавлять дни недели: $ my_date = new \ DateTime (); $ my_date-> изменить ("+ 7 будний день"); просто будет казаться безупречным.

mika 09.04.2015 14:04

Подробный блог: goo.gl/YOsfPX

Suresh Kamrushi 14.01.2016 11:51

Более простой / понятный ответ: stackoverflow.com/questions/5532002/…

Dave 13.01.2018 20:29

Я создал для этого приличную библиотеку. github.com/andrejsstepanovs/business-days-calculator Он стабилен и готов к производству.

wormhit 06.01.2015 13:59
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Symfony Station Communiqué - 7 июля 2023 г
Symfony Station Communiqué - 7 июля 2023 г
Это коммюнике первоначально появилось на Symfony Station .
Оживление вашего приложения Laravel: Понимание режима обслуживания
Оживление вашего приложения Laravel: Понимание режима обслуживания
Здравствуйте, разработчики! В сегодняшней статье мы рассмотрим важный аспект управления приложениями, который часто упускается из виду в суете...
Установка и настройка Nginx и PHP на Ubuntu-сервере
Установка и настройка Nginx и PHP на Ubuntu-сервере
В этот раз я сделаю руководство по установке и настройке nginx и php на Ubuntu OS.
Коллекции в Laravel более простым способом
Коллекции в Laravel более простым способом
Привет, читатели, сегодня мы узнаем о коллекциях. В Laravel коллекции - это способ манипулировать массивами и играть с массивами данных. Благодаря...
Как установить PHP на Mac
Как установить PHP на Mac
PHP - это популярный язык программирования, который используется для разработки веб-приложений. Если вы используете Mac и хотите разрабатывать...
106
4
130 376
37
Перейти к ответу Данный вопрос помечен как решенный

Ответы 37

Есть несколько аргументов для функции Дата(), которые должны помочь. Если вы отметите дату («w»), вы получите номер дня недели от 0 для воскресенья до 6 для субботы. Так что .. может что-то вроде ..

$busDays = 3;
$day = date("w");
if ( $day > 2 && $day <= 5 ) { /* if between Wed and Fri */
  $day += 2; /* add 2 more days for weekend */
}
$day += $busDays;

Это всего лишь примерный пример одной возможности ..

Для праздников создайте массив дней в каком-либо формате, который может выдавать date (). Пример:

// I know, these aren't holidays
$holidays = array(
    'Jan 2',
    'Feb 3',
    'Mar 5',
    'Apr 7',
    // ...
);

Затем используйте функции in_array () и Дата(), чтобы проверить, представляет ли метка времени праздник:

$day_of_year = date('M j', $timestamp);
$is_holiday = in_array($day_of_year, $holidays);
Ответ принят как подходящий

Вот функция из комментарии пользователей на странице функции date () в руководстве по PHP. Это усовершенствование более ранней функции в комментариях, которая добавляет поддержку високосных лет.

Введите даты начала и окончания, а также массив праздников, которые могут быть между ними, и он вернет рабочие дни в виде целого числа:

<?php
//The function returns the no. of business days between two dates and it skips the holidays
function getWorkingDays($startDate,$endDate,$holidays){
    // do strtotime calculations just once
    $endDate = strtotime($endDate);
    $startDate = strtotime($startDate);


    //The total number of days between the two dates. We compute the no. of seconds and divide it to 60*60*24
    //We add one to inlude both dates in the interval.
    $days = ($endDate - $startDate) / 86400 + 1;

    $no_full_weeks = floor($days / 7);
    $no_remaining_days = fmod($days, 7);

    //It will return 1 if it's Monday,.. ,7 for Sunday
    $the_first_day_of_week = date("N", $startDate);
    $the_last_day_of_week = date("N", $endDate);

    //---->The two can be equal in leap years when february has 29 days, the equal sign is added here
    //In the first case the whole interval is within a week, in the second case the interval falls in two weeks.
    if ($the_first_day_of_week <= $the_last_day_of_week) {
        if ($the_first_day_of_week <= 6 && 6 <= $the_last_day_of_week) $no_remaining_days--;
        if ($the_first_day_of_week <= 7 && 7 <= $the_last_day_of_week) $no_remaining_days--;
    }
    else {
        // (edit by Tokes to fix an edge case where the start day was a Sunday
        // and the end day was NOT a Saturday)

        // the day of the week for start is later than the day of the week for end
        if ($the_first_day_of_week == 7) {
            // if the start date is a Sunday, then we definitely subtract 1 day
            $no_remaining_days--;

            if ($the_last_day_of_week == 6) {
                // if the end date is a Saturday, then we subtract another day
                $no_remaining_days--;
            }
        }
        else {
            // the start date was a Saturday (or earlier), and the end date was (Mon..Fri)
            // so we skip an entire weekend and subtract 2 days
            $no_remaining_days -= 2;
        }
    }

    //The no. of business days is: (number of weeks between the two dates) * (5 working days) + the remainder
//---->february in none leap years gave a remainder of 0 but still calculated weekends between first and last day, this is one way to fix it
   $workingDays = $no_full_weeks * 5;
    if ($no_remaining_days > 0 )
    {
      $workingDays += $no_remaining_days;
    }

    //We subtract the holidays
    foreach($holidays as $holiday){
        $time_stamp=strtotime($holiday);
        //If the holiday doesn't fall in weekend
        if ($startDate <= $time_stamp && $time_stamp <= $endDate && date("N",$time_stamp) != 6 && date("N",$time_stamp) != 7)
            $workingDays--;
    }

    return $workingDays;
}

//Example:

$holidays=array("2008-12-25","2008-12-26","2009-01-01");

echo getWorkingDays("2008-12-22","2009-01-02",$holidays)
// => will return 7
?>

эта функция ожидает дату начала и дату окончания. Что, если у вас есть дата начала и вы хотите, чтобы результат был равен x рабочим дням с указанной даты?

mcgrailm 18.05.2010 16:15

@mcgrailm: это похожая идея, но вы, вероятно, захотите написать вторую функцию, потому что аргументы и возвращаемые значения меняются местами. Это будет что-то вроде ((X дней% 5 дней в неделю) * 2 дня в выходные) + X дней + разница дней недели начальной и конечной дат + праздники).

flamingLogos 20.05.2010 07:32

@mcgrailm: Только что нашел этот вопрос - его ответы могут указать вам правильное направление: stackoverflow.com/questions/2681787/….

flamingLogos 20.05.2010 07:46

Просто если это кому-то поможет. Я уже пытался вызвать этот формат даты unix, а не использовать строки в формате YYYY-MM-DD, который ожидает этот метод. Если у вас уже есть формат даты в int, вы можете просто удалить вызовы strtotime () вверху. Надеюсь, это поможет кому-нибудь с моей проблемой

ThePerson 05.07.2012 15:08

Еще одна вещь. Если вы сравните пятницу, 14:00, со следующей субботой, 11:00, то увидите, что это 1 день, затем, поскольку конец - суббота, он берет один выходной и возвращает 0. Это правда, что это 0 дней, но для некоторых применений (например, при сравнении сроков сдачи работы) любое время после пятницы 14:00 должно считаться 1 днем. (Как и при сравнении 14:00 пятницы с 15:00 пятницы, это один день, но сравнение 14:00 пятницы и 11:00 субботы дает 0)

ThePerson 05.07.2012 16:57

В этой функции есть ошибка. Что, если между этими датами произошла смена часового пояса? Например, для CEST: echo getWorkingDays («2012-01-01», «2012-05-01», $ holiday); не даст вам целого числа. stackoverflow.com/questions/12490521/…

mnowotka 19.09.2012 11:58

Это способ Старый. Если возможно, используйте ответ Главича

Thomas Ruiz 30.06.2014 14:42

Для меня эта функция не работает в последних кварталах - возможно, из-за вышеупомянутой ошибки DST. Метод Главича работает отлично.

Steve Horvath 21.10.2016 04:30

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

jpswade 25.01.2020 15:04

Вот функция для добавления рабочих дней к дате

 function add_business_days($startdate,$buisnessdays,$holidays,$dateformat){
  $i=1;
  $dayx = strtotime($startdate);
  while($i < $buisnessdays){
   $day = date('N',$dayx);
   $date = date('Y-m-d',$dayx);
   if ($day < 6 && !in_array($date,$holidays))$i++;
   $dayx = strtotime($date.' +1 day');
  }
  return date($dateformat,$dayx);
 }

 //Example
 date_default_timezone_set('Europe\London');
 $startdate = '2012-01-08';
 $holidays=array("2012-01-10");
 echo '<p>Start date: '.date('r',strtotime( $startdate));
 echo '<p>'.add_business_days($startdate,7,$holidays,'r');

В другом сообщении упоминается getWorkingDays (из комментариев php.net и включенных здесь), но я думаю, что он ломается, если вы начинаете в воскресенье и заканчиваете рабочий день.

Используя следующее (вам нужно будет включить функцию getWorkingDays из предыдущего сообщения)

 date_default_timezone_set('Europe\London');
 //Example:
 $holidays = array('2012-01-10');
 $startDate = '2012-01-08';
 $endDate = '2012-01-13';
 echo getWorkingDays( $startDate,$endDate,$holidays);

Дает результат 5, а не 4

Sun, 08 Jan 2012 00:00:00 +0000 weekend
Mon, 09 Jan 2012 00:00:00 +0000
Tue, 10 Jan 2012 00:00:00 +0000 holiday
Wed, 11 Jan 2012 00:00:00 +0000
Thu, 12 Jan 2012 00:00:00 +0000
Fri, 13 Jan 2012 00:00:00 +0000 

Следующая функция была использована для создания вышеуказанного.

     function get_working_days($startDate,$endDate,$holidays){
      $debug = true;
      $work = 0;
      $nowork = 0;
      $dayx = strtotime($startDate);
      $endx = strtotime($endDate);
      if ($debug){
       echo '<h1>get_working_days</h1>';
       echo 'startDate: '.date('r',strtotime( $startDate)).'<br>';
       echo 'endDate: '.date('r',strtotime( $endDate)).'<br>';
       var_dump($holidays);
       echo '<p>Go to work...';
      }
      while($dayx <= $endx){
       $day = date('N',$dayx);
       $date = date('Y-m-d',$dayx);
       if ($debug)echo '<br />'.date('r',$dayx).' ';
       if ($day > 5 || in_array($date,$holidays)){
        $nowork++;
     if ($debug){
      if ($day > 5)echo 'weekend';
      else echo 'holiday';
     }
       } else $work++;
       $dayx = strtotime($date.' +1 day');
      }
      if ($debug){
      echo '<p>No work: '.$nowork.'<br>';
      echo 'Work: '.$work.'<br>';
      echo 'Work + no work: '.($nowork+$work).'<br>';
      echo 'All seconds / seconds in a day: '.floatval(strtotime($endDate)-strtotime($startDate))/floatval(24*60*60);
      }
      return $work;
     }

    date_default_timezone_set('Europe\London');
     //Example:
     $holidays=array("2012-01-10");
     $startDate = '2012-01-08';
     $endDate = '2012-01-13';
//broken
     echo getWorkingDays( $startDate,$endDate,$holidays);
//works
     echo get_working_days( $startDate,$endDate,$holidays);

Принеси праздники ...

функция get_business_days_forward_from_date ($ num_days, $ start_date = '', $ rtn_fmt = 'Y-m-d') {

// $start_date will default to today    

if ($start_date=='') { $start_date = date("Y-m-d"); }

$business_day_ct = 0;

$max_days = 10000 + $num_days;  // to avoid any possibility of an infinite loop


// define holidays, this currently only goes to 2012 because, well, you know... ;-)
// if the world is still here after that, you can find more at
// http://www.opm.gov/Operating_Status_Schedules/fedhol/2013.asp
// always add holidays in order, because the iteration will stop when the holiday is > date being tested

$fed_holidays=array(
    "2010-01-01",
    "2010-01-18",
    "2010-02-15",
    "2010-05-31",
    "2010-07-05",
    "2010-09-06",
    "2010-10-11",
    "2010-11-11",
    "2010-11-25",
    "2010-12-24",

    "2010-12-31",
    "2011-01-17",
    "2011-02-21",
    "2011-05-30",
    "2011-07-04",
    "2011-09-05",
    "2011-10-10",
    "2011-11-11",
    "2011-11-24",
    "2011-12-26",

    "2012-01-02",
    "2012-01-16",
    "2012-02-20",
    "2012-05-28",
    "2012-07-04",
    "2012-09-03",
    "2012-10-08",
    "2012-11-12",
    "2012-11-22",
    "2012-12-25",
    );

$curr_date_ymd = date('Y-m-d', strtotime($start_date));    

for ($x=1;$x<$max_days;$x++)
{
    if (intval($num_days)==intval($business_day_ct)) { return(date($rtn_fmt, strtotime($curr_date_ymd))); }  // date found - return

    // get next day to check

    $curr_date_ymd = date('Y-m-d', (strtotime($start_date)+($x * 86400)));   // add 1 day to the current date

    $is_business_day = 1;

    // check if this is a weekend   1 (for Monday) through 7 (for Sunday)

    if ( intval(date("N",strtotime($curr_date_ymd))) > 5) { $is_business_day = 0; }

    //check for holiday
    foreach($fed_holidays as $holiday)
    {
        if (strtotime($holiday)==strtotime($curr_date_ymd))  // holiday found
        {
            $is_business_day = 0;
            break 1;
        }

        if (strtotime($holiday)>strtotime($curr_date_ymd)) { break 1; }  // past date, stop searching (always add holidays in order)


    }

    $business_day_ct = $business_day_ct + $is_business_day;  // increment if this is a business day

} 

// if we get here, you are hosed
return ("ERROR");

}

Функция для добавления или вычитания рабочих дней из заданной даты, это не учитывает праздники.

function dateFromBusinessDays($days, $dateTime=null) {
  $dateTime = is_null($dateTime) ? time() : $dateTime;
  $_day = 0;
  $_direction = $days == 0 ? 0 : intval($days/abs($days));
  $_day_value = (60 * 60 * 24);

  while($_day !== $days) {
    $dateTime += $_direction * $_day_value;

    $_day_w = date("w", $dateTime);
    if ($_day_w > 0 && $_day_w < 6) {
      $_day += $_direction * 1; 
    }
  }

  return $dateTime;
}

использовать так ...

echo date("m/d/Y", dateFromBusinessDays(-7));
echo date("m/d/Y", dateFromBusinessDays(3, time() + 3*60*60*24));

У меня была такая же потребность, я начал с первого примера бобины и закончил с этим

  function add_business_days($startdate,$buisnessdays,$holidays=array(),$dateformat){
    $enddate = strtotime($startdate);
    $day = date('N',$enddate);
    while($buisnessdays > 1){
        $enddate = strtotime(date('Y-m-d',$enddate).' +1 day');
        $day = date('N',$enddate);
        if ($day < 6 && !in_array($enddate,$holidays))$buisnessdays--;
    }
    return date($dateformat,$enddate);
  }

кто-то

извините, но ваш mcgrailm не совсем работает .... он не учитывает, если день в $ enddate выпадает на праздник ... его беспокоят только праздники во время добавления, если я что-то не упускаю

user605865 07.02.2011 05:35

@ Ричард, кажется, я понимаю, о чем ты говоришь. Он НЕ проверяет дату начала, чтобы узнать, выходной или выходной, он рассчитывает рабочие дни ПОСЛЕ даты начала. если вы хотите указать дату начала в чеке, вы можете убрать +1 день

mcgrailm 07.02.2011 17:09

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

Craig Francis 14.10.2011 14:35

Очень полезно - во-первых, мне пришлось заменить !in_array($enddate,$holidays) на !in_array(date('Y-m-d',$enddate),$holidays), если использовался праздничный массив, переданный как Боббинс - $holidays=array('2013-06-16','2013-07-12','2013-08-05');. В противном случае вы проверяете массив, полный дат для временной метки, которая всегда возвращает false.

McNab 10.06.2013 18:08
<?php
// $today is the UNIX timestamp for today's date
$today = time();
echo "<strong>Today is (ORDER DATE): " . '<font color = "red">' . date('l, F j, Y', $today) . "</font></strong><br/><br/>";

//The numerical representation for day of week (Ex. 01 for Monday .... 07 for Sunday
$today_numerical = date("N",$today);

//leadtime_days holds the numeric value for the number of business days 
$leadtime_days = $_POST["leadtime"];

//leadtime is the adjusted date for shipdate
$shipdate = time();

while ($leadtime_days > 0) 
{
 if ($today_numerical != 5 && $today_numerical != 6)
 {
  $shipdate = $shipdate + (60*60*24);
  $today_numerical = date("N",$shipdate);
  $leadtime_days --;
 }
 else
  $shipdate = $shipdate + (60*60*24);
  $today_numerical = date("N",$shipdate);
}

echo '<strong>Estimated Ship date: ' . '<font color = "green">' . date('l, F j, Y', $shipdate) . "</font></strong>";
?>

Я просто заставляю свою функцию работать на основе кода Bobbin и mcgrailm, добавляя некоторые вещи, которые мне идеально подходят.

function add_business_days($startdate,$buisnessdays,$holidays,$dateformat){
    $enddate = strtotime($startdate);
    $day = date('N',$enddate);
    while($buisnessdays > 0){ // compatible with 1 businessday if I'll need it
        $enddate = strtotime(date('Y-m-d',$enddate).' +1 day');
        $day = date('N',$enddate);
        if ($day < 6 && !in_array(date('Y-m-d',$enddate),$holidays))$buisnessdays--;
    }
    return date($dateformat,$enddate);
}

// as a parameter in in_array function we should use endate formated to 
// compare correctly with the holidays array.

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

mcgrailm 25.01.2011 16:55

В add_business_days есть небольшая ошибка. Попробуйте сделать следующее с существующей функцией, и результат будет в субботу.

Дата начала = пятница Рабочие дни для добавления = 1 Массив праздников = Добавить дату следующего понедельника.

Я исправил это в своей функции ниже.

function add_business_days($startdate, $buisnessdays, $holidays = array(), $dateformat = 'Y-m-d'){
$i= 1;
$dayx= strtotime($startdate);
$buisnessdays= ceil($buisnessdays);

while($i < $buisnessdays)
{
    $day= date('N',$dayx);

    $date= date('Y-m-d',$dayx);
    if ($day < 6 && !in_array($date,$holidays))
        $i++;

    $dayx= strtotime($date.' +1 day');
}

## If the calculated day falls on a weekend or is a holiday, then add days to the next business day
$day= date('N',$dayx);
$date= date('Y-m-d',$dayx);

while($day >= 6 || in_array($date,$holidays))
{
    $dayx= strtotime($date.' +1 day');
    $day= date('N',$dayx);
    $date= date('Y-m-d',$dayx);
}

return date($dateformat, $dayx);}

Я предполагаю, что это основано на коде Бобина, я полагаю, что я тоже решил эту проблему

mcgrailm 04.02.2011 19:20

Вариант 1:

<?php
/*
 * Does not count current day, the date returned is the last business day
 * Requires PHP 5.1 (Using ISO-8601 week)
 */

function businessDays($timestamp = false, $bDays = 2) {
    if ($timestamp === false) $timestamp = time();
    while ($bDays>0) {
        $timestamp += 86400;
        if (date('N', $timestamp)<6) $bDays--;
    }
    return $timestamp;
}

Вариант 2:

<?php
/*
 * Does not count current day, the date returned is a business day 
 * following the last business day
 * Requires PHP 5.1 (Using ISO-8601 week)
 */

function businessDays($timestamp = false, $bDays = 2) {
    if ($timestamp === false) $timestamp = time();
    while ($bDays+1>0) {
        $timestamp += 86400;
        if (date('N', $timestamp)<6) $bDays--;
    }
    return $timestamp;
}

Вариант 3:

<?php
/*
 * Does not count current day, the date returned is 
 * a date following the last business day (can be weekend or not. 
 * See above for alternatives)
 * Requires PHP 5.1 (Using ISO-8601 week)
 */

function businessDays($timestamp = false, $bDays = 2) {
    if ($timestamp === false) $timestamp = time();
    while ($bDays>0) {
        $timestamp += 86400;
        if (date('N', $timestamp)<6) $bDays--;
    }
    return $timestamp += 86400;
}

Дополнительные соображения по поводу праздников можно сделать, используя варианты вышеописанного, выполнив следующие действия. Примечание! убедитесь, что все временные метки относятся к одному и тому же времени суток (например, в полночь).

Создайте массив праздничных дат (как unixtimestamps), то есть:

$holidays = array_flip(strtotime('2011-01-01'),strtotime('2011-12-25'));

Изменить строку:

if (date('N', $timestamp)<6) $bDays--;

быть :

if (date('N', $timestamp)<6 && !isset($holidays[$timestamp])) $bDays--;

Сделанный!

<?php
/*
 * Does not count current day, the date returned is the last business day
 * Requires PHP 5.1 (Using ISO-8601 week)
 */

function businessDays($timestamp = false, $bDays = 2) {
    if ($timestamp === false) $timestamp = strtotime(date('Y-m-d',time()));
    $holidays = array_flip(strtotime('2011-01-01'),strtotime('2011-12-25'));
    while ($bDays>0) {
        $timestamp += 86400;
        if (date('N', $timestamp)<6 && !isset($holidays[$timestamp])) $bDays--;
    }
    return $timestamp;
}
<?php 
function AddWorkDays(){
$i = 0;
$d = 5; // Number of days to add

    while($i <= $d) {
    $i++;
        if (date('N', mktime(0, 0, 0, date(m), date(d)+$i, date(Y))) < 5) {
            $d++;
        }
    }
    return date(Y).','.date(m).','.(date(d)+$d);
}
?>

Моя версия, основанная на работе @mcgrailm ... была изменена, потому что отчет нужно было просмотреть в течение 3 рабочих дней, а если он был отправлен в выходные, подсчет начнется в следующий понедельник:

function business_days_add($start_date, $business_days, $holidays = array()) {
    $current_date = strtotime($start_date);
    $business_days = intval($business_days); // Decrement does not work on strings
    while ($business_days > 0) {
        if (date('N', $current_date) < 6 && !in_array(date('Y-m-d', $current_date), $holidays)) {
            $business_days--;
        }
        if ($business_days > 0) {
            $current_date = strtotime('+1 day', $current_date);
        }
    }
    return $current_date;
}

И вычисляем разницу двух дат в рабочих днях:

function business_days_diff($start_date, $end_date, $holidays = array()) {
    $business_days = 0;
    $current_date = strtotime($start_date);
    $end_date = strtotime($end_date);
    while ($current_date <= $end_date) {
        if (date('N', $current_date) < 6 && !in_array(date('Y-m-d', $current_date), $holidays)) {
            $business_days++;
        }
        if ($current_date <= $end_date) {
            $current_date = strtotime('+1 day', $current_date);
        }
    }
    return $business_days;
}

В качестве примечания, все, кто использует 86400 или 24 * 60 * 60, пожалуйста, не ... время вашего забвения меняется с зимнего / летнего времени, где день - это не ровно 24 часа. Хотя strtotime немного медленнее ('+ 1 день', $ timestamp), он намного надежнее.

Грубая попытка определения рабочего времени - с понедельника по пятницу с 8 до 16:

if (date('N')<6 && date('G')>8 && date('G')<16) {
   // we have a working time (or check for holidays)
}

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

//  Returns a $numBusDays-sized array of all business dates, 
//  starting from and including $currentDate. 
//  Any date in $holidays will be skipped over.

function getWorkingDays($currentDate, $numBusDays, $holidays = array(), 
  $resultDates = array())
{
  //  exit when we have collected the required number of business days
  if ($numBusDays === 0) {
    return $resultDates;
  }

  //  add current date to return array, if not a weekend or holiday
  $date = date("w", strtotime($currentDate));
  if ( $date != 0  &&  $date != 6  &&  !in_array($currentDate, $holidays) ) {
    $resultDates[] = $currentDate;
    $numBusDays -= 1;
  }

  //  set up the next date to test
  $currentDate = new DateTime("$currentDate + 1 day");
  $currentDate = $currentDate->format('Y-m-d');

  return getWorkingDays($currentDate, $numBusDays, $holidays, $resultDates);
}

//  test
$days = getWorkingDays('2008-12-05', 4);
print_r($days);
date_default_timezone_set('America/New_York');


/** Given a number days out, what day is that when counting by 'business' days
  * get the next business day. by default it looks for next business day
  * ie calling  $date = get_next_busines_day(); on monday will return tuesday
  *             $date = get_next_busines_day(2); on monday will return wednesday
  *             $date = get_next_busines_day(2); on friday will return tuesday
  *
  * @param $number_of_business_days (integer)       how many business days out do you want
  * @param $start_date (string)                     strtotime parseable time value
  * @param $ignore_holidays (boolean)               true/false to ignore holidays
  * @param $return_format (string)                  as specified in php.net/date
 */
function get_next_business_day($number_of_business_days=1,$start_date='today',$ignore_holidays=false,$return_format='m/d/y') {

    // get the start date as a string to time
    $result = strtotime($start_date);

    // now keep adding to today's date until number of business days is 0 and we land on a business day
    while ($number_of_business_days > 0) {
        // add one day to the start date
        $result = strtotime(date('Y-m-d',$result) . " + 1 day");

        // this day counts if it's a weekend and not a holiday, or if we choose to ignore holidays
        if (is_weekday(date('Y-m-d',$result)) && (!(is_holiday(date('Y-m-d',$result))) || $ignore_holidays) ) 
            $number_of_business_days--;

    }

    // when my $number of business days is exausted I have my final date

    return(date($return_format,$result));
}

    function is_weekend($date) {
    // return if this is a weekend date or not.
    return (date('N', strtotime($date)) >= 6);
}

function is_weekday($date) {
    // return if this is a weekend date or not.
    return (date('N', strtotime($date)) < 6);
}

function is_holiday($date) {
    // return if this is a holiday or not.

    // what are my holidays for this year
    $holidays = array("New Year's Day 2011" => "12/31/10",
                        "Good Friday" => "04/06/12",
                        "Memorial Day" => "05/28/12",
                        "Independence Day" => "07/04/12",
                        "Floating Holiday" => "12/31/12",
                        "Labor Day" => "09/03/12",
                        "Thanksgiving Day" => "11/22/12",
                        "Day After Thanksgiving Day" => "11/23/12",
                        "Christmas Eve" => "12/24/12",
                        "Christmas Day" => "12/25/12",
                        "New Year's Day 2012" => "01/02/12",
                        "New Year's Day 2013" => "01/01/13"
                        );

    return(in_array(date('m/d/y', strtotime($date)),$holidays));
}


print get_next_business_day(1) . "\n";

Есть формула:

number_of_days - math_round_down(10 * (number_of_days / (business_days_in_a_week * days_in_a_week)))

Тада! вы подсчитываете количество рабочих дней в месяц, в неделю, как хотите.

math_round_down () - это гипотетический метод, который реализует математическую функцию с округлением в меньшую сторону.

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

/**
 * National American Holidays
 * @param string $year
 * @return array
 */
public static function getNationalAmericanHolidays($year) {


    //  January 1 - New Year’s Day (Observed)
    //  Calc Last Monday in May - Memorial Day  strtotime("last Monday of May 2011");
    //  July 4 Independence Day
    //  First monday in september - Labor Day strtotime("first Monday of September 2011")
    //  November 11 - Veterans’ Day (Observed)
    //  Fourth Thursday in November Thanksgiving strtotime("fourth Thursday of November 2011");
    //  December 25 - Christmas Day        
    $bankHolidays = array(
          $year . "-01-01" // New Years
        , "". date("Y-m-d",strtotime("last Monday of May " . $year) ) // Memorial Day
        , $year . "-07-04" // Independence Day (corrected)
        , "". date("Y-m-d",strtotime("first Monday of September " . $year) ) // Labor Day
        , $year . "-11-11" // Veterans Day
        , "". date("Y-m-d",strtotime("fourth Thursday of November " . $year) ) // Thanksgiving
        , $year . "-12-25" // XMAS
        );

    return $bankHolidays;
}
Независимость день равен $ year. '-07-04' (4 июля), а не 4 июня
solepixel 22.04.2013 23:13

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

BrookeAH 30.11.2017 19:20

Улучшение функции, предложенной Джеймсом Пастой выше, для включения всех федеральных праздников и исправления 4 июля (было рассчитано как 4 июня выше!), А также для включения названия праздника в качестве ключа массива ...

/ **
* Национальные американские праздники * @param string $ year
* @return array
* /
общедоступная статическая функция getNationalAmericanHolidays ($ year) {

//  January 1 - New Year's Day (Observed)
//  Third Monday in January - Birthday of Martin Luther King, Jr.
//  Third Monday in February - Washington’s Birthday / President's Day
//  Last Monday in May - Memorial Day
//  July 4 - Independence Day
//  First Monday in September - Labor Day
//  Second Monday in October - Columbus Day
//  November 11 - Veterans’ Day (Observed)
//  Fourth Thursday in November Thanksgiving Day
//  December 25 - Christmas Day
$bankHolidays = array(
    ['New Years Day'] => $year . "-01-01",
    ['Martin Luther King Jr Birthday'] => "". date("Y-m-d",strtotime("third Monday of January " . $year) ),
    ['Washingtons Birthday'] => "". date("Y-m-d",strtotime("third Monday of February " . $year) ),
    ['Memorial Day'] => "". date("Y-m-d",strtotime("last Monday of May " . $year) ),
    ['Independance Day'] => $year . "-07-04",
    ['Labor Day'] => "". date("Y-m-d",strtotime("first Monday of September " . $year) ),
    ['Columbus Day'] => "". date("Y-m-d",strtotime("second Monday of October " . $year) ),
    ['Veterans Day'] => $year . "-11-11",
    ['Thanksgiving Day'] => "". date("Y-m-d",strtotime("fourth Thursday of November " . $year) ),
    ['Christmas Day'] => $year . "-12-25"
);

return $bankHolidays;

}

https://github.com/Arbitr108/useful_php Вот класс, который может оценивать рабочие дни в календаре. Поэтому после оценки просто добавьте календарный период к нужной вам дате.

Только что закончили писать API, который можно использовать для управления рабочими днями (ни одно из этих решений не сработало в моей ситуации :-); ссылку на него здесь, если кто-то сочтет это полезным.

~ Нейт

Класс PHP для расчета рабочих дней

Спасибо Bobbin, mcgrailm, Tony, James Pasta и некоторым другим, кто разместил здесь. Я написал свою собственную функцию для добавления рабочих дней к дате, но изменил ее с помощью некоторого кода, который я нашел здесь. Это будет обрабатывать дату начала, приходящуюся на выходные / праздничные дни. Это также относится к рабочим часам. Я добавил несколько комментариев и разбил код, чтобы его было легче читать.

<?php
function count_business_days($date, $days, $holidays) {
    $date = strtotime($date);

    for ($i = 1; $i <= intval($days); $i++) { //Loops each day count

        //First, find the next available weekday because this might be a weekend/holiday
        while (date('N', $date) >= 6 || in_array(date('Y-m-d', $date), $holidays)){
            $date = strtotime(date('Y-m-d',$date).' +1 day');
        }

        //Now that we know we have a business day, add 1 day to it
        $date = strtotime(date('Y-m-d',$date).' +1 day');

        //If this day that was previously added falls on a weekend/holiday, then find the next business day
        while (date('N', $date) >= 6 || in_array(date('Y-m-d', $date), $holidays)){
            $date = strtotime(date('Y-m-d',$date).' +1 day');
        }
    }
    return date('Y-m-d', $date);
}

//Also add in the code from Tony and James Pasta to handle holidays...

function getNationalAmericanHolidays($year) {
$bankHolidays = array(
    'New Years Day' => $year . "-01-01",
    'Martin Luther King Jr Birthday' => "". date("Y-m-d",strtotime("third Monday of January " . $year) ),
    'Washingtons Birthday' => "". date("Y-m-d",strtotime("third Monday of February " . $year) ),
    'Memorial Day' => "". date("Y-m-d",strtotime("last Monday of May " . $year) ),
    'Independance Day' => $year . "-07-04",
    'Labor Day' => "". date("Y-m-d",strtotime("first Monday of September " . $year) ),
    'Columbus Day' => "". date("Y-m-d",strtotime("second Monday of October " . $year) ),
    'Veterans Day' => $year . "-11-11",
    'Thanksgiving Day' => "". date("Y-m-d",strtotime("fourth Thursday of November " . $year) ),
    'Christmas Day' => $year . "-12-25"
);
return $bankHolidays;

}

//Now to call it... since we're working with business days, we should
//also be working with business hours so check if it's after 5 PM
//and go to the next day if necessary.

//Go to next day if after 5 pm (5 pm = 17)
if (date(G) >= 17) {
    $start_date = date("Y-m-d", strtotime("+ 1 day")); //Tomorrow
} else {
    $start_date = date("Y-m-d"); //Today
}

//Get the holidays for the current year and also for the next year
$this_year = getNationalAmericanHolidays(date('Y'));
$next_year = getNationalAmericanHolidays(date('Y', strtotime("+12 months")));
$holidays = array_merge($this_year, $next_year);

//The number of days to count
$days_count = 10;

echo count_business_days($start_date, $days_count, $holidays);

?>

Получите количество рабочие дни без выходных между двумя датами:

Пример использования:

echo number_of_working_days('2013-12-23', '2013-12-29');

Выход:

3

Функция:

function number_of_working_days($from, $to) {
    $workingDays = [1, 2, 3, 4, 5]; # date format = N (1 = Monday, ...)
    $holidayDays = ['*-12-25', '*-01-01', '2013-12-23']; # variable and fixed holidays

    $from = new DateTime($from);
    $to = new DateTime($to);
    $to->modify('+1 day');
    $interval = new DateInterval('P1D');
    $periods = new DatePeriod($from, $interval, $to);

    $days = 0;
    foreach ($periods as $period) {
        if (!in_array($period->format('N'), $workingDays)) continue;
        if (in_array($period->format('Y-m-d'), $holidayDays)) continue;
        if (in_array($period->format('*-m-d'), $holidayDays)) continue;
        $days++;
    }
    return $days;
}

Я проголосовал за, потому что он работает и имеет несколько значительных улучшений :) Но вы действительно должны хотя бы упомянуть @Suresh Kamrushi, который опубликовал большую часть этого кода месяцем ранее. :)

KOGI 20.01.2015 23:37

Если рабочие дни всегда с понедельника по пятницу, вы можете заменить массив DateInterval и рабочих дней на $interval = DateInterval::createFromFormat('1 weekday');. Я также рекомендую использовать $holidays = array_flip($holidays); перед foreach и if isset($holidays[$period->format('Y-m-d')]);, чтобы уменьшить время обработки, необходимое для каждой итерации. Но рекомендуется создать настраиваемую функцию для праздников, чтобы иметь возможность обрабатывать относительные праздники, такие как День благодарения last thursday of november или день труда first monday of september.

Will B. 26.02.2015 00:09

Можно ли добавить сюда Страстную пятницу (которую я, кажется, помню, это пятница перед первым воскресеньем после первого полнолуния после 21 марта)?

Damian Yerrick 21.07.2015 19:45

Помните о разных часовых поясах. Здесь с Европой / Берлином это не сработает. Я просто делаю это по метке времени сейчас с датой ('N') и добавляю 1 день и его часовой пояс независимо.

user3655829 10.07.2019 13:38

Лично я считаю, что это более ясное и лаконичное решение:

function onlyWorkDays( $d ) {
    $holidays = array('2013-12-25','2013-12-31','2014-01-01','2014-01-20','2014-02-17','2014-05-26','2014-07-04','2014-09-01','2014-10-13','2014-11-11','2014-11-27','2014-12-25','2014-12-31');
    while (in_array($d->format("Y-m-d"), $holidays)) { // HOLIDAYS
        $d->sub(new DateInterval("P1D"));
    }
    if ($d->format("w") == 6) { // SATURDAY
        $d->sub(new DateInterval("P1D"));
    }
    if ($d->format("w") == 0) { // SUNDAY
        $d->sub(new DateInterval("P2D"));
    }
    return $d;
}

Просто отправьте этой функции предложенную дату new.

Я только что создал эту функцию, которая, кажется, работает очень хорошо:

function getBusinessDays($date1, $date2){

    if (!is_numeric($date1)){
        $date1 = strtotime($date1);
    }

    if (!is_numeric($date2)){
        $date2 = strtotime($date2);
    }

    if ($date2 < $date1){
        $temp_date = $date1;
        $date1 = $date2;
        $date2 = $temp_date;
        unset($temp_date);
    }

    $diff = $date2 - $date1;

    $days_diff = intval($diff / (3600 * 24));
    $current_day_of_week = intval(date("N", $date1));
    $business_days = 0;

    for($i = 1; $i <= $days_diff; $i++){
        if (!in_array($current_day_of_week, array("Sunday" => 1, "Saturday" => 7))){
            $business_days++;
        }

        $current_day_of_week++;
        if ($current_day_of_week > 7){
            $current_day_of_week = 1;
        }
    }

    return $business_days;
}

echo "Business days: " . getBusinessDays("8/15/2014", "8/8/2014");
$startDate = new DateTime( '2013-04-01' );    //intialize start date
$endDate = new DateTime( '2013-04-30' );    //initialize end date
$holiday = array('2013-04-11','2013-04-25');  //this is assumed list of holiday
$interval = new DateInterval('P1D');    // set the interval as 1 day
$daterange = new DatePeriod($startDate, $interval ,$endDate);
foreach($daterange as $date){
if ($date->format("N") <6 AND !in_array($date->format("Y-m-d"),$holiday))
$result[] = $date->format("Y-m-d");
}
echo "<pre>";print_r($result);

У PHPClasses есть хороший класс для этого под названием Рабочие дни PHP. Вы можете проверить этот класс.

рассчитывать рабочие дни между двумя датами, включая праздники и индивидуальную рабочую неделю

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

  1. необходимо указать, в какие дни недели вы будете работать (по умолчанию ПН-ПТ) - класс позволяет включать или отключать каждый день недели индивидуально.
  2. нужно знать, что нужно учитывать праздничные дни (страна и штат), чтобы быть точным

Функциональный подход

/**
 * @param days, int
 * @param $format, string: dateformat (if format defined OTHERWISE int: timestamp) 
 * @param start, int: timestamp (mktime) default: time() //now
 * @param $wk, bit[]: flags for each workday (0=SUN, 6=SAT) 1=workday, 0=day off
 * @param $holiday, string[]: list of dates, YYYY-MM-DD, MM-DD 
 */
function working_days($days, $format='', $start=null, $week=[0,1,1,1,1,1,0], $holiday=[])
{
    if (is_null($start)) $start = time();
    if ($days <= 0) return $start;
    if (count($week) != 7) trigger_error('workweek must contain bit-flags for 7 days');
    if (array_sum($week) == 0) trigger_error('workweek must contain at least one workday');
    $wd = date('w', $start);//0=sun, 6=sat
    $time = $start;
    while($days)
    {
        if (
        $week[$wd]
        && !in_array(date('Y-m-d', $time), $holiday)
        && !in_array(date('m-d', $time), $holiday)
        ) --$days; //decrement on workdays
        $wd = date('w', $time += 86400); //add one day in seconds
    }
    $time -= 86400;//include today
    return $format ? date($format, $time): $time;
}

//simple usage
$ten_days = working_days(10, 'D F d Y');
echo '<br>ten workingdays (MON-FRI) disregarding holidays: ',$ten_days;

//work on saturdays and add new years day as holiday
$ten_days = working_days(10, 'D F d Y', null, [0,1,1,1,1,1,1], ['01-01']);
echo '<br>ten workingdays (MON-SAT) disregarding holidays: ',$ten_days;

Вы можете попробовать эту более простую функцию.

function getWorkingDays($startDate, $endDate)
{
    $begin = strtotime($startDate);
    $end   = strtotime($endDate);
    if ($begin > $end) {

        return 0;
    } else {
        $no_days  = 0;
        while ($begin <= $end) {
            $what_day = date("N", $begin);
            if (!in_array($what_day, [6,7]) ) // 6 and 7 are weekend
                $no_days++;
            $begin += 86400; // +1 day
        };

        return $no_days;
    }
}

Ниже приведен рабочий код для расчета рабочих дней с заданной даты.

<?php
$holiday_date_array = array("2016-01-26", "2016-03-07", "2016-03-24", "2016-03-25", "2016-04-15", "2016-08-15", "2016-09-12", "2016-10-11", "2016-10-31");
$date_required = "2016-03-01";

function increase_date($date_required, $holiday_date_array=array(), $days = 15){
    if (!empty($date_required)){
        $counter_1=0;
        $incremented_date = '';
        for($i=1; $i <= $days; $i++){
            $date = strtotime("+$i day", strtotime($date_required));
            $day_name = date("D", $date);
            $incremented_date = date("Y-m-d", $date);
            if ($day_name=='Sat'||$day_name=='Sun'|| in_array($incremented_date ,$holiday_date_array)==true){
                $counter_1+=1;
            }
        }
        if ($counter_1 > 0){
            return increase_date($incremented_date, $holiday_date_array, $counter_1);
        }else{
            return $incremented_date;
        }
    }else{
        return 'invalid';
    }
}

echo increase_date($date_required, $holiday_date_array, 15);
?>

//output after adding 15 business working days in 2016-03-01 will be "2016-03-23"

Это еще одно решение, оно почти на 25% быстрее, чем проверка праздников с помощью in_array:

/**
 * Function to calculate the working days between two days, considering holidays.
 * @param string $startDate -- Start date of the range (included), formatted as Y-m-d.
 * @param string $endDate -- End date of the range (included), formatted as Y-m-d.
 * @param array(string) $holidayDates -- OPTIONAL. Array of holidays dates, formatted as Y-m-d. (e.g. array("2016-08-15", "2016-12-25"))
 * @return int -- Number of working days.
 */
function getWorkingDays($startDate, $endDate, $holidayDates=array()){
    $dateRange = new DatePeriod(new DateTime($startDate), new DateInterval('P1D'), (new DateTime($endDate))->modify("+1day"));
    foreach ($dateRange as $dr) { if ($dr->format("N")<6){$workingDays[]=$dr->format("Y-m-d");} }
    return count(array_diff($workingDays, $holidayDates));
}

Я знаю, что опаздываю на вечеринку, но я использую этот старый набор функций от Маркоса Дж. Монтеса для определения праздников и рабочих дней. Он нашел время, чтобы добавить алгоритм из 1876 года для Пасхи, и он добавил все главные праздники США. Это можно легко обновить для других стран.

//Usage
$days = 30;
$next_working_date = nextWorkingDay($days, $somedate);

//add date function
function DateAdd($interval, $number, $date) {

    $date_time_array = getdate($date);
    //die(print_r($date_time_array));

    $hours = $date_time_array["hours"];
    $minutes = $date_time_array["minutes"];
    $seconds = $date_time_array["seconds"];
    $month = $date_time_array["mon"];
    $day = $date_time_array["mday"];
    $year = $date_time_array["year"];

    switch ($interval) {

        case "yyyy":
            $year+=$number;
            break;
        case "q":
            $year+=($number*3);
            break;
        case "m":
            $month+=$number;
            break;
        case "y":
        case "d":
        case "w":
            $day+=$number;
            break;
        case "ww":
            $day+=($number*7);
            break;
        case "h":
            $hours+=$number;
            break;
        case "n":
            $minutes+=$number;
            break;
        case "s":
            $seconds+=$number; 
            break;            
    }
    //      echo "day:" . $day;
    $timestamp= mktime($hours,$minutes,$seconds,$month,$day,$year);
    return $timestamp;
}

// the following function get_holiday() is based on the work done by
// Marcos J. Montes
function get_holiday($year, $month, $day_of_week, $week = "") {
    if ( (($week != "") && (($week > 5) || ($week < 1))) || ($day_of_week > 6) || ($day_of_week < 0) ) {
        // $day_of_week must be between 0 and 6 (Sun=0, ... Sat=6); $week must be between 1 and 5
        return FALSE;
    } else {
        if (!$week || ($week == "")) {
            $lastday = date("t", mktime(0,0,0,$month,1,$year));
            $temp = (date("w",mktime(0,0,0,$month,$lastday,$year)) - $day_of_week) % 7;
        } else {
            $temp = ($day_of_week - date("w",mktime(0,0,0,$month,1,$year))) % 7;
        }

        if ($temp < 0) {
            $temp += 7;
        }

        if (!$week || ($week == "")) {
            $day = $lastday - $temp;
        } else {
            $day = (7 * $week) - 6 + $temp;
        }
        //echo $year.", ".$month.", ".$day . "<br><br>";
        return format_date($year, $month, $day);
    }
}

function observed_day($year, $month, $day) {
    // sat -> fri & sun -> mon, any exceptions?
    //
    // should check $lastday for bumping forward and $firstday for bumping back,
    // although New Year's & Easter look to be the only holidays that potentially
    // move to a different month, and both are accounted for.

    $dow = date("w", mktime(0, 0, 0, $month, $day, $year));

    if ($dow == 0) {
        $dow = $day + 1;
    } elseif ($dow == 6) {
        if (($month == 1) && ($day == 1)) {    // New Year's on a Saturday
            $year--;
            $month = 12;
            $dow = 31;
        } else {
            $dow = $day - 1;
        }
    } else {
        $dow = $day;
    }

    return format_date($year, $month, $dow);
}

function calculate_easter($y) {
    // In the text below, 'intval($var1/$var2)' represents an integer division neglecting
    // the remainder, while % is division keeping only the remainder. So 30/7=4, and 30%7=2
//
    // This algorithm is from Practical Astronomy With Your Calculator, 2nd Edition by Peter
    // Duffett-Smith. It was originally from Butcher's Ecclesiastical Calendar, published in
    // 1876. This algorithm has also been published in the 1922 book General Astronomy by
    // Spencer Jones; in The Journal of the British Astronomical Association (Vol.88, page
    // 91, December 1977); and in Astronomical Algorithms (1991) by Jean Meeus. 

    $a = $y%19;
    $b = intval($y/100);
    $c = $y%100;
    $d = intval($b/4);
    $e = $b%4;
    $f = intval(($b+8)/25);
    $g = intval(($b-$f+1)/3);
    $h = (19*$a+$b-$d-$g+15)%30;
    $i = intval($c/4);
    $k = $c%4;
    $l = (32+2*$e+2*$i-$h-$k)%7;
    $m = intval(($a+11*$h+22*$l)/451);
    $p = ($h+$l-7*$m+114)%31;
    $EasterMonth = intval(($h+$l-7*$m+114)/31);    // [3 = March, 4 = April]
    $EasterDay = $p+1;    // (day in Easter Month)

    return format_date($y, $EasterMonth, $EasterDay);
}


function nextWorkingDay($number_days, $start_date = "") {
    $day_counter = 0;
    $intCounter = 0;    

    if ($start_date= = "") {
        $today  = mktime(0, 0, 0, date("m")  , date("d"), date("Y"));
    } else {
        $start_time = strtotime($start_date);
        $today  = mktime(0, 0, 0, date("m", $start_time)  , date("d", $start_time), date("Y", $start_time));
    }

    while($day_counter < $number_days) {
        $working_time = DateAdd("d", 1, $today);
        $working_date = date("Y-m-d", $working_date);
        if (!isWeekend($working_date) && !confirm_holiday(date("Y-m-d", strtotime($working_date))) ) {
            $day_counter++;
        }
        $intCounter++;
        $today  = $working_time;
        if ($intCounter > 1000) {
            //just in case out of control?
            break;
        }
    }

    return $working_date;
}
function isWeekend($check_date) {
    return (date("N",  strtotime($check_date)) > 5);
}
function confirm_holiday($somedate = "") {
    if ($somedate= = "") {
        $somedate = date("Y-m-d");
    }
    $year = date("Y", strtotime($somedate));
    $blnHoliday = false;
    //newyears
    if ($somedate == observed_day($year, 1, 1)) {
        $blnHoliday = true;
    }
    if ($somedate == format_date($year, 1, 1)) {
        $blnHoliday = true;
    }
    if ($somedate == format_date($year, 12, 31)) {
        $blnHoliday = true;
    }
    //Martin Luther King
    if ($somedate == get_holiday($year, 1, 1, 3)) {
        $blnHoliday = true;
    }
    //President's
    if ($somedate == get_holiday($year, 2, 1, 3)) {
        $blnHoliday = true;
    }
    //easter
    if ($somedate == calculate_easter($year)) {
        $blnHoliday = true;
    }
    //Memorial
    if ($somedate == get_holiday($year, 5, 1)) {
        $blnHoliday = true;
    }
    //july4
    if ($somedate == observed_day($year, 7, 4)) {
        $blnHoliday = true;
    }
    //labor
    if ($somedate == get_holiday($year, 9, 1, 1)) {
        $blnHoliday = true;
    }
    //columbus
    if ($somedate == get_holiday($year, 10, 1, 2)) {
        $blnHoliday = true;
    }
    //thanks
    if ($somedate == get_holiday($year, 11, 4, 4)) {
        $blnHoliday = true;
    }
    //xmas
    if ($somedate == format_date($year, 12, 24)) {
        $blnHoliday = true;
    }
    if ($somedate == format_date($year, 12, 25)) {
        $blnHoliday = true;
    }
    return $blnHoliday;
}

Этот фрагмент кода очень легко рассчитать рабочий день без выходных и праздников:

function getWorkingDays($startDate,$endDate,$offdays,$holidays){
$endDate = strtotime($endDate);
$startDate = strtotime($startDate);
$days = ($endDate - $startDate) / 86400 + 1;
$counter=0;
for ($i = 1; $i <= $days; $i++) {
    $the_first_day_of_week = date("N", $startDate);
    $startDate+=86400;
if (!in_array($the_first_day_of_week, $offdays) && !in_array(date("Y-m-
d",$startDate), $holidays)) {
$counter++;
}

}   
return $counter;
}
//example to use
$holidays=array("2017-07-03","2017-07-20");
$offdays=array(5,6);//weekend days Monday=1 .... Sunday=7
echo getWorkingDays("2017-01-01","2017-12-31",$offdays,$holidays)

Вот еще одно решение без цикла на каждый день.

$from = new DateTime($first_date);
$to = new DateTime($second_date);

$to->modify('+1 day');
$interval = $from->diff($to);
$days = $interval->format('%a');

$extra_days = fmod($days, 7);
$workdays = ( ( $days - $extra_days ) / 7 ) * 5;

$first_day = date('N', strtotime($first_date));
$last_day = date('N', strtotime("1 day", strtotime($second_date)));
$extra = 0;
if ($first_day > $last_day) {
   if ($first_day == 7) {
       $first_day = 6;
   }

   $extra = (6 - $first_day) + ($last_day - 1);
   if ($extra < 0) {
       $extra = $extra * -1;
   }
}
if ($last_day > $first_day) {
    $extra = $last_day - $first_day;
}
$days = $workdays + $extra

Если вам нужно узнать, сколько времени прошло между двумя датами, вы можете использовать https://github.com/maximnara/business-days-counter. Работает просто, но теперь только с laravel $diffInSeconds = $this->datesCounter->getDifferenceInSeconds(Carbon::create(2019, 1, 1), Carbon::now(), DateCounter::COUNTRY_FR);

Он не учитывает праздничные и выходные дни, и вы можете установить рабочий интервал, например, с 9 до 18 с часом запуска или без.

Или, если вам нужны только выходные и вы используете Carbon, вы можете использовать встроенную функцию:

$date1 = Carbon::create(2019, 1, 1)->endOfDay();
$date2 = $dt->copy()->startOfDay();
$diff = $date1->diffFiltered(CarbonInterval::minute(), function(Carbon $date) {
   return !$date->isWeekend();
}, $date2, true);

Но он будет выполнять каждую минуту в интервале, для битовых интервалов это может занять некоторое время.

Я подумал, что вы можете включить список праздников в Великобритании, поэтому вот что:

<?php

class ProductionDays
{

    private function getHolidays()
    {
        //from https://www.gov.uk/bank-holidays.json
        $holidaysData = '{"england-and-wales":{"division":"england-and-wales","events":[{"title":"New Year’s Day","date":"2015-01-01","notes":"","bunting":true},{"title":"Good Friday","date":"2015-04-03","notes":"","bunting":false},{"title":"Easter Monday","date":"2015-04-06","notes":"","bunting":true},{"title":"Early May bank holiday","date":"2015-05-04","notes":"","bunting":true},{"title":"Spring bank holiday","date":"2015-05-25","notes":"","bunting":true},{"title":"Summer bank holiday","date":"2015-08-31","notes":"","bunting":true},{"title":"Christmas Day","date":"2015-12-25","notes":"","bunting":true},{"title":"Boxing Day","date":"2015-12-28","notes":"Substitute day","bunting":true},{"title":"New Year’s Day","date":"2016-01-01","notes":"","bunting":true},{"title":"Good Friday","date":"2016-03-25","notes":"","bunting":false},{"title":"Easter Monday","date":"2016-03-28","notes":"","bunting":true},{"title":"Early May bank holiday","date":"2016-05-02","notes":"","bunting":true},{"title":"Spring bank holiday","date":"2016-05-30","notes":"","bunting":true},{"title":"Summer bank holiday","date":"2016-08-29","notes":"","bunting":true},{"title":"Boxing Day","date":"2016-12-26","notes":"","bunting":true},{"title":"Christmas Day","date":"2016-12-27","notes":"Substitute day","bunting":true},{"title":"New Year’s Day","date":"2017-01-02","notes":"Substitute day","bunting":true},{"title":"Good Friday","date":"2017-04-14","notes":"","bunting":false},{"title":"Easter Monday","date":"2017-04-17","notes":"","bunting":true},{"title":"Early May bank holiday","date":"2017-05-01","notes":"","bunting":true},{"title":"Spring bank holiday","date":"2017-05-29","notes":"","bunting":true},{"title":"Summer bank holiday","date":"2017-08-28","notes":"","bunting":true},{"title":"Christmas Day","date":"2017-12-25","notes":"","bunting":true},{"title":"Boxing Day","date":"2017-12-26","notes":"","bunting":true},{"title":"New Year’s Day","date":"2018-01-01","notes":"","bunting":true},{"title":"Good Friday","date":"2018-03-30","notes":"","bunting":false},{"title":"Easter Monday","date":"2018-04-02","notes":"","bunting":true},{"title":"Early May bank holiday","date":"2018-05-07","notes":"","bunting":true},{"title":"Spring bank holiday","date":"2018-05-28","notes":"","bunting":true},{"title":"Summer bank holiday","date":"2018-08-27","notes":"","bunting":true},{"title":"Christmas Day","date":"2018-12-25","notes":"","bunting":true},{"title":"Boxing Day","date":"2018-12-26","notes":"","bunting":true},{"title":"New Year’s Day","date":"2019-01-01","notes":"","bunting":true},{"title":"Good Friday","date":"2019-04-19","notes":"","bunting":false},{"title":"Easter Monday","date":"2019-04-22","notes":"","bunting":true},{"title":"Early May bank holiday","date":"2019-05-06","notes":"","bunting":true},{"title":"Spring bank holiday","date":"2019-05-27","notes":"","bunting":true},{"title":"Summer bank holiday","date":"2019-08-26","notes":"","bunting":true},{"title":"Christmas Day","date":"2019-12-25","notes":"","bunting":true},{"title":"Boxing Day","date":"2019-12-26","notes":"","bunting":true},{"title":"New Year’s Day","date":"2020-01-01","notes":"","bunting":true},{"title":"Good Friday","date":"2020-04-10","notes":"","bunting":false},{"title":"Easter Monday","date":"2020-04-13","notes":"","bunting":true},{"title":"Early May bank holiday (VE day)","date":"2020-05-08","notes":"","bunting":true},{"title":"Spring bank holiday","date":"2020-05-25","notes":"","bunting":true},{"title":"Summer bank holiday","date":"2020-08-31","notes":"","bunting":true},{"title":"Christmas Day","date":"2020-12-25","notes":"","bunting":true},{"title":"Boxing Day","date":"2020-12-28","notes":"Substitute day","bunting":true},{"title":"New Year’s Day","date":"2021-01-01","notes":"","bunting":true},{"title":"Good Friday","date":"2021-04-02","notes":"","bunting":false},{"title":"Easter Monday","date":"2021-04-05","notes":"","bunting":true},{"title":"Early May bank holiday","date":"2021-05-03","notes":"","bunting":true},{"title":"Spring bank holiday","date":"2021-05-31","notes":"","bunting":true},{"title":"Summer bank holiday","date":"2021-08-30","notes":"","bunting":true},{"title":"Christmas Day","date":"2021-12-27","notes":"Substitute day","bunting":true},{"title":"Boxing Day","date":"2021-12-28","notes":"Substitute day","bunting":true}]},"scotland":{"division":"scotland","events":[{"title":"New Year’s Day","date":"2015-01-01","notes":"","bunting":true},{"title":"2nd January","date":"2015-01-02","notes":"","bunting":true},{"title":"Good Friday","date":"2015-04-03","notes":"","bunting":false},{"title":"Early May bank holiday","date":"2015-05-04","notes":"","bunting":true},{"title":"Spring bank holiday","date":"2015-05-25","notes":"","bunting":true},{"title":"Summer bank holiday","date":"2015-08-03","notes":"","bunting":true},{"title":"St Andrew’s Day","date":"2015-11-30","notes":"","bunting":true},{"title":"Christmas Day","date":"2015-12-25","notes":"","bunting":true},{"title":"Boxing Day","date":"2015-12-28","notes":"Substitute day","bunting":true},{"title":"New Year’s Day","date":"2016-01-01","notes":"","bunting":true},{"title":"2nd January","date":"2016-01-04","notes":"Substitute day","bunting":true},{"title":"Good Friday","date":"2016-03-25","notes":"","bunting":false},{"title":"Early May bank holiday","date":"2016-05-02","notes":"","bunting":true},{"title":"Spring bank holiday","date":"2016-05-30","notes":"","bunting":true},{"title":"Summer bank holiday","date":"2016-08-01","notes":"","bunting":true},{"title":"St Andrew’s Day","date":"2016-11-30","notes":"","bunting":true},{"title":"Boxing Day","date":"2016-12-26","notes":"","bunting":true},{"title":"Christmas Day","date":"2016-12-27","notes":"Substitute day","bunting":true},{"title":"2nd January","date":"2017-01-02","notes":"","bunting":true},{"title":"New Year’s Day","date":"2017-01-03","notes":"Substitute day","bunting":true},{"title":"Good Friday","date":"2017-04-14","notes":"","bunting":false},{"title":"Early May bank holiday","date":"2017-05-01","notes":"","bunting":true},{"title":"Spring bank holiday","date":"2017-05-29","notes":"","bunting":true},{"title":"Summer bank holiday","date":"2017-08-07","notes":"","bunting":true},{"title":"St Andrew’s Day","date":"2017-11-30","notes":"","bunting":true},{"title":"Christmas Day","date":"2017-12-25","notes":"","bunting":true},{"title":"Boxing Day","date":"2017-12-26","notes":"","bunting":true},{"title":"New Year’s Day","date":"2018-01-01","notes":"","bunting":true},{"title":"2nd January","date":"2018-01-02","notes":"","bunting":true},{"title":"Good Friday","date":"2018-03-30","notes":"","bunting":false},{"title":"Early May bank holiday","date":"2018-05-07","notes":"","bunting":true},{"title":"Spring bank holiday","date":"2018-05-28","notes":"","bunting":true},{"title":"Summer bank holiday","date":"2018-08-06","notes":"","bunting":true},{"title":"St Andrew’s Day","date":"2018-11-30","notes":"","bunting":true},{"title":"Christmas Day","date":"2018-12-25","notes":"","bunting":true},{"title":"Boxing Day","date":"2018-12-26","notes":"","bunting":true},{"title":"New Year’s Day","date":"2019-01-01","notes":"","bunting":true},{"title":"2nd January","date":"2019-01-02","notes":"","bunting":true},{"title":"Good Friday","date":"2019-04-19","notes":"","bunting":false},{"title":"Early May bank holiday","date":"2019-05-06","notes":"","bunting":true},{"title":"Spring bank holiday","date":"2019-05-27","notes":"","bunting":true},{"title":"Summer bank holiday","date":"2019-08-05","notes":"","bunting":true},{"title":"St Andrew’s Day","date":"2019-12-02","notes":"Substitute day","bunting":true},{"title":"Christmas Day","date":"2019-12-25","notes":"","bunting":true},{"title":"Boxing Day","date":"2019-12-26","notes":"","bunting":true},{"title":"New Year’s Day","date":"2020-01-01","notes":"","bunting":true},{"title":"2nd January","date":"2020-01-02","notes":"","bunting":true},{"title":"Good Friday","date":"2020-04-10","notes":"","bunting":false},{"title":"Early May bank holiday (VE day)","date":"2020-05-08","notes":"","bunting":true},{"title":"Spring bank holiday","date":"2020-05-25","notes":"","bunting":true},{"title":"Summer bank holiday","date":"2020-08-03","notes":"","bunting":true},{"title":"St Andrew’s Day","date":"2020-11-30","notes":"","bunting":true},{"title":"Christmas Day","date":"2020-12-25","notes":"","bunting":true},{"title":"Boxing Day","date":"2020-12-28","notes":"Substitute day","bunting":true},{"title":"New Year’s Day","date":"2021-01-01","notes":"","bunting":true},{"title":"2nd January","date":"2021-01-04","notes":"Substitute day","bunting":true},{"title":"Good Friday","date":"2021-04-02","notes":"","bunting":false},{"title":"Early May bank holiday","date":"2021-05-03","notes":"","bunting":true},{"title":"Spring bank holiday","date":"2021-05-31","notes":"","bunting":true},{"title":"Summer bank holiday","date":"2021-08-02","notes":"","bunting":true},{"title":"St Andrew’s Day","date":"2021-11-30","notes":"","bunting":true},{"title":"Christmas Day","date":"2021-12-27","notes":"Substitute day","bunting":true},{"title":"Boxing Day","date":"2021-12-28","notes":"Substitute day","bunting":true}]},"northern-ireland":{"division":"northern-ireland","events":[{"title":"New Year’s Day","date":"2015-01-01","notes":"","bunting":true},{"title":"St Patrick’s Day","date":"2015-03-17","notes":"","bunting":true},{"title":"Good Friday","date":"2015-04-03","notes":"","bunting":false},{"title":"Easter Monday","date":"2015-04-06","notes":"","bunting":true},{"title":"Early May bank holiday","date":"2015-05-04","notes":"","bunting":true},{"title":"Spring bank holiday","date":"2015-05-25","notes":"","bunting":true},{"title":"Battle of the Boyne (Orangemen’s Day)","date":"2015-07-13","notes":"Substitute day","bunting":false},{"title":"Summer bank holiday","date":"2015-08-31","notes":"","bunting":true},{"title":"Christmas Day","date":"2015-12-25","notes":"","bunting":true},{"title":"Boxing Day","date":"2015-12-28","notes":"Substitute day","bunting":true},{"title":"New Year’s Day","date":"2016-01-01","notes":"","bunting":true},{"title":"St Patrick’s Day","date":"2016-03-17","notes":"","bunting":true},{"title":"Good Friday","date":"2016-03-25","notes":"","bunting":false},{"title":"Easter Monday","date":"2016-03-28","notes":"","bunting":true},{"title":"Early May bank holiday","date":"2016-05-02","notes":"","bunting":true},{"title":"Spring bank holiday","date":"2016-05-30","notes":"","bunting":true},{"title":"Battle of the Boyne (Orangemen’s Day)","date":"2016-07-12","notes":"","bunting":false},{"title":"Summer bank holiday","date":"2016-08-29","notes":"","bunting":true},{"title":"Boxing Day","date":"2016-12-26","notes":"","bunting":true},{"title":"Christmas Day","date":"2016-12-27","notes":"Substitute day","bunting":true},{"title":"New Year’s Day","date":"2017-01-02","notes":"Substitute day","bunting":true},{"title":"St Patrick’s Day","date":"2017-03-17","notes":"","bunting":true},{"title":"Good Friday","date":"2017-04-14","notes":"","bunting":false},{"title":"Easter Monday","date":"2017-04-17","notes":"","bunting":true},{"title":"Early May bank holiday","date":"2017-05-01","notes":"","bunting":true},{"title":"Spring bank holiday","date":"2017-05-29","notes":"","bunting":true},{"title":"Battle of the Boyne (Orangemen’s Day)","date":"2017-07-12","notes":"","bunting":false},{"title":"Summer bank holiday","date":"2017-08-28","notes":"","bunting":true},{"title":"Christmas Day","date":"2017-12-25","notes":"","bunting":true},{"title":"Boxing Day","date":"2017-12-26","notes":"","bunting":true},{"title":"New Year’s Day","date":"2018-01-01","notes":"","bunting":true},{"title":"St Patrick’s Day","date":"2018-03-19","notes":"Substitute day","bunting":true},{"title":"Good Friday","date":"2018-03-30","notes":"","bunting":false},{"title":"Easter Monday","date":"2018-04-02","notes":"","bunting":true},{"title":"Early May bank holiday","date":"2018-05-07","notes":"","bunting":true},{"title":"Spring bank holiday","date":"2018-05-28","notes":"","bunting":true},{"title":"Battle of the Boyne (Orangemen’s Day)","date":"2018-07-12","notes":"","bunting":false},{"title":"Summer bank holiday","date":"2018-08-27","notes":"","bunting":true},{"title":"Christmas Day","date":"2018-12-25","notes":"","bunting":true},{"title":"Boxing Day","date":"2018-12-26","notes":"","bunting":true},{"title":"New Year’s Day","date":"2019-01-01","notes":"","bunting":true},{"title":"St Patrick’s Day","date":"2019-03-18","notes":"Substitute day","bunting":true},{"title":"Good Friday","date":"2019-04-19","notes":"","bunting":false},{"title":"Easter Monday","date":"2019-04-22","notes":"","bunting":true},{"title":"Early May bank holiday","date":"2019-05-06","notes":"","bunting":true},{"title":"Spring bank holiday","date":"2019-05-27","notes":"","bunting":true},{"title":"Battle of the Boyne (Orangemen’s Day)","date":"2019-07-12","notes":"","bunting":false},{"title":"Summer bank holiday","date":"2019-08-26","notes":"","bunting":true},{"title":"Christmas Day","date":"2019-12-25","notes":"","bunting":true},{"title":"Boxing Day","date":"2019-12-26","notes":"","bunting":true},{"title":"New Year’s Day","date":"2020-01-01","notes":"","bunting":true},{"title":"St Patrick’s Day","date":"2020-03-17","notes":"","bunting":true},{"title":"Good Friday","date":"2020-04-10","notes":"","bunting":false},{"title":"Easter Monday","date":"2020-04-13","notes":"","bunting":true},{"title":"Early May bank holiday (VE day)","date":"2020-05-08","notes":"","bunting":true},{"title":"Spring bank holiday","date":"2020-05-25","notes":"","bunting":true},{"title":"Battle of the Boyne (Orangemen’s Day)","date":"2020-07-13","notes":"Substitute day","bunting":false},{"title":"Summer bank holiday","date":"2020-08-31","notes":"","bunting":true},{"title":"Christmas Day","date":"2020-12-25","notes":"","bunting":true},{"title":"Boxing Day","date":"2020-12-28","notes":"Substitute day","bunting":true},{"title":"New Year’s Day","date":"2021-01-01","notes":"","bunting":true},{"title":"St Patrick’s Day","date":"2021-03-17","notes":"","bunting":true},{"title":"Good Friday","date":"2021-04-02","notes":"","bunting":false},{"title":"Easter Monday","date":"2021-04-05","notes":"","bunting":true},{"title":"Early May bank holiday","date":"2021-05-03","notes":"","bunting":true},{"title":"Spring bank holiday","date":"2021-05-31","notes":"","bunting":true},{"title":"Battle of the Boyne (Orangemen’s Day)","date":"2021-07-12","notes":"","bunting":false},{"title":"Summer bank holiday","date":"2021-08-30","notes":"","bunting":true},{"title":"Christmas Day","date":"2021-12-27","notes":"Substitute day","bunting":true},{"title":"Boxing Day","date":"2021-12-28","notes":"Substitute day","bunting":true}]}}';

        $holidaysArray = json_decode($holidaysData, true);
        $holidays = [];
        foreach ($holidaysArray["england-and-wales"]['events'] as $event) {
            $holidays[] = $event['date'];
        }
        return $holidays;
    }

    public function getWorkingDays($dateFrom, $dateTo)
    {
        $holidays = $this->getHolidays();
        $startDate = new DateTime($dateFrom);
        $endDate = new DateTime($dateTo);
        $interval = new DateInterval('P1D');
        $dateRange = new DatePeriod($startDate, $interval, $endDate);
        $results = [];
        foreach ($dateRange as $date) {
            if ($date->format('N') < 6 && !in_array($date->format('Y-m-d'), $holidays)) {
                $results[] = $date->format("Y-m-d");
            }
        }
        return $results;
    }

}

$dateFrom = '01-01-2020';
$dateTo = '31-01-2020';

$productionDays = new ProductionDays();
$getWorkingDays = $productionDays->getWorkingDays($dateFrom, $dateTo);

echo json_encode($getWorkingDays);

Хотя на момент написания на этот вопрос было более 35 ответов, есть лучшее решение этой проблемы.

@flamingLogos предоставил ответ, основанный на математике и не содержащий никаких циклов. Это решение обеспечивает временную сложность Θ(1). Однако математика, стоящая за этим, довольно сложна, особенно обработка високосного года.

@ Glavić предоставил хорошее решение, минималистичное и элегантное. Но он не выполняет расчет в постоянное время, поэтому он может вызвать атаку отказа в обслуживании (DOS) или, по крайней мере, тайм-аут, если используется с большими периодами, такими как 10 или 100 лет, поскольку он зацикливается с интервалом в 1 день.

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

Идея состоит в том, чтобы подсчитать, сколько дней нужно иметь полные недели.

<?php
function getWorkingHours($start_date, $end_date) {
    // validate input
    if (!validateDate($start_date) || !validateDate($end_date)) return ['error' => 'Invalid Date'];
    if ($end_date < $start_date) return ['error' => 'End date must be greater than or equal Start date'];

    //We save timezone and switch to UTC to prevent issues
    $old_timezone = date_default_timezone_get();
    date_default_timezone_set("UTC");

    $startDate = strtotime($start_date);
    $endDate = strtotime($end_date);

    //The total number of days between the two dates. We compute the no. of seconds and divide it to 60*60*24
    //We add one to include both dates in the interval.
    $days = ($endDate - $startDate) / 86400 + 1;
    $no_full_weeks = ceil($days / 7);
    //we get only missing days count to complete full weeks
    //we take modulo 7 in case it was already full weeks
    $no_of_missing_days = (7 - ($days % 7)) % 7;

    $workingDays = $no_full_weeks * 5;
    //Next we remove the working days we added, this loop will have max of 6 iterations.
    for ($i = 1; $i <= $no_of_missing_days; $i++){
        if (date('N', $endDate + $i * 86400) < 6) $workingDays--;
    }

    $holidays = getHolidays(date('Y', $startDate), date('Y', $endDate));

    //We subtract the holidays
    foreach($holidays as $holiday){
        $time_stamp=strtotime($holiday);
        //If the holiday doesn't fall in weekend
        if ($startDate <= $time_stamp && $time_stamp <= $endDate && date("N",$time_stamp) != 6 && date("N",$time_stamp) != 7)
            $workingDays--;
    }

    date_default_timezone_set($old_timezone);
    return ['days' => $workingDays];

}

Входные данные функции находятся в формате Y-m-d в php или yyyy-mm-dd в общем формате даты.

Функция get holiday вернет массив дат праздников, начиная с года начала и до года окончания.

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