107 votes

Calculer les jours ouvrables

J'ai besoin d'une méthode pour ajouter des "jours ouvrables" en PHP. Par exemple, vendredi 12/5 + 3 jours ouvrables = mercredi 12/10.

Il faut au minimum que le code comprenne les week-ends, mais l'idéal serait qu'il tienne également compte des jours fériés fédéraux américains. Je suis sûr que je pourrais trouver une solution par force brute si nécessaire, mais j'espère qu'il existe une approche plus élégante. Quelqu'un ?

Merci.

0 votes

J'ai créé une bibliothèque décente pour cela. github.com/andrejsstepanovs/business-days-calculator Il est stable et prêt à entrer en production.

6 votes

Oh, je pense que nous devrions mentionner que, de nos jours, nous pouvons utiliser la fonction DateTime::modify pour ajouter directement les jours de la semaine : $my_date = new \DateTime () ; $my_date->modify("+ 7 weekday") ; s'exécutera simplement sans problème.

0 votes

Un blog de détail : goo.gl/YOsfPX

108voto

flamingLogos Points 3637

Voici une fonction de la commentaires des utilisateurs sur la page de la fonction date() dans le manuel PHP. C'est une amélioration d'une fonction précédente dans les commentaires qui ajoute le support des années bissextiles.

Saisissez les dates de début et de fin, ainsi qu'un tableau de tous les jours fériés qui pourraient se trouver entre les deux, et il renvoie les jours ouvrables sous forme de nombre entier :

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

4 votes

Cette fonction attend une date de début et de fin. Que se passe-t-il si vous avez une date de début et que vous voulez que le résultat soit à x jours ouvrables de la date donnée ?

0 votes

@mcgrailm : C'est une idée similaire, mais vous voudriez probablement écrire une deuxième fonction parce que les arguments et les valeurs de retour sont échangés. Ce serait quelque chose comme ((X jours % 5 jours par semaine) * 2 jours par week-end) + X jours + différence du jour de la semaine des dates de début et de fin + jours fériés).

0 votes

@mcgrailm : Je viens de trouver cette question - ses réponses peuvent vous mettre sur la bonne voie : stackoverflow.com/questions/2681787/ .

95voto

Glavić Points 13021

Obtenez le nombre de jours ouvrables sans congés entre deux dates :

Utilisez l'exemple :

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

Sortie :

3

Fonction :

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

0 votes

J'ai upvoted parce qu'il fonctionne et a quelques grandes améliorations :) Mais vous devriez au moins mentionner @Suresh Kamrushi qui a posté la plupart de ce code un mois plus tôt :)

0 votes

Si les jours ouvrables sont toujours du lundi au vendredi, vous pouvez remplacer le tableau DateInterval et workingdays par le tableau suivant $interval = DateInterval::createFromFormat('1 weekday'); Je recommande également d'utiliser $holidays = array_flip($holidays); avant le foreach et if isset($holidays[$period->format('Y-m-d')]); pour réduire le temps de traitement nécessaire par itération. Mais nous recommandons de créer une fonction personnalisée pour les vacances afin de pouvoir traiter les vacances relatives comme Thanksgiving. last thursday of november ou la fête du travail first monday of september

3 votes

Cela peut-il être étendu pour inclure le Vendredi saint (qui, si je me souviens bien, est le vendredi précédant le premier dimanche après la première pleine lune après le 21 mars) ?

13voto

Tim Points 759

Il y a quelques arguments pour le date() qui devrait vous aider. Si vous vérifiez date("w"), vous obtiendrez un nombre pour le jour de la semaine, de 0 pour le dimanche à 6 pour le samedi. Donc peut-être quelque chose comme

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

C'est juste un exemple approximatif d'une possibilité

11voto

James Pasta Points 81

Le calcul des congés n'est pas standard dans chaque État. J'écris une application bancaire pour laquelle j'ai besoin de règles de gestion strictes, mais je ne peux obtenir qu'une norme approximative.

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

1 votes

Indépendance le jour est $year.'-07-04' (4 juillet), et non le 4 juin

0 votes

Les jours fériés qui tombent le week-end sont observés la veille (si c'est un samedi) ou le lendemain (si c'est un dimanche). J'ai pu utiliser votre tableau pour obtenir les jours réellement observés. Merci.

6voto

Bobbin Points 301

Voici une fonction qui permet d'ajouter des jours ouvrables à une date.

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

Un autre article mentionne getWorkingDays (tiré des commentaires de php.net et inclus ici) mais je pense que cela ne fonctionne pas si vous commencez un dimanche et terminez un jour ouvrable.

En utilisant ce qui suit (vous devrez inclure la fonction getWorkingDays du post précédent)

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

Donne le résultat 5 et non 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 fonction suivante a été utilisée pour générer les données ci-dessus.

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

Apportez les vacances...

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X