Calcolo fra due date con esclusione delle feste

MarcoGrazia

Utente Attivo
15 Dic 2009
852
20
28
63
Udine
www.stilisticamente.com
Ciao, sto creando un modulo per la gestione delle ferie e mi sono bloccato su un dilemma non da poco, il calcolo dei giorni richiesti, ovvero la differenza tra due date, considerando anche le giornate festive e prefestive.

In pratica, oltre a dover escludere dal calcolo le domeniche, dovrei togliere anche i sabati per chi sceglie il regime lavorativo su 5 giorni, e in più, peggio del peggio, calcolare le feste infrasettimanali.

Sto impazzendo e se qualcuno sa dove posso trovare della corda per impiccarmi, o magari conosce già la soluzione, gli sarò grato in eterno ed avrà una fornitura a vita di grazie tante. :D
 
Allora, secondo me bisogna bisogna dividere il problema in sottopassaggi:
- crei una funzione differenza(fine, inizio) che dovrà fare il return dei giorni di differenza.
- attraverso la funzione diff, calcoli il numero totale di giorni che ci sono tra le due date, questa differenza chiamiamola $range
- sia per la data di inizio che per la data di fine, usi la funzione date (con parametro 'w') per calcolare il "numero" del giorno della settimana (in particolare se è Domenica l'output è 0, Lunedì 1, .. , Sabato 6), ipotizziamo che $ini=5 (la data di inizio è di Venerdì) e $fin=2 (la data di fine è di martedì)
- ora viene la prima parte difficile, cioè calcolare il numero di domeniche e sabati compresi tra le due date, provo a fare qualcosa di generale, poi probabilmente va sistemato in base a dettagli tipo estremi inclusi/esclusi ecc. Chiamiamo $sabati il numero di sabati e $domeniche il numero di domeniche. Entrambi sono uguali a floor($range/7) cioè il numero di settimane complete, poi però con degli if-else su $ini e $fin bisogna aggiungerci uno in più (ad esempio tra il 4/06 e il 29/06, cioè $ini=5 e $fin=2, ci sono floor(25/7)=3 settimane, però $domeniche=4 perché va considerata anche quella iniziale).
-a questo punto se l'utente lavora anche il sabato fai $range-$domeniche, altrimenti $range-$sabati-$domeniche.
-infine per togliere le date festive (che non sono di sabato o domenica perché quelle le hai già eliminate) fai un array e lo riempi con le date festive che vuoi inserire a mano, poi con un ciclo for controlli per ognuna se è compresa tra la data di inizio e di fine (puoi utilizzare strtotime) e se è un sabato/domenica, se non lo è incrementi una variabile che conta i giorni da eliminare, e poi fai la sottrazione finale

È un po' complicato ma dovrebbe funzionare
 
Sì è molto interessante e sicuramente mi servirà proprio per escludere le feste comandate, ma rimane il problema delle domeniche, quelle non sono fisse, e in quanto alla pasqua il PHP condivide due funzioni per il suo facile calcolo.

PS. ho scoperto che da quella discussione che mi hai linkato, è stata usata in molti siti per creare funzioni di calcolo, persino su MRW :D
 
Allora, secondo me bisogna bisogna dividere il problema in sottopassaggi:
- crei una funzione differenza(fine, inizio) che dovrà fare il return dei giorni di differenza.
- attraverso la funzione diff, calcoli il numero totale di giorni che ci sono tra le due date, questa differenza chiamiamola $range
- sia per la data di inizio che per la data di fine, usi la funzione date (con parametro 'w') per calcolare il "numero" del giorno della settimana (in particolare se è Domenica l'output è 0, Lunedì 1, .. , Sabato 6), ipotizziamo che $ini=5 (la data di inizio è di Venerdì) e $fin=2 (la data di fine è di martedì)
- ora viene la prima parte difficile, cioè calcolare il numero di domeniche e sabati compresi tra le due date, provo a fare qualcosa di generale, poi probabilmente va sistemato in base a dettagli tipo estremi inclusi/esclusi ecc. Chiamiamo $sabati il numero di sabati e $domeniche il numero di domeniche. Entrambi sono uguali a floor($range/7) cioè il numero di settimane complete, poi però con degli if-else su $ini e $fin bisogna aggiungerci uno in più (ad esempio tra il 4/06 e il 29/06, cioè $ini=5 e $fin=2, ci sono floor(25/7)=3 settimane, però $domeniche=4 perché va considerata anche quella iniziale).
-a questo punto se l'utente lavora anche il sabato fai $range-$domeniche, altrimenti $range-$sabati-$domeniche.
-infine per togliere le date festive (che non sono di sabato o domenica perché quelle le hai già eliminate) fai un array e lo riempi con le date festive che vuoi inserire a mano, poi con un ciclo for controlli per ognuna se è compresa tra la data di inizio e di fine (puoi utilizzare strtotime) e se è un sabato/domenica, se non lo è incrementi una variabile che conta i giorni da eliminare, e poi fai la sottrazione finale

È un po' complicato ma dovrebbe funzionare
Eeeeh! :D

No ho capito, ma così non va è troppo complesso e inutile.
Per fare un esempio, se ti basi su "w" ti basta ciclare il range per sapere quante domeniche ci sono in mezzo e fai prima.

In ogni modo ho risolto alla fine, con un metodo che non mi piace perché induttivo e non testato a fondo, ma sembra funzionare.
Ecco il codice:
PHP:
//  Calcolo la durata della vacanza
    //  Verifica tra le date
    $d1 = new DateTimeImmutable($dataPartenza . ' 00:00:00');       
    $d2 = new DateTimeImmutable($dataRitorno . ' 00:00:00');
    
    $intervallo = $d1->diff($d2);
    $durata = $intervallo->format('%a') + 1;
    //  unset($d1, $d2, $intervallo);
    //  if ($durata == 0) $errore .= '<dt><strong>Durata tra le date sbagliata</strong></dt><dd>Hai inserito la stessa data sia in partenza che in arrivo.</dd>';
    
//  Calcolo periodo
    $intervallo = new DateInterval('P1W');
    $periodo = new DatePeriod($d1, $intervallo, $d2);
    echo "Periodo:<br>";
    $domeniche = 0;
    foreach ($periodo as $date) {
        $domeniche++;
    }
    echo <<<EOF
Domeniche presenti nel periodo: $domeniche
Periodo di vacanza di giorni: $durata
Partenza: $dataPartenza
Ritorno: $dataRitorno
EOF;
Ovviamente va migliorato è solo una bozza, ma sembra funzionare bene.
 
Buongiorno a tutti,
quanto da te richiesto (@MarcoGrazia ), "raffinato o meno" (ovvio si parla di codice) potrebbe essere sintetizzato in questo modo:
PHP:
namespace App\Libraries;

class WorkDays {

    protected $work_days=null;

    protected $nation_holidays=['01-01','01-06','04-25','05-01','06-02','08-15','11-01','12-08','12-25','12-26'];

    public $add_no_work=null;

    public $yearWorking=null;

    public  $results=
        [
            'calendar'=>[],
            'holidays'=>[],
            'cleared'=>[],
            'diff_date'=>[
            ]
        ];

    public function __construct($options=[]) {
        $this->makeOptions($options);
        (null===$this->work_days)?$this->work_days=range(1,5):null;
    }
    public static function getInstance($options=[]){
        return (new self($options));
    }

    private function makeOptions($opt=[]){
        array_walk($opt,function($v,$k){
            (property_exists($this,$k))?$this->$k=$v:null;
        });
        $this->yearWorking=array_unique($this->yearWorking);
        sort($this->yearWorking);
        array_map(function($el){
            $this->add_no_work[]=date('m-d',easter_date($el));
        },$this->yearWorking);
    }

    public static function createCalendar($year,$options=[]){
        $options['yearWorking']=[$year];
        $instance=self::getInstance($options);
        $instance->createEcc($instance)->saveCalendar($instance)->filteringCalendar($instance);
        dd($instance->results);
        return $instance->results;
    }

    public static function createDiffDate($start,$end,$options=[]){
        if(strtotime($start)>strtotime($end)){$t=$start;$start=$end;$end=$t;}
        $options['yearWorking']=[date('Y',strtotime($start)),date('Y',strtotime($end))];
        $instance=self::getInstance($options);
        $instance->saveCalendar($instance);
        $in=(new \DateTime())->setTimestamp(strtotime($start));
        $en=(new \DateTime())->setTimestamp(strtotime($end));
        return array_slice($instance->results['calendar'],$in->format('z'),($en->format('z')-$in->format('z'))+1);
    }


    private function saveCalendar(WorkDays $instance){
        $last=null;
        foreach ($instance->yearWorking as $year){
            array_map(function($el) use (&$instance,&$last,$year){
                (null===$last)?$last=new \DateTime($year.'-01-01'):$last->add(new \DateInterval('P1D'));
                $instance->results['calendar'][]=['day'=>$last->format('Y-m-d'),
                                                  'd_w'=>$last->format('w'),
                                                  'day_y'=>$last->format('z'),
                                                  'work'=>$this->checkDays($instance,$last)];
            },range(1,date('z', strtotime($year.'-12-31'))+1));
        }
        return $this;
    }

    private function filteringCalendar(WorkDays $instance){
        $instance->results['cleared']=array_values(array_filter($instance->results['calendar'],function($el){
            return($el['work'])?$el['day']:null;
        }));
        return $this;
    }

    private function checkDays(WorkDays $instance, \DateTime $check){
        $r=true;
        if(!in_array($check->format('w'),$instance->work_days))$r=false;
        if($r && in_array($check->format('Y-m-d'),$instance->results['holidays']))$r=false;
        return $r;
    }

    private function createEcc(WorkDays $instance){
        foreach ( $instance->yearWorking as $item ) {
            foreach ($instance->nation_holidays as $el)
                $instance->results['holidays'][]=$item.'-'.$el;

            foreach ($instance->add_no_work as $el)
                $instance->results['holidays'][]=$item.'-'.$el;
        }
        return $this;
    }
}
Ovviamente il vero calcolo dei giorni e la loro formattazione vengono lasciati al programmatore, derivabili da output (semplice array).
Un output esemplificativo:
PHP:
$days=WorkDays::createDiffDate('2021-05-06','2021-05-08');
dd($days);
/*
array:3 [
  0 => array:4 [
    "day" => "2021-05-06"
    "d_w" => "4"
    "day_y" => "125"
    "work" => true
  ]
  1 => array:4 [
    "day" => "2021-05-07"
    "d_w" => "5"
    "day_y" => "126"
    "work" => true
  ]
  2 => array:4 [
    "day" => "2021-05-08"
    "d_w" => "6"
    "day_y" => "127"
    "work" => false
  ]
]
*/
Applicando solo i giorni lavorativi si potrebbe riassumere come segue:
PHP:
$only_work=array_filter($days,function($el){
    return ($el['work'])?$el:null;
});
/*
array:2 [
  0 => array:4 [
    "day" => "2021-05-06"
    "d_w" => "4"
    "day_y" => "125"
    "work" => true
  ]
  1 => array:4 [
    "day" => "2021-05-07"
    "d_w" => "5"
    "day_y" => "126"
    "work" => true
  ]
]
*/
Per ottenere i festivi, totali e lavorativi basterebbe aggiungere questo:
PHP:
$result=[
    'festivi'=>array_diff_key($days,$only_work),
    'totali'=>$days,
    'lavorativi'=>$only_work,
];
Spero sia utile!

Up:
Ovvio il tutto configurabile passando un semplice array associativo per alterare i giorni lavorativi festività custom etc....
 
Ultima modifica:

Discussioni simili