Calcular días laborales

103

Necesito un método para agregar "días laborales" en PHP. Por ejemplo, viernes 12/5 + 3 días hábiles = miércoles 12/10.

Como mínimo, necesito el código para comprender los fines de semana, pero lo ideal es que también tenga en cuenta los feriados federales de EE. UU. Estoy seguro de que podría encontrar una solución mediante la fuerza bruta si fuera necesario, pero espero que haya un enfoque más elegante. ¿Nadie?

Gracias.

AdamTheHutt
fuente
Creé una biblioteca decente para eso. github.com/andrejsstepanovs/business-days-calculator Es estable y está listo para entrar en producción.
wormhit
6
Oh, creo que deberíamos mencionar que, hoy en día, podemos usar la función DateTime :: modificar para agregar días de la semana de inmediato: $ my_date = new \ DateTime (); $ mi_fecha-> modificar ("+ 7 días de la semana"); simplemente funcionará sin parecer.
mika
Un blog detallado: goo.gl/YOsfPX
Suresh Kamrushi
Una respuesta más simple / limpia: stackoverflow.com/questions/5532002/…
Dave

Respuestas:

103

Aquí hay una función de los comentarios del usuario en la página de funciones date () en el manual de PHP. Es una mejora de una función anterior en los comentarios que agrega soporte para años bisiestos.

Ingrese las fechas de inicio y finalización, junto con una matriz de días festivos que podrían estar en el medio, y devuelve los días hábiles como un número entero:

<?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
fuente
4
esta función espera una fecha de inicio y finalización, ¿qué sucede si tiene una fecha de inicio y desea que el resultado sea x días hábiles a partir de la fecha dada?
mcgrailm
@mcgrailm: Es una idea similar, pero probablemente quieras escribir una segunda función porque los argumentos y los valores de retorno se intercambian. Sería algo así como ((X días% 5 días por semana) * 2 días por fin de semana) + X días + diferencia del día de la semana de las fechas de inicio y finalización + feriados).
flamingLogos
@mcgrailm: Acabo de encontrar esta pregunta; sus respuestas pueden indicarle la dirección correcta: stackoverflow.com/questions/2681787/… .
flamingLogos
1
Hay un error en esta función. ¿Qué pasa si hay un cambio de zona horaria entre esas fechas? Por ejemplo, para CEST: echo getWorkingDays ("2012-01-01", "2012-05-01", $ vacaciones); no te dará un número entero. stackoverflow.com/questions/12490521/…
mnowotka
5
Esa es la forma antigua de hacerlo. Use la respuesta de Glavić si es posible
Thomas Ruiz
90

Obtenga el número de días laborables sin festivos entre dos fechas:

Ejemplo de uso:

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

Salida:

3

Función:

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ć
fuente
Elegí mi voto porque funciona y tiene grandes mejoras :) Pero al menos deberías mencionar a @Suresh Kamrushi, quien publicó la mayor parte de este código un mes antes. :)
KOGI
Si los días laborables son siempre de lunes a viernes, puede sustituir la matriz DateInterval y los días laborables porque $interval = DateInterval::createFromFormat('1 weekday');también recomiendo usar $holidays = array_flip($holidays);antes de foreach y if isset($holidays[$period->format('Y-m-d')]);reducir la cantidad de tiempo de procesamiento necesario por iteración. Pero recomiende crear una función personalizada para las vacaciones para poder procesar las vacaciones relativas, como last thursday of novemberel día de acción de gracias o el día del trabajofirst monday of september
fyrye
3
¿Se puede ampliar esto para incluir el Viernes Santo (que creo recordar es el viernes antes del primer domingo después de la primera luna llena después del 21 de marzo)?
Damian Yerrick
Tenga en cuenta las diferentes zonas horarias. Aquí con Europa / Berlín esto no funcionará. Solo lo hago en la marca de tiempo ahora con fecha ('N') y agrego 1 día y su zona horaria independiente.
user3655829
12

Hay algunos argumentos para la función date () que deberían ayudar. Si marca la fecha ("w"), le dará un número para el día de la semana, desde 0 para el domingo hasta 6 para el sábado. Entonces ... tal vez 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 es solo un ejemplo aproximado de una posibilidad.

Tim
fuente
11

El cálculo de vacaciones no es estándar en cada estado. Estoy escribiendo una solicitud bancaria para la que necesito algunas reglas comerciales estrictas, pero aún así solo puedo obtener un estándar 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
fuente
1
El día de la independencia es $ año .'- 07-04 '(4 de julio), no el 4 de junio
solepixel
Los días festivos que caen los fines de semana se observan el día anterior (si es sábado) o el día siguiente (si es domingo). Pude usar su matriz para obtener los días observados reales. Gracias.
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
fuente
6

Aquí hay una función para agregar días de negocios a una fecha.

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

Otra publicación menciona getWorkingDays (de los comentarios de php.net e incluidos aquí) pero creo que se rompe si comienzas un domingo y terminas un día laboral.

Usando lo siguiente (deberá incluir la función getWorkingDays de la publicación 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);

Da el resultado como 5, no 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 

La siguiente función se utilizó para generar lo anterior.

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

Que vengan las vacaciones ...

Bobina
fuente
3

Puede probar esta función que es más simple.

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
fuente
2

Una función para sumar o restar días hábiles de una fecha determinada, esto no tiene en cuenta los días festivos.

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 así ...

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

Mi versión basada en el trabajo de @mcgrailm ... modificada porque el informe tenía que ser revisado dentro de los 3 días hábiles, y si se enviaba un fin de semana, el conteo comenzaría el lunes siguiente:

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

Y calculando la diferencia de dos fechas en términos de días hábiles:

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 nota, todos los que están usando 86400, o 24 * 60 * 60, por favor no ... su hora de olvido cambia de invierno / verano, donde un día no es exactamente 24 horas. Si bien es un poco más lento el strtotime ('+ 1 día', $ timestamp), es mucho más confiable.

Craig Francis
fuente
2

Intento brutal de detectar el tiempo de trabajo: de lunes a viernes de 8 a.m. a 4 p.m.

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

A continuación se muestra el código de trabajo para calcular los días hábiles a partir de una fecha determinada.

<?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
fuente
2

Aquí hay otra solución sin bucle for para cada día.

$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
Terrateniente
fuente
1

Para las vacaciones, cree una matriz de días en algún formato que pueda producir date (). Ejemplo:

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

Luego use las funciones in_array () y date () para verificar si la marca de tiempo representa un día festivo:

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

Tenía esta misma necesidad, comencé con el primer ejemplo de Bobbin y terminé con esto

  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 alguien

mcgrailm
fuente
lo siento, pero mcgrailm el suyo no funciona del todo ... no tiene en cuenta si el día en $ enddate cae en un día festivo ... solo le preocupan los días festivos durante los
@Richard, creo que entiendo lo que dices. NO verifica la fecha de inicio para ver si es un día festivo o de fin de semana; calcula los días hábiles DESPUÉS de la fecha de inicio. si desea incluir la fecha de inicio en el cheque, puede retirar el día +1
mcgrailm
Creo que depende de la gramática y de lo que intentes lograr. Por ejemplo, estoy tratando de calcular cuándo se debe revisar un informe, pero si se envía un fin de semana y debe completarse en 3 negocios, entonces el conteo comienza el lunes (tomando que no es un feriado) ... He publicado mis versiones, que se basan en su código, pero las modifiqué ligeramente.
Craig Francis
Muy útil - una cosa, tuve que cambiar !in_array($enddate,$holidays)a !in_array(date('Y-m-d',$enddate),$holidays)si se utiliza una matriz de vacaciones aprobada en como Bobinas - $holidays=array('2013-06-16','2013-07-12','2013-08-05');De lo contrario se está comprobando una gama completa de fechas para una marca de tiempo, que siempre devuelve falso.
McNab
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;
}

Las consideraciones de vacaciones adicionales se pueden realizar utilizando variaciones de lo anterior haciendo lo siguiente. ¡Nota! asegúrese de que todas las marcas de tiempo sean a la misma hora del día (es decir, medianoche).

Haga una serie de fechas de vacaciones (como unixtimestamps), es decir:

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

Modificar línea:

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

ser - estar :

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

¡Hecho!

<?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
fuente
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);
}
?>
maravillarse
fuente
1

Aquí hay una solución recursiva. Se puede modificar fácilmente para realizar un seguimiento y devolver la última fecha.

//  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
fuente
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
fuente
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
fuente
1

calcular los días laborables entre dos fechas, incluidas las vacaciones y la semana laboral personalizada

La respuesta no es tan trivial; por lo tanto, mi sugerencia sería usar una clase en la que pueda configurar más que confiar en una función simplista (o asumir una configuración regional y una cultura fijas). Para obtener la fecha después de una determinada cantidad de días laborables:

  1. necesita especificar en qué días de la semana trabajará (predeterminado de lunes a viernes): la clase le permite habilitar o deshabilitar cada día de la semana individualmente.
  2. Necesita saber que debe considerar los días festivos (país y estado) para ser precisos.

Enfoque 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
fuente
1

Esta es otra solución, es casi un 25% más rápida que comprobar las vacaciones con 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
fuente
1

Este fragmento de código es muy fácil de calcular el día laborable sin fines de semana ni festivos:

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
fuente
1

Sé que llego tarde a la fiesta, pero utilizo este antiguo conjunto de funciones de Marcos J. Montes para calcular los días festivos y laborales. Se tomó el tiempo para agregar un algoritmo de 1876 para la Pascua y agregó todas las principales festividades de EE. UU. Esto se puede actualizar fácilmente para otros 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;
}
Nicolás Giszpenc
fuente
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
fuente
0

El add_business_days tiene un pequeño error. Intente lo siguiente con la función existente y la salida será un sábado.

Fecha de inicio = Viernes Días hábiles para agregar = 1 Matriz de vacaciones = Agregar fecha para el lunes siguiente.

Lo he arreglado en mi función a continuación.

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
fuente
Supongo que esto se basa en el código de Bobbin, creo que también
abordé
0

Acabo de hacer que mi función funcione según el código Bobbin y mcgrailm, agregando algunas cosas que funcionaron perfectamente para mí.

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
fuente
1
la única diferencia entre el mío y el tuyo es que el mío está basado en uno y el tuyo está basado en cero. Realmente no has hecho nada aquí. Estoy feliz de que este código te haya ayudado. Sin embargo, esto no califica como "basado en" código, es más como si intentas reclamar mi código como tuyo
mcgrailm
0

Una mejora de la función ofrecida por James Pasta arriba, para incluir todos los días festivos federales y para corregir el 4 de julio (¡se calculó como el 4 de junio arriba!), Y para incluir también el nombre del día festivo como clave de matriz ...

/ **
* Días festivos nacionales estadounidenses
* @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
fuente
0

Acabo de terminar de escribir una API que se puede usar para manipular los días hábiles (ninguna de estas soluciones funcionó del todo para mi situación :-); vinculándolo aquí en caso de que alguien más lo encuentre útil.

~ Nate

Clase PHP para calcular días laborales

opensourcejunkie
fuente
0

Gracias a Bobbin, mcgrailm, Tony, James Pasta y algunos otros que publicaron aquí. Escribí mi propia función para agregar días hábiles a una fecha, pero la modifiqué con un código que encontré aquí. Esto manejará que la fecha de inicio sea un fin de semana / feriado. Esto también manejará el horario comercial. Agregué algunos comentarios y desglosé el código para que sea más fácil de leer.

<?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
fuente
0

Personalmente, creo que esta es una solución más limpia y 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;
}

Simplemente envíe la newfecha propuesta a esta función.

DevlshOne
fuente