Code Coverage
 
Classes and Traits
Functions and Methods
Lines
Total
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 11
CRAP
0.00% covered (danger)
0.00%
0 / 88
WorkDay
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 11
2862.00
0.00% covered (danger)
0.00%
0 / 88
 __construct
0.00% covered (danger)
0.00%
0 / 1
12.00
0.00% covered (danger)
0.00%
0 / 6
 is_workable
0.00% covered (danger)
0.00%
0 / 1
6.00
0.00% covered (danger)
0.00%
0 / 3
 is_holiday
0.00% covered (danger)
0.00%
0 / 1
12.00
0.00% covered (danger)
0.00%
0 / 5
 get_markedDay
0.00% covered (danger)
0.00%
0 / 1
12.00
0.00% covered (danger)
0.00%
0 / 5
 get_AllMarkedDays
0.00% covered (danger)
0.00%
0 / 1
30.00
0.00% covered (danger)
0.00%
0 / 8
 get_AllHolidays
0.00% covered (danger)
0.00%
0 / 1
12.00
0.00% covered (danger)
0.00%
0 / 5
 get_weekDaySchedule
0.00% covered (danger)
0.00%
0 / 1
2.00
0.00% covered (danger)
0.00%
0 / 1
 get_holidaysTemplate
0.00% covered (danger)
0.00%
0 / 1
2.00
0.00% covered (danger)
0.00%
0 / 1
 calcuateForYear
0.00% covered (danger)
0.00%
0 / 1
72.00
0.00% covered (danger)
0.00%
0 / 29
 get_easter_datetime
0.00% covered (danger)
0.00%
0 / 1
2.00
0.00% covered (danger)
0.00%
0 / 3
 validateWorkHoursByWeekDay
0.00% covered (danger)
0.00%
0 / 1
552.00
0.00% covered (danger)
0.00%
0 / 22
<?php
namespace ia\Work\WorkDay;
class WorkDay {
/*
1 ene
primer lunes feb (5 feb)  strtotime("first monday of $iYear", $timestamp))
tercer linues marzo (21 mar) strtotime("third monday of $iYear", $timestamp))
jue/vie santo
1 mayo
16 sept
tercer lunes noviembre, (20 nov) strtotime("third monday of $iYear", $timestamp))
25 dic
1º de diciembre de cada seis años (checar)
Bancario 12 dic
*/
    public static $defaultHolidays = [
        ['title'=>'Año Nuevo', 'is_holiday'=>'Si', 'day'=>1,  'month'=>1,  'type'=>'MonthDay'],
        ['title'=>'Aniversario de la Constitución', 'is_holiday'=>'Si', 'day'=>5,  'month'=>2,  'type'=>'first monday'],
        ['title'=>'Aniversario de Benito Juárez', 'is_holiday'=>'Si', 'day'=>21, 'month'=>3,  'type'=>'third monday'],
        ['title'=>'Viernes Santo', 'is_holiday'=>'Si', 'day'=>31, 'month'=>3,  'type'=>'holy week', 'inc'=>-2],
        ['title'=>'Jueves Santo', 'is_holiday'=>'Si', 'day'=>31, 'month'=>3,  'type'=>'holy week', 'inc'=>-3],
        ['title'=>'Día del trabajo', 'is_holiday'=>'Si', 'day'=>1,  'month'=>5,  'type'=>'MonthDay'],
        ['title'=>'Día de la Independencia', 'is_holiday'=>'Si', 'day'=>16, 'month'=>9,  'type'=>'MonthDay'],
        ['title'=>'Día de la Revolución', 'is_holiday'=>'Si', 'day'=>20, 'month'=>11, 'type'=>'third monday'],
        ['title'=>'Día del la Virgen de Guadalupe', 'is_holiday'=>'Si', 'day'=>12, 'month'=>12, 'type'=>'MonthDay'], // bancario
        ['title'=>'Navidad', 'is_holiday'=>'Si', 'day'=>25, 'month'=>12, 'type'=>'MonthDay'],
    ];
    //[ [ 'is_holiday'=>'Si', 'day'=>28, 'month'=>'3', 'type'=>'fixed|rule'  ... ] ]
    private $holidaysTemplate = [];
    // ['y-m-d']=>[ 'is_holiday'=>'Si', ... ]
    private $markedDays = [];
    // [$year] => [ 'ymd'=>[dayInfo]  ]
    private $yearsCalculated = [];
    private $monthForStrToTime = ['', 'January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'];
    //@array is [1=> [[9,14],[15,18]], 2=> [[9,17]], 3=> [[9,17]], 4=> [[9,17]], 5=> [[9,17]] ]
    private $weekDaySchedule;
    /**
     * WorkDay::__construct()
     *
     * @param array $holidaysTemplate
     * @param array $weekDaySchedules
     * @return
     */
    public function __construct($holidaysTemplate = null, $weekDaySchedules = null) {
        $this->holidaysTemplate = $holidaysTemplate === null ? self::$defaultHolidays : $holidaysTemplate;
        $this->weekDaySchedule = self::validateWorkHoursByWeekDay(
            $weekDaySchedules === null ?
                [1=> [[9,17]], 2=> [[9,17]], 3=> [[9,17]], 4=> [[9,17]], 5=> [[9,17]] ] :
                self::validateWorkHoursByWeekDay($weekDaySchedules)
        );
    }
    /**
     * Is it a workday
     *
     * @param string|int|DateTimeInterface $anyDate
     * @return bool
     */
    public function is_workable($anyDate) {
        if(!array_key_exists(self::dayOfWeek($anyDate), $this->weekDaySchedule )) {
            return false;
        }
        return !$this->is_holiday($anyDate);
    }
    /**
     * is it a holiday
     *
     * @param string|int|DateTimeInterface $anyDate
     * @return bool
     */
    public function is_holiday($anyDate) {
        $ymd = self::toYmd($anyDate);
        $year = substr($ymd, 0, 4);
        if(!array_key_exists($year, $this->yearsCalculated)) {
            $this->calcuateForYear($year);
        }
        return array_key_exists($ymd, $this->markedDays) && $this->markedDays[$ymd]['is_holiday'] === 'Si';
    }
    /**
     * Return info for a special day
     *
     * @param string|int|DateTimeInterface $anyDate
     * @return null|array
     */
    public function get_markedDay($anyDate) {
        $ymd = self::toYmd($anyDate);
        $year = substr($ymd, 0, 4);
        if(!array_key_exists($year, $this->yearsCalculated)) {
            $this->calcuateForYear($year);
        }
        return array_key_exists($ymd, $this->markedDays) ? $this->markedDays[$ymd] : null;
    }
    /**
     * espacial dates marked and holiday
     *
     * @param int|null $year
     * @return array with espacial dates marked and holiday
     */
    public function get_AllMarkedDays($year = null) {
        if(empty($year)) {
            $year = Date('Y');
            if(empty($this->yearsCalculated) || !array_key_exists($year, $this->yearsCalculated)) {
                $this->calcuateForYear($year);
            }
            return $this->markedDays;
        }
        if(!array_key_exists($year, $this->yearsCalculated)) {
            $this->calcuateForYear($year);
        }
        return $this->yearsCalculated[$year];
    }
    /**
     * Holidays
     *
     * @param int|null $year
     * @return array
     */
    public function get_AllHolidays($year = null) {
        $holidays = [];
        foreach($this->get_AllMarkedDays($year) as $ymd => $dayInfo) {
            if($dayInfo['is_holiday'] === 'Si') {
                $holidays[$ymd] = $dayInfo;
            }
        }
        return $holidays;
    }
    /**
     * WorkDay::get_weekDaySchedule()
     *
     * @return array
     */
    public function get_weekDaySchedule() {
        return $this->weekDaySchedule;
    }
    /**
     * WorkDay::get_holidaysTemplate()
     *
     * @return array
     */
    public function get_holidaysTemplate() {
        return $this->holidaysTemplate;
    }
// ______________________________________________________________________________________________________________
/*
// _______________
    //public function set_holidaysTemplate(array $holidaysTemplate) {
        $this->holidaysTemplate = $holidaysTemplate;
        $this->markedDays = [];
        $this->yearsCalculated = [];
    }
// Template ie al inicio poner el template para todos? _________________________________________
    //public static function get_holidaysTemplateDefault() {
        return self::$holidaysTemplateDefault;
    }
    //public static function set_holidaysTemplateDefault(array $holidaysTemplateDefault) {
        self::$holidaysTemplateDefault = $holidaysTemplateDefault;
    }
*/
// ________________________________________________________________________________________________________________________
    /**
     * WorkDay::calcuateForYear()
     *
     * @param int $year
     * @return void
     */
    private function calcuateForYear($year) {
        foreach($this->holidaysTemplate as $dayInfo) {
            $ymd = "$year-".str_pad($dayInfo['month'],2,'0',STR_PAD_LEFT)."-".str_pad($dayInfo['day'],2,'0',STR_PAD_LEFT);
            switch($dayInfo['type']) {
                case 'MonthDay':
                    $dayInfo['realday'] = $dayInfo['holiday'] = $ymd;
                    $this->yearsCalculated[$year][$ymd] = $this->markedDays[$ymd] = $dayInfo;
                    break;
                case 'ExactDate':
                    if($dayInfo['year'] == $year) {
                        $ymd = "$dayInfo[year]-".str_pad($dayInfo['month'],2,'0',STR_PAD_LEFT)."-".str_pad($dayInfo['day'],2,'0',STR_PAD_LEFT);
                        $dayInfo['realday'] = $dayInfo['holiday'] = $ymd;
                        $this->yearsCalculated[$year][$ymd] = $this->markedDays[$ymd] = $dayInfo;
                    }
                    break;
                case 'holy week':
                    $easterHoliday = $this->get_easter_datetime($year, empty($dayInfo['inc']) ? 0 : (int)$dayInfo['inc'] );
                    $ymd = $easterHoliday->format('Y-m-d');
                    $this->markedDays[$ymd] = $dayInfo;
                    $dayInfo['realday'] = $dayInfo['holiday'] = $ymd;
                    $this->yearsCalculated[substr($ymd,0,4)][$ymd] = $dayInfo;
                    break;
                default:
                    $dayInfo['realday'] = $ymd;
                    $ymdHoliday = Date('Y-m-d', strtotime("$dayInfo[type] of ".$this->monthForStrToTime[$dayInfo['month']]." $year") );
                    $dayInfo['holiday'] = $ymdHoliday;
                    $this->markedDays[$ymdHoliday] = $dayInfo;
                    $this->yearsCalculated[substr($ymdHoliday,0,4)][$ymdHoliday] = $dayInfo;
                    if($dayInfo['realday']!==$dayInfo['holiday']) {
                        $dayInfo['is_holiday'] = 'No';
                        $this->yearsCalculated[substr($ymd,0,4)][$ymd] = $this->markedDays[$ymd] = $dayInfo;
                    }
            }
        }
    }
    /**
     * WorkDay::get_easter_datetime()
     *
     * @param int $year
     * @param int $incDays
     * @return DateTime
     */
    private function get_easter_datetime($year, $incDays = 0) {
        $base = new DateTime("$year-03-21");
        $days = easter_days($year) + $incDays;
        return $base->add(new DateInterval("P{$days}D"));
    }
// __ WEEKENDS _______________________________________________________________
    /**
     * Checks if workHoursByWeekDay is valid.
     *
     * @param array $workHoursByWeekDay
     * @throws Exception
     * @return array
     */
    public static function validateWorkHoursByWeekDay(array $workHoursByWeekDay) {
        if(empty($workHoursByWeekDay) || !is_array($workHoursByWeekDay)) {
            throw new Exception("Invalid workHoursByWeekDay Expected [ 1=>[[8,14],[15,17], ]");
        }
        foreach($workHoursByWeekDay as $dayOfWeek => &$horarios) {
            if($dayOfWeek < 0 || $dayOfWeek > 6) {
                throw new Exception("Invalid day of week '$dayOfWeek' must be 0-6");
            }
            if(empty($horarios) || !is_array($horarios)) {
                throw new Exception("Day of week '$dayOfWeek' has invalid horarios must be '$dayOfWeek'=>[[8,14],[15,17],]");
            }
            foreach($horarios as &$rango1) {
                if($rango1[0]>$rango1[1]) {
                    $swap = $rango1[0];
                    $rango1[0] = $rango1[1];
                    $rango1[1] = $swap;
                }
            }
            unset($rango1);
            uasort($horarios, function($a, $b) { return $a[0]<=>$b[0]; });
            $prev = [-1,-1];
            foreach($horarios as $rango) {
                if(empty($rango) || !is_array($rango) || count($rango) !== 2 ||
                    $rango[0]<0 || $rango[0]>23 || $rango[1]<0 || $rango[1]>24 || $rango[0]>=$rango[1]
                ){
                    throw new Exception("Day of week '$dayOfWeek' has invalid hour range ".print_r($rango, true));
                }
                if($rango[0] < $prev[0] || $rango[0] < $prev[1] || $rango[1] < $prev[0] || $rango[1] < $prev[1] ) {
                    throw new Exception("Day of week '$dayOfWeek' hour range ".print_r($rango, true)." conflicts with previous entry: ".print_r($prev, true));
                }
                $prev = $rango;
            }
        }
        return $workHoursByWeekDay;
    }
}