Calcular dias úteis

103

Preciso de um método para adicionar "dias úteis" em PHP. Por exemplo, sexta-feira 05/12 + 3 dias úteis = quarta-feira, 10/12.

Preciso, no mínimo, do código para entender os fins de semana, mas o ideal é que ele também leve em consideração os feriados federais dos EUA. Tenho certeza de que poderia chegar a uma solução pela força bruta, se necessário, mas espero que haja uma abordagem mais elegante por aí. Qualquer um?

Obrigado.

AdamTheHutt
fonte
Criei uma biblioteca decente para isso. github.com/andrejsstepanovs/business-days-calculator É estável e pronto para entrar em produção.
wormhit
6
Ah, acho que devemos mencionar que, hoje em dia, podemos usar a função DateTime :: modify para adicionar dias da semana imediatamente: $ my_date = new \ DateTime (); $ minha_data-> modificar ("+ 7 dias da semana"); terá um desempenho perfeito.
mika,
Um blog detalhado: goo.gl/YOsfPX
Suresh Kamrushi
Uma resposta mais simples / limpa: stackoverflow.com/questions/5532002/…
Dave

Respostas:

103

Aqui está uma função dos comentários do usuário na página de função date () no manual do PHP. É uma melhoria de uma função anterior nos comentários que adiciona suporte para anos bissextos.

Insira as datas de início e término, junto com uma matriz de quaisquer feriados que possam estar entre os dois, e ele retorna os dias úteis como um número inteiro:

<?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
?>
flamingLogos
fonte
4
esta função espera uma data de início e de término. E se você tiver uma data de início e quiser que o resultado seja x dias úteis a partir de determinada data?
mcgrailm
@mcgrailm: É uma ideia semelhante, mas você provavelmente gostaria de escrever uma segunda função porque os argumentos e valores de retorno são trocados. Seria algo como ((X dias% 5 dias por semana) * 2 dias por fim de semana) + X dias + diferença do dia da semana das datas de início e término + feriados).
flamingLogos
@mcgrailm: Acabei de encontrar esta pergunta - suas respostas podem apontar a direção certa: stackoverflow.com/questions/2681787/… .
flamingLogos
1
Existe um bug nesta função. E se houver uma mudança de fuso horário entre essas datas? Por exemplo, para CEST: echo getWorkingDays ("2012-01-01", "2012-05-01", $ feriados); não vai te dar um número inteiro. stackoverflow.com/questions/12490521/…
mnowotka
5
Essa é a maneira antiga de fazer isso. Use a resposta de Glavić se possível
Thomas Ruiz
90

Obtenha o número de dias úteis sem feriados entre duas datas:

Use o exemplo:

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

Resultado:

3

Função:

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;
}
Glavić
fonte
Eu votei a favor porque funciona e tem algumas melhorias ótimas :) Mas você realmente deveria pelo menos mencionar @Suresh Kamrushi, que postou a maior parte deste código um mês antes. :)
KOGI
Se os dias úteis são sempre de segunda a sexta-feira, você pode substituir o array DateInterval e workingdays por $interval = DateInterval::createFromFormat('1 weekday');Eu também recomendo usar $holidays = array_flip($holidays);before the foreach e if isset($holidays[$period->format('Y-m-d')]);para reduzir o tempo de processamento necessário por iteração. Mas recomendamos a criação de uma função personalizada para feriados para poder processar feriados relativos, como ação de graças last thursday of novemberou dia de trabalhofirst monday of september
fyrye
3
Isso pode ser expandido para incluir a Sexta-feira Santa (que eu me lembro é a sexta-feira antes do primeiro domingo após a primeira lua cheia após 21 de março)?
Damian Yerrick
Esteja ciente dos diferentes fusos horários. Aqui com Europa / Berlim isso não funcionará. Acabei de fazer isso no carimbo de data / hora agora com data ('N') e adicionando 1 dia e seu fuso horário independente.
user3655829
12

Existem alguns argumentos para a função date () que devem ajudar. Se você marcar a data ("w"), receberá um número para o dia da semana, de 0 para domingo a 6 para sábado. Então .. talvez algo como ..

$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;

Este é apenas um exemplo aproximado de uma possibilidade ..

Tim
fonte
11

O cálculo de férias não é padrão em cada estado. Estou escrevendo um aplicativo de banco para o qual preciso de algumas regras de negócios rígidas, mas ainda só consigo obter um padrão aproximado.

/**
 * 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;
}
James Pasta
fonte
1
O dia da independência é $ year .'- 07-04 '(4 de julho), não 4 de junho
solepixel
Os feriados que caem nos fins de semana são observados na véspera (se for sábado) ou no dia seguinte (se for domingo). Consegui usar sua matriz para obter os dias reais observados. Obrigado.
BrookeAH
9
$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);
Suresh Kamrushi
fonte
6

Aqui está uma função para adicionar dias de negócios a uma data

 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');

Outra postagem menciona getWorkingDays (dos comentários do php.net e incluídos aqui), mas acho que quebra se você começar em um domingo e terminar em um dia de trabalho.

Usando o seguinte (você precisará incluir a função getWorkingDays da postagem anterior)

 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);

Dá o resultado como 5 e não 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 

A seguinte função foi usada para gerar o acima.

     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);

Traga as férias ...

Bobina
fonte
3

Você pode tentar esta função que é mais simples.

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;
    }
}
George john
fonte
2

Função para somar ou subtrair dias úteis de uma determinada data, não contabiliza feriados.

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;
}

usar assim ...

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

Minha versão baseada no trabalho de @mcgrailm ... ajustada porque o relatório precisava ser revisado em 3 dias úteis e, se enviado em um fim de semana, a contagem começaria na segunda-feira seguinte:

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;
}

E calculando a diferença de duas datas em termos de dias úteis:

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;
}

Como uma nota, todos que estão usando 86400, ou 24 * 60 * 60, por favor, não ... seu tempo de esquecimento muda de inverno / verão, onde um dia não é exatamente 24 horas. Embora seja um pouco mais lento o strtotime ('+ 1 dia', $ timestamp), é muito mais confiável.

Craig Francis
fonte
2

Tentativa bruta de detectar o tempo de trabalho - segunda a sexta, das 8h às 16h:

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

Abaixo está o código de trabalho para calcular dias úteis úteis a partir de uma determinada data.

<?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"
easycodingclub
fonte
2

Aqui está outra solução sem loop for para cada dia.

$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
Laird
fonte
1

Para feriados, crie uma série de dias em algum formato que a data () possa produzir. Exemplo:

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

Em seguida, use as funções in_array () e date () para verificar se o carimbo de data / hora representa um feriado:

$day_of_year = date('M j', $timestamp);
$is_holiday = in_array($day_of_year, $holidays);
Jeremy Ruten
fonte
1

Eu tinha essa mesma necessidade, comecei com o primeiro exemplo da bobina e acabei com este

  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);
  }

hth alguem

Mcgrailm
fonte
desculpe, mas o seu mcgrailm não funciona ... ele não leva em consideração se o dia em $ enddate cai em um feriado ... ele só se preocupa com feriados durante as adições, a menos que eu esteja faltando alguma coisa
@Richard acho que entendo o que você está dizendo. Ele NÃO verifica a data de início para ver se é um feriado ou fim de semana, ele calcula os dias úteis APÓS a data de início. se você quiser incluir a data de início no cheque, poderá retirar o dia +1
mcgrailm
Acho que depende da gramática e do que você está tentando alcançar. Por exemplo, estou tentando descobrir quando um relatório precisa ser revisado, mas se for enviado em um fim de semana e precisar ser concluído em 3 negócios, a contagem começa na segunda-feira (não é feriado) ... Eu postei minhas versões, que são baseadas em seu código, mas foram ligeiramente ajustadas.
Craig Francis
Muito útil - uma coisa, eu tive que mudar !in_array($enddate,$holidays)para !in_array(date('Y-m-d',$enddate),$holidays)if usando um array de feriado passado como Bobbins '- $holidays=array('2013-06-16','2013-07-12','2013-08-05');caso contrário, você está verificando um array cheio de datas para um timestamp, que sempre retorna falso.
McNab de
1

Variante 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;
}

Variante 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;
}

Variante 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;
}

As considerações adicionais sobre feriados podem ser feitas usando variações do acima, fazendo o seguinte. Nota! certifique-se de que todos os carimbos de data / hora estão na mesma hora do dia (ou seja, meia-noite).

Faça uma série de datas de feriados (como carimbos de data / hora), ou seja:

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

Modificar linha:

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

ser estar :

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

Feito!

<?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;
}
Pawpro
fonte
1
<?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);
}
?>
marve
fonte
1

Aqui está uma solução recursiva. Ele pode ser facilmente modificado para apenas acompanhar e retornar a data mais recente.

//  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);
shepardtone
fonte
1
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";
James
fonte
1
<?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>";
?>
Vijay
fonte
1

calcular dias úteis entre duas datas, incluindo feriados e semana de trabalho personalizada

A resposta não é tão trivial - portanto, minha sugestão seria usar uma classe onde você possa configurar mais do que confiar em uma função simplista (ou assumir um local e uma cultura fixos). Para obter a data após um determinado número de dias úteis, você:

  1. precisa especificar em quais dias da semana você trabalhará (o padrão é MON-FRI) - a classe permite que você ative ou desative cada dia da semana individualmente.
  2. precisa saber que você precisa considerar feriados (país e estado) para ser preciso

Abordagem Funcional

/**
 * @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;
Ian Carter
fonte
1

Esta é outra solução, é quase 25% mais rápida do que verificar feriados com 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));
}
Enrico
fonte
1

Este snippet de código é muito fácil de calcular o dia útil sem fim de semana e feriados:

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)
Mahmoud Obaid
fonte
1

Sei que estou atrasado para a festa, mas uso esse velho conjunto de funções do Marcos J. Montes para calcular feriados e dias úteis. Ele se deu ao trabalho de adicionar um algoritmo de 1876 para a Páscoa e acrescentou todos os principais feriados dos EUA. Isso pode ser facilmente atualizado para outros países.

//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;
}
Nicolas Giszpenc
fonte
0

function get_business_days_forward_from_date ($ num_days, $ start_date = '', $ rtn_fmt = 'Ym-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");

}

Richard Varno
fonte
0

O add_business_days tem um pequeno bug. Tente o seguinte com a função existente e a saída será um sábado.

Startdate = Sexta-feira Dias úteis para adicionar = 1 Matriz de feriados = Adicionar data para a segunda-feira seguinte.

Eu consertei isso na minha função abaixo.

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);}
Pratik Thakkar
fonte
Suponho que isso seja baseado no código do Bobbin. Acredito que também
abordei
0

Acabei de fazer minha função funcionar com base no código Bobbin e mcgrailm, adicionando algumas coisas que funcionaram perfeitamente para mim.

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.
FAOM
fonte
1
a única diferença entre o meu e o seu é que o meu é baseado em um e o seu é baseado em zero. Você realmente não fez nada aqui. Estou feliz que este código ajudou você. No entanto, isso não se qualifica como sendo "baseado em" código, é mais como você tentar reivindicar meu código como seu
mcgrailm
0

Um aprimoramento da função oferecida por James Pasta acima, para incluir todos os feriados federais, e para corrigir 4 de julho (foi calculado como 4 de junho acima!), E também para incluir o nome do feriado como a chave da matriz ...

/ **
* National American Holidays
* @param string $ year
* @return array
* /
public static function 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;

}

Tony
fonte
0

Acabei de escrever uma API que pode ser usada para manipular dias úteis (nenhuma dessas soluções funcionou muito bem para a minha situação :-); vinculando a ele aqui, caso alguém mais o considere útil.

~ Nate

Classe PHP para calcular dias úteis

opensourcejunkie
fonte
0

Obrigado a Bobbin, mcgrailm, Tony, James Pasta e alguns outros que postaram aqui. Eu escrevi minha própria função para adicionar dias úteis a uma data, mas modifiquei com algum código que encontrei aqui. Isso controlará a data de início em um fim de semana / feriado. Isso também controlará o horário comercial. Eu adicionei alguns comentários e divido o código para torná-lo mais fácil de ler.

<?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);

?>
Dustin W
fonte
0

Pessoalmente, acho que esta é uma solução mais limpa e concisa:

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;
}

Basta enviar a newdata proposta para esta função.

DevlshOne
fonte