Nombre de jours ouvrés

Pour faire suite au billet sur l'ajout d'un nombre de jours ouvrés, voici quelques méthodes permettant de calculer le nombre de jours ouvrés entre deux dates.
On considèrera ici que les jours ouvrés sont uniquement les jours compris entre le lundi et le vendredi.

Méthode la plus simple

La méthode la plus simple consiste à réaliser une boucle :

function date_getNbWorkingDays( $dateStart_ts , $dateEnd_ts ) {
    $wd = 0;
    
    // Ajuster le compteur en fonction du jour de la date de départ
    if( in_array(date('w', $dateStart_ts), array(0, 6)) ) {
        $wd -= 1;
    }

    // Ajuster les timestamp
    $dateStart_ts = mktime(0,0,0 , date("n",$dateStart_ts),date("j",$dateStart_ts),date("Y",$dateStart_ts));
    $dateEnd_ts = mktime(0,0,0 , date("n",$dateEnd_ts),date("j",$dateEnd_ts),date("Y",$dateEnd_ts));
    
    // Recherche du nombre de jours ouvrés
    while( $dateStart_ts < $dateEnd_ts ) {
        $dateStart_ts = strtotime( date("Y-m-d", $dateStart_ts) . " +1 days");
        if( !in_array(date('w', $dateStart_ts), array(0, 6))  ) {
            $wd++;
        }
    }

    if ($wd < 0 ) {
        $wd = 0;
    }

    return $wd;
}



echo date_getNbWorkingDays( strtotime("2017-12-13") , strtotime("2017-12-20") );
// retourne : 5

echo date_getNbWorkingDays( strtotime("2017-12-16") , strtotime("2017-12-20") );
// retourne : 2

 

En prenant en compte une heure limite :

function date_getNbWorkingDays( $dateStart_ts , $dateEnd_ts, $time_limit ) {
    $wd = 0;
    
    // Ajustement du compteur en fonction de l'heure limite
    if( !in_array(date('w', $dateStart_ts), array(0, 6)) && date( "G" , $dateStart_ts ) >= $time_limit ) {
        $wd -= 1;
    }
    // Ajustement du compteur en fonction du jour de la date de départ
    if( in_array(date('w', $dateStart_ts), array(0, 6)) ) {
        $wd -= 1;
    }

    
    // Correction des timestamp 
    $dateStart_ts = mktime(0,0,0 , date("n",$dateStart_ts),date("j",$dateStart_ts),date("Y",$dateStart_ts));
    $dateEnd_ts = mktime(0,0,0 , date("n",$dateEnd_ts),date("j",$dateEnd_ts),date("Y",$dateEnd_ts));
    
    // Recherche du nombre de jours ouvrés
    while( $dateStart_ts < $dateEnd_ts ) {
        $dateStart_ts = strtotime( date("Y-m-d", $dateStart_ts) . " +1 days");
        if( !in_array(date('w', $dateStart_ts), array(0, 6))  ) {
            $wd++;
        }
    }

    if ($wd < 0 ) {
        $wd = 0;
    }

    return $wd;
}


echo date_getNbWorkingDays( strtotime("2017-12-13 11:50") , strtotime("2017-12-20"), 12 );
// retourne : 5
echo date_getNbWorkingDays( strtotime("2017-12-13 13:30") , strtotime("2017-12-20"), 12 );
// retourne : 4

echo date_getNbWorkingDays( strtotime("2017-12-16 11:50") , strtotime("2017-12-20"), 12 );
// retourne : 2 
echo date_getNbWorkingDays( strtotime("2017-12-16 13:30") , strtotime("2017-12-20"), 12 );
// retourne : 2

 

En prenant en compte les jours fériés :

function date_getArrayHolidays( $year = null , $format = "Y-m-d" ) {
    if ($year === null) {
        $year = intval(date('Y'));
    }

    $easterDate  = easter_date($year);
    $easterDay   = date('j', $easterDate);
    $easterMonth = date('n', $easterDate);
    $easterYear   = date('Y', $easterDate);

    $holidays = array(
        // Dates fixes
        date( $format, mktime(0, 0, 0, 1,  1,  $year)),  // 1er janvier
        date( $format, mktime(0, 0, 0, 5,  1,  $year)),  // Fête du travail
        date( $format, mktime(0, 0, 0, 5,  8,  $year)),  // Victoire des alliés
        date( $format, mktime(0, 0, 0, 7,  14, $year)),  // Fête nationale
        date( $format, mktime(0, 0, 0, 8,  15, $year)),  // Assomption
        date( $format, mktime(0, 0, 0, 11, 1,  $year)),  // Toussaint
        date( $format, mktime(0, 0, 0, 11, 11, $year)),  // Armistice
        date( $format, mktime(0, 0, 0, 12, 25, $year)),  // Noel
        // Dates variables
        date( $format, mktime(0, 0, 0, $easterMonth, $easterDay + 1,  $easterYear)),  // Lundi de paques
        date( $format, mktime(0, 0, 0, $easterMonth, $easterDay + 39, $easterYear)),  // Jeudi de Ascension
        date( $format, mktime(0, 0, 0, $easterMonth, $easterDay + 50, $easterYear)),  // Lundi de Pentecôte
    );
    sort($holidays);
    return $holidays;  
}


function date_getNbWorkingDays( $dateStart_ts , $dateEnd_ts, $time_limit ) {

    // Récupération de la liste des jours fériés
    $ary_holidays = array();
    for( $i = date("Y", $dateStart_ts) ; $i <= date("Y", $dateEnd_ts) ; $i++ ) {
        $ary_holidays = array_merge( $ary_holidays , date_getArrayHolidays( $i , "Y-m-d" ) );
    }

    $wd = 0;
    
    
    // Ajustement du compteur en fonction de l'heure limite
    if( !in_array(date('w', $dateStart_ts), array(0, 6)) && !in_array( date( "Y-m-d", $dateStart_ts) , $ary_holidays ) && date( "G" , $dateStart_ts ) >= $time_limit ) {
        $wd -= 1;
    }
    // Ajustement du compteur en fonction du jour de la date de départ
    if( in_array(date('w', $dateStart_ts), array(0, 6)) || in_array( date( "Y-m-d", $dateStart_ts) , $ary_holidays ) ) {
        $wd -= 1;
    }

    // Correction des timestamp
    $dateStart_ts = mktime(0,0,0 , date("n",$dateStart_ts),date("j",$dateStart_ts),date("Y",$dateStart_ts));
    $dateEnd_ts = mktime(0,0,0 , date("n",$dateEnd_ts),date("j",$dateEnd_ts),date("Y",$dateEnd_ts));
    
    // Recherche du nombre de jours ouvrés
    while( $dateStart_ts < $dateEnd_ts ) {
        $dateStart_ts = strtotime( date("Y-m-d", $dateStart_ts) . " +1 days");
        if( !in_array(date('w', $dateStart_ts), array(0, 6)) && !in_array( date( "Y-m-d", $dateStart_ts) , $ary_holidays ) ) {
            $wd++;
        }
    }
    
    if ($wd < 0 ) {
        $wd = 0;
    }

    return $wd;
}

 

La principale limite de cette méthode est la boucle, et par conséquent, le temps nécessaire à rechercher le nombre de jours ouvrés...

 

Méthode alternative

Plutôt que de faire une boucle, on va procéder de la façon suivante :

  • calculer le nombre de semaines entre les deux dates
  • calculer le delta de jours entre le jour de la semaine de la date de début et le jour de la semaine de la date de fin
  • calculer le nombre de jours ouvrés : nombre de semaines x 5 + delta

Le calcul du nombre de semaines n'est pas problématique.

// $tsStart : timestamp de la date de départ
// $tsEnd : timestamp de la date de de fin

$nbDays = round ( ( $tsEnd - $tsStart ) / 86400 );
$nbWeeks = floor( $nbDays / 7 );

 

Calcul du delta

date("w" , timestamp) nous permet de connaitre le Jour de la semaine au format numérique (0 pour dimanche à 6 pour samedi).

Plutôt qu'un grand discours, un tableau !


     | S M T W T F S
    -|--------------
    S| 0 1 2 3 4 5 5
    M| 4 0 1 2 3 4 4
    T| 3 4 0 1 2 3 3
    W| 2 3 4 0 1 2 2
    T| 1 2 3 4 0 1 1
    F| 0 1 2 3 4 0 0
    S| 0 1 2 3 4 5 0

Pour calculer le delta de jours, il n'y a qu'à parcourir ce magnifique tableau ! Les lignes correspondent au jour de la semaine de la date de début. Les colonnes, au jour de la semaine de la date de fin. Nous pourrions très bien faire un tableau à deux dimensions : $ary[ jour 1 ] [ jour 2 ] = delta

$ary = array(
    0 => array(0=>0, 1=>1, 2=>2, 3=>3, 4=>4, 5=>5, 6=>5),
    1 => array(0=>4, 1=>0, 2=>1, 3=>2, 4=>3, 5=>4, 6=>4),
    ...
);

$nbDaysDiff = $ary[ date("w" , $dateStart_ts) ][ date("w" , $dateEnd_ts) ];

 

En transformant ce tableau sous la forme d'une chaine de caractères, ce sera encore plus facile à exploiter :

0123450 4012344 3401233 2340122 1234011 0123400 0123450

0123455401234434012332340122123401101234000123450

Pour calculer le delta, il suffit de rechercher dans cette chaine de la façon suivante :

$strDaysDiff = "0123455401234434012332340122123401101234000123450";
$nbDaysDiff = substr($strDaysDiff , 7 * date("w" , $dateStart_ts) + date("w" , $dateEnd_ts) , 1);

Au final, voici notre petite fonction

function date_getNbWorkingDays( $dateStart_ts , $dateEnd_ts, $time_limit ) {

    $strDaysDiff = "0123455401234434012332340122123401101234000123450";
    $tsEnd = mktime(0,0,0 , date("n",$dateEnd_ts),date("j",$dateEnd_ts),date("Y",$dateEnd_ts));
    $tsStart = mktime(0,0,0 , date("n",$dateStart_ts),date("j",$dateStart_ts),date("Y",$dateStart_ts));
    

    $nbDays = round ( ( $tsEnd - $tsStart ) / 86400 );
    $nbWeeks = floor( $nbDays / 7 );
    $nbDaysDiff = substr($strDaysDiff , 7 * date("w" , $dateStart_ts) + date("w" , $dateEnd_ts) , 1);
    $nbWorkingDays = ($nbWeeks * 5) + $nbDaysDiff;


    if( !in_array(date('w', $dateStart_ts), array(0, 6)) && date( "G" , $dateStart_ts ) >= $time_limit ) {
        $nbWorkingDays -= 1;
    }

    if( in_array(date('w', $dateStart_ts), array(0, 6)) ) {
        $nbWorkingDays -= 1;
    }

    if ($nbWorkingDays < 0 ) {
        $nbWorkingDays = 0;
    }

    return $nbWorkingDays;

}


echo date_getNbWorkingDays( strtotime("2017-12-13 11:50") , strtotime("2017-12-20"), 12 );
// retourne : 5
echo date_getNbWorkingDays( strtotime("2017-12-13 13:30") , strtotime("2017-12-20"), 12 );
// retourne : 4

echo date_getNbWorkingDays( strtotime("2017-12-16 11:50") , strtotime("2017-12-20"), 12 );
// retourne : 2 
echo date_getNbWorkingDays( strtotime("2017-12-16 13:30") , strtotime("2017-12-20"), 12 );
// retourne : 2

 

Cette fonction ne prends pas en compte les jours fériés. Si nous voulons les intégrer, il faudra... refaire une boucle... mais uniquement sur les jours fériés :

function date_getNbWorkingDays( $dateStart_ts , $dateEnd_ts, $time_limit ) {

    $ary_holidays = array();
    for( $i = date("Y", $dateStart_ts) ; $i <= date("Y", $dateEnd_ts) ; $i++ ) {
        $ary_holidays = array_merge( $ary_holidays , date_getArrayHolidays( $i , "Y-m-d" ) );
    }

    $strDaysDiff = "0123455401234434012332340122123401101234000123450";
    $tsEnd = mktime(0,0,0 , date("n",$dateEnd_ts),date("j",$dateEnd_ts),date("Y",$dateEnd_ts));
    $tsStart = mktime(0,0,0 , date("n",$dateStart_ts),date("j",$dateStart_ts),date("Y",$dateStart_ts));
    
    $nbDays = round ( ( $tsEnd - $tsStart ) / 86400 );
    $nbWeeks = floor( $nbDays / 7 );
    $nbDaysDiff = substr($strDaysDiff , 7 * date("w" , $dateStart_ts) + date("w" , $dateEnd_ts) , 1);
    $nbWorkingDays = ($nbWeeks * 5) + $nbDaysDiff;


    if( !in_array(date('w', $dateStart_ts), array(0, 6)) && !in_array( date( "Y-m-d", $dateStart_ts) , $ary_holidays ) && date( "G" , $dateStart_ts ) >= $time_limit ) {
        $nbWorkingDays -= 1;
    }
    if( in_array(date('w', $dateStart_ts), array(0, 6)) || in_array( date( "Y-m-d", $dateStart_ts) , $ary_holidays ) ) {
        $nbWorkingDays -= 1;
    }



    foreach ($ary_holidays as $holiday) {
        $holiday_ts = mktime(0,0,0 , substr($holiday, 5, 2),substr($holiday, 8, 2), substr($holiday, 0, 4) );

        if( $holiday_ts > $tsStart && $holiday_ts < $tsEnd && !in_array(date('w', $holiday_ts), array(0, 6))) {
            $nbWorkingDays -= 1;
        }
    }

    if ($nbWorkingDays < 0 ) {
        $nbWorkingDays = 0;
    }

    return $nbWorkingDays;

}

 

Calcul du nombre de jours ouvrés avec MySQL

MySQL dispose de deux fonctions pour retourner le jour de la semaine d'une date spécifiée :

  • WEEKDAY() : 0 pour lundi à 6 pour dimanche
  • DAYOFWEEK() : 1 pour dimanche à  7 pour samedi

Pour calculer le nombre de jours ouvrés entre deux dates, nous allons appliquer une méthode identique à la précédente.

WEEKDAY() : 0 (Monday) ... 6 (Sunday)
        
         | M T W T F S S
        -|--------------
        M| 0 1 2 3 4 4 4
        T| 4 0 1 2 3 3 3
        W| 3 4 0 1 2 2 2
        T| 2 3 4 0 1 1 1
        F| 1 2 3 4 0 0 0
        S| 0 1 2 3 4 0 0
        S| 0 1 2 3 4 0 0

0123444 4012333 3401222 2340111 1234000 0123400 0123440


DAYOFWEEK() : 1 (Sunday) ... 7 (Saturday)
        
         | S M T W T F S
        -|--------------
        -| 0 0 0 0 0 0 0
        S| 0 0 1 2 3 4 4
        M| 4 0 1 2 3 4 4
        T| 3 4 0 1 2 3 3
        W| 2 3 4 0 1 2 2
        T| 1 2 3 4 0 1 1
        F| 0 1 2 3 4 0 0
        S| 0 0 1 2 3 4 0
        
0000000 0012344 4012344 3401233 2340122 1234011 0123400 0012340
    

 

@date1 : date de debut
@date2 : date de fin

5 * ( DATEDIFF( @date2, @date1 ) DIV 7) + MID('0123444401233334012222340111123400001234000123440',        7 * WEEKDAY( @date1 )   + WEEKDAY( @date2 ) + 1 , 1)
5 * ( DATEDIFF( @date2, @date1 ) DIV 7) + MID('00000000012344401234434012332340122123401101234000012340', 7 * DAYOFWEEK( @date1 ) + DAYOFWEEK( @date2 )   , 1) 



5 * ( DATEDIFF(SYSDATE(), `date`) DIV 7) + MID('0123444401233334012222340111123400001234000123440',        7 * WEEKDAY(`date`)   + WEEKDAY(SYSDATE()) + 1 , 1)
5 * ( DATEDIFF(SYSDATE(), `date`) DIV 7) + MID('00000000012344401234434012332340122123401101234000012340', 7 * DAYOFWEEK(`date`) + DAYOFWEEK(SYSDATE())   , 1) 

 

Pour ajuster le nombre de jours ouvrés en fonction des samedis et les dimanches, il faudra simplement ajouter une condition avec IF, par exemple :

IF( 
    (WEEKDAY( @date1 ) = 5 AND WEEKDAY( @date2 ) = 5)
    OR ( WEEKDAY( @date1 ) = 6 AND WEEKDAY( @date2 ) = 6)
    OR (WEEKDAY( @date1 ) = 5 AND WEEKDAY( @date2 ) = 6)
    
    ,  calcul_precedant - 1 , calcul_precedent 

  ) 

 

 

 

Étiquettes