Overview

La classe DateTime possède une méthode pour ajouter facilement une période de temps (ici on ajoute 10 jours):

$date = new DateTime('now');
$date->add(new DateInterval('P10D'));

Mais il n’est pas possible d’ajouter des jours ouvrés, ce qui est parfois bien utile. Une solution simple est d’étendre la classe DateTime avec deux méthodes :

  • addBusinessDays qui prend en seul paramètre : le nombre de jours ouvrés à ajouter à la date
  • addBusinessDay, méthode sans paramètre qui va ajouter 1 jour ouvrés en vérifiant avant si la date courante est vendredi ou samedi

Il est importer d’utiliser un espace de nom si le nom de la classe est DateTime :

namespace LDDW;

class DateTime extends \DateTime
{
    private $periods;

    public function __construct(string $time = 'now', \DateTimeZone $timezone = null)
    {
        parent::__construct($time, $timezone);

        // Put periods in a var cache
        $this->periods = [
            1 => new \DateInterval('P1D'),
            2 => new \DateInterval('P2D'),
            3 => new \DateInterval('P3D'),
        ];
    }

    public function addBusinessDays(int $nb)
    {
        if($nb > 0) {
            for($a = 0; $a < $nb; $a++) {
                $this->addBusinessDay();
            }
        }

        return $this;
    }

    private function addBusinessDay()
    {
        if($this->isFriday()) {
            $this->add($this->periods[3]);
        } elseif($this->isSaturday()) {
            $this->add($this->periods[2]);
        } else {
            $this->add($this->periods[1]);
        }
    }

    private function isFriday(): bool
    {
        return $this->format('N') == 5;
    }

    private function isSaturday(): bool
    {
        return $this->format('N') == 6;
    }
}

Utilisation

La classe s’utilise comme DateTime, à savoir :

use LDDW\DateTime;

$date = new DateTime('now');
$date->addBusinessDays(10);

Bien s’assurer d’utiliser le bon espace de nom avec use LDDW\DateTime;

Partons en vacances

Supposons que certains jours ouvrés sont en réalité des jours de vacances. Dans ce cas, si après avoir ajouté x jours ouvrés on tombe sur un jour de vacance, on doit renvoyer la prochaine date considérée comme ouvrée. Ajoutons donc quelques méthodes à notre classe :

  • une propriété $holidays qui va contenir les jours considérés comme des vacances
  • les getters/setters de la propriété $holidays
  • une méthode addHoliday() qui va ajouter un jour de vacance
  • une méthode isSunday() qui va nous dire si la date est un dimanche. Cette méthode n’est pas fondamentalement obligatoire mais je vais m’en servir dans la suivante
  • une méthode isWeekend() qui va nous dire si la date est un des 2 jours de week-end
  • une méthode isBusinnessDay() qui va nous dire si je jour est ouvré : si on est pas en week-end ni en vacances, on a donc un jour ouvré..
class DateTime extends \DateTime
{
    /** @var array */
    private $holidays;

    // ...

    /**
     * @return array
     */
    public function getHolidays(): array
    {
        return $this->holidays;
    }

    /**
     * @param array $holidays
     * @return $this
     */
    public function setHolidays(array $holidays)
    {
        $this->holidays = array_fill_keys($holidays, true);

        return $this;
    }

    /**
     * @param $day dd-mm-yyyy
     * Ex: $this->addHoliday('06-12-2018');
     * @return $this
     */
    public function addHoliday($day)
    {
        if(preg_match('/^[0-9]{2}-[0-9]{2}-20[0-9]{2}$/', $day)) {
            $this->holidays[$day] = true;
        }

        return $this;
    }

    /**
     * @return bool
     */
    private function isBusinnessDay(): bool
    {
        $key = $this->format('d-m-Y');

        return !$this->isWeekend() && empty($this->holidays[$key]);
    }

    public function isSunday(): bool
    {
        return $this->format('N') == 7;
    }

    public function isWeekend(): bool
    {
        return ($this->isSunday() || $this->isSaturday());
    }
}

Reste plus qu’à modifier la méthode addBusinessDays() qui va maintenant vérifier que le résultat est bien un jour ouvré :

public function addBusinessDays(int $nb)
{
    // ...

    // Check for holidays
    while(!$this->isBusinnessDay()) {
        $this->addBusinessDay();
    }

    return $this;
}

Petit rappel : un jour est ouvré si on est ni en vacances ni en week-end.

Et voilà comment utiliser la classe :

use LDDW\DateTime;

$date = new DateTime('now');
$date->setHolidays(array(
    '22-12-2018',
    '23-12-2018'
));
$date->addBusinessDays(10);

Amélioration

Cette classe a un défaut majeur : elle ne prend en compte que les week-ends, passant sous silence les jours fériés et autres joyeusetés du calendrier. Une belle amélioration serait de les intégrer, au moins en Français et Anglais.

La classe complète

Voilà la classe au grand complet, merci de m’avoir lu jusqu’ici !

namespace LDDW;

class DateTime extends \DateTime
{
    private $periods;
    private $holidays;

    /**
     * @return array
     */
    public function getPeriods(): array
    {
        return $this->periods;
    }

    /**
     * @param array $periods
     */
    public function setPeriods(array $periods): void
    {
        $this->periods = $periods;
    }

    public function __construct(string $time = 'now', \DateTimeZone $timezone = null)
    {
        parent::__construct($time, $timezone);

        // Put periods in a var cache
        $this->periods = [
            1 => new \DateInterval('P1D'),
            2 => new \DateInterval('P2D'),
            3 => new \DateInterval('P3D'),
        ];
    }

    public function addBusinessDays(int $nb)
    {
        if($nb > 0) {
            for($a = 0; $a < $nb; $a++) {
                $this->addBusinessDay();
            }
        }

        return $this;
    }

    private function addBusinessDay()
    {
        if($this->isFriday()) {
            $this->add($this->periods[3]);
        } elseif($this->isSaturday()) {
            $this->add($this->periods[2]);
        } else {
            $this->add($this->periods[1]);
        }

        // Check for holidays
        while(!$this->isBusinnessDay()) {
            $this->addBusinessDay();
        }
    }

    public function getHolidays(): array
    {
        return $this->holidays;
    }

    /**
     * @param array $holidays
     * @return $this
     */
    public function setHolidays(array $holidays)
    {
        $this->holidays = array_fill_keys($holidays, true);

        return $this;
    }

    /**
     * @param $day dd-mm-yyyy
     * Ex: $this->addHoliday('06-12-2018');
     * @return $this
     */
    public function addHoliday($day)
    {
        if(preg_match('/^[0-9]{2}-[0-9]{2}-20[0-9]{2}$/', $day)) {
            $this->holidays[$day] = true;
        }

        return $this;
    }

    /**
     * @return bool
     */
    private function isBusinnessDay(): bool
    {
        $key = $this->format('d-m-Y');

        return !$this->isWeekend() && empty($this->holidays[$key]);
    }

    private function isFriday(): bool
    {
        return $this->format('N') == 5;
    }

    private function isSaturday(): bool
    {
        return $this->format('N') == 6;
    }

    public function isSunday(): bool
    {
        return $this->format('N') == 7;
    }

    public function isWeekend(): bool
    {
        return ($this->isSunday() || $this->isSaturday());
    }
}

Références externes